Exemple #1
0
        /// <summary>
        /// Gets the manifest containing information about available closed caption tracks on the specified video.
        /// </summary>
        public async ValueTask <ClosedCaptionManifest> GetManifestAsync(
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var videoInfo = await _controller.GetVideoInfoAsync(videoId, cancellationToken);

            var playerResponse =
                videoInfo.TryGetPlayerResponse() ??
                throw new YoutubeExplodeException("Could not extract player response.");

            var trackInfos = playerResponse
                             .GetClosedCaptionTracks()
                             .Select(t =>
            {
                var url =
                    t.TryGetUrl() ??
                    throw new YoutubeExplodeException("Could not extract track URL.");

                var languageCode =
                    t.TryGetLanguageCode() ??
                    throw new YoutubeExplodeException("Could not extract track language code.");

                var languageName =
                    t.TryGetLanguageName() ??
                    throw new YoutubeExplodeException("Could not extract track language name.");

                var isAutoGenerated = t.IsAutoGenerated();

                return(new ClosedCaptionTrackInfo(
                           url,
                           new Language(languageCode, languageName),
                           isAutoGenerated
                           ));
            })
                             .ToArray();

            return(new ClosedCaptionManifest(trackInfos));
        }
        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.");
        }