private async ValueTask PopulateStreamInfosAsync(
            ICollection <IStreamInfo> streamInfos,
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);

            // Try to get player source (failing is ok because there's a decent chance we won't need it)
            var playerSourceUrl = watchPage.TryGetPlayerSourceUrl();
            var playerSource    = !string.IsNullOrWhiteSpace(playerSourceUrl)
                ? await _controller.GetPlayerSourceAsync(playerSourceUrl, cancellationToken)
                : null;

            var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null;

            var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse();

            if (playerResponseFromWatchPage is not null)
            {
                var purchasePreviewVideoId = playerResponseFromWatchPage.TryGetPreviewVideoId();
                if (!string.IsNullOrWhiteSpace(purchasePreviewVideoId))
                {
                    throw new VideoRequiresPurchaseException(
                              $"Video '{videoId}' requires purchase and cannot be played.",
                              purchasePreviewVideoId
                              );
                }

                if (playerResponseFromWatchPage.IsVideoPlayable())
                {
                    // Extract streams from watch page
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        watchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from player response
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        playerResponseFromWatchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from DASH manifest
                    var dashManifestUrlRaw = playerResponseFromWatchPage.TryGetDashManifestUrl();
                    if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw))
                    {
                        var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw);
                        var dashManifest    = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken);

                        await PopulateStreamInfosAsync(
                            streamInfos,
                            dashManifest.GetStreams(),
                            signatureScrambler,
                            cancellationToken
                            );
                    }
                }

                // If successfully retrieved streams, return
                if (streamInfos.Any())
                {
                    return;
                }
            }

            // Try to get streams from video info
            // Note: it seems YouTube has stopped using get_video_info and replaced it with an
            // internal API endpoint that resolves player response directly.
            // This may be an area for future improvement.
            var signatureTimestamp = playerSource?.TryGetSignatureTimestamp() ?? "";
            var videoInfo          = await _controller.GetVideoInfoAsync(videoId, signatureTimestamp, cancellationToken);

            var playerResponseFromVideoInfo = videoInfo.TryGetPlayerResponse();

            if (playerResponseFromVideoInfo is not null)
            {
                if (playerResponseFromVideoInfo.IsVideoPlayable())
                {
                    // Extract streams from video info
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        videoInfo.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from player response
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        playerResponseFromVideoInfo.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from DASH manifest
                    var dashManifestUrlRaw = playerResponseFromVideoInfo.TryGetDashManifestUrl();
                    if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw))
                    {
                        var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw);
                        var dashManifest    = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken);

                        await PopulateStreamInfosAsync(
                            streamInfos,
                            dashManifest.GetStreams(),
                            signatureScrambler,
                            cancellationToken
                            );
                    }

                    // If successfully retrieved streams, return
                    if (streamInfos.Any())
                    {
                        return;
                    }
                }
                else
                {
                    var errorMessage = playerResponseFromVideoInfo.TryGetVideoPlayabilityError();
                    throw new VideoUnplayableException($"Video '{videoId}' is unplayable. Reason: {errorMessage}.");
                }
            }

            // Couldn't extract any streams
            throw new VideoUnplayableException($"Video '{videoId}' does not contain any playable streams.");
        }
Exemple #2
0
        private async ValueTask PopulateStreamInfosAsync(
            ICollection <IStreamInfo> streamInfos,
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);

            // Try to get player source (failing is ok because there's a decent chance we won't need it)
            var playerSourceUrl = watchPage.TryGetPlayerSourceUrl();
            var playerSource    = !string.IsNullOrWhiteSpace(playerSourceUrl)
                ? await _controller.GetPlayerSourceAsync(playerSourceUrl, cancellationToken)
                : null;

            File.WriteAllText(@"c:\temp\base.js", playerSource?.Content);

            var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null;

            var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse();

            if (playerResponseFromWatchPage is not null)
            {
                var purchasePreviewVideoId = playerResponseFromWatchPage.TryGetPreviewVideoId();
                if (!string.IsNullOrWhiteSpace(purchasePreviewVideoId))
                {
                    throw new VideoRequiresPurchaseException(
                              $"Video '{videoId}' requires purchase and cannot be played.",
                              purchasePreviewVideoId
                              );
                }

                if (playerResponseFromWatchPage.IsVideoPlayable())
                {
                    // Extract streams from watch page
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        watchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from player response
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        playerResponseFromWatchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from DASH manifest
                    var dashManifestUrlRaw = playerResponseFromWatchPage.TryGetDashManifestUrl();
                    if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw))
                    {
                        var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw);
                        var dashManifest    = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken);

                        await PopulateStreamInfosAsync(
                            streamInfos,
                            dashManifest.GetStreams(),
                            signatureScrambler,
                            cancellationToken
                            );
                    }
                }

                // If successfully retrieved streams, return
                if (streamInfos.Any())
                {
                    return;
                }
            }

            // Couldn't extract any streams
            throw new VideoUnplayableException($"Video '{videoId}' does not contain any playable streams.");
        }