Esempio n. 1
0
        /// <inheritdoc />
        public async Task <MediaStreamInfoSet> GetVideoMediaStreamInfosAsync(string videoId)
        {
            videoId.GuardNotNull(nameof(videoId));

            if (!ValidateVideoId(videoId))
            {
                throw new ArgumentException($"Invalid YouTube video ID [{videoId}].", nameof(videoId));
            }

            // Get player context
            var playerContext = await GetVideoPlayerContextAsync(videoId).ConfigureAwait(false);

            // Get video info
            var videoInfo = await GetVideoInfoAsync(videoId, playerContext.Sts).ConfigureAwait(false);

            // Check if requires purchase
            if (videoInfo.ContainsKey("ypc_vid"))
            {
                var previewVideoId = videoInfo["ypc_vid"];
                throw new VideoRequiresPurchaseException(videoId, previewVideoId);
            }

            // Prepare stream info collections
            var muxedStreamInfoMap = new Dictionary <int, MuxedStreamInfo>();
            var audioStreamInfoMap = new Dictionary <int, AudioStreamInfo>();
            var videoStreamInfoMap = new Dictionary <int, VideoStreamInfo>();

            // Resolve muxed streams
            var muxedStreamInfosEncoded = videoInfo.GetOrDefault("url_encoded_fmt_stream_map");

            if (muxedStreamInfosEncoded.IsNotBlank())
            {
                foreach (var streamEncoded in muxedStreamInfosEncoded.Split(","))
                {
                    var streamInfoDic = UrlEx.SplitQuery(streamEncoded);

                    // Extract values
                    var itag = streamInfoDic["itag"].ParseInt();
                    var url  = streamInfoDic["url"];
                    var sig  = streamInfoDic.GetOrDefault("s");

#if RELEASE
                    if (!MediaStreamInfo.IsKnown(itag))
                    {
                        continue;
                    }
#endif

                    // Decipher signature if needed
                    if (sig.IsNotBlank())
                    {
                        var playerSource =
                            await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                        sig = playerSource.Decipher(sig);
                        url = UrlEx.SetQueryParameter(url, "signature", sig);
                    }

                    // Probe stream and get content length
                    var contentLength = await _httpClient.GetContentLengthAsync(url, false).ConfigureAwait(false) ?? -1;

                    // If probe failed, the stream is gone or faulty
                    if (contentLength < 0)
                    {
                        continue;
                    }

                    var streamInfo = new MuxedStreamInfo(itag, url, contentLength);
                    muxedStreamInfoMap[itag] = streamInfo;
                }
            }

            // Resolve adaptive streams
            var adaptiveStreamInfosEncoded = videoInfo.GetOrDefault("adaptive_fmts");
            if (adaptiveStreamInfosEncoded.IsNotBlank())
            {
                foreach (var streamEncoded in adaptiveStreamInfosEncoded.Split(","))
                {
                    var streamInfoDic = UrlEx.SplitQuery(streamEncoded);

                    // Extract values
                    var itag          = streamInfoDic["itag"].ParseInt();
                    var url           = streamInfoDic["url"];
                    var sig           = streamInfoDic.GetOrDefault("s");
                    var contentLength = streamInfoDic["clen"].ParseLong();
                    var bitrate       = streamInfoDic["bitrate"].ParseLong();

#if RELEASE
                    if (!MediaStreamInfo.IsKnown(itag))
                    {
                        continue;
                    }
#endif

                    // Decipher signature if needed
                    if (sig.IsNotBlank())
                    {
                        var playerSource =
                            await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                        sig = playerSource.Decipher(sig);
                        url = UrlEx.SetQueryParameter(url, "signature", sig);
                    }

                    // Check if audio
                    var isAudio = streamInfoDic["type"].Contains("audio/");

                    // If audio stream
                    if (isAudio)
                    {
                        var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate);
                        audioStreamInfoMap[itag] = streamInfo;
                    }
                    // If video stream
                    else
                    {
                        // Parse additional data
                        var size         = streamInfoDic["size"];
                        var width        = size.SubstringUntil("x").ParseInt();
                        var height       = size.SubstringAfter("x").ParseInt();
                        var resolution   = new VideoResolution(width, height);
                        var framerate    = streamInfoDic["fps"].ParseInt();
                        var qualityLabel = streamInfoDic["quality_label"];

                        var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate,
                                                             qualityLabel);
                        videoStreamInfoMap[itag] = streamInfo;
                    }
                }
            }

            // Resolve dash streams
            var dashManifestUrl = videoInfo.GetOrDefault("dashmpd");
            if (dashManifestUrl.IsNotBlank())
            {
                // Parse signature
                var sig = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value;

                // Decipher signature if needed
                if (sig.IsNotBlank())
                {
                    var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    sig             = playerSource.Decipher(sig);
                    dashManifestUrl = UrlEx.SetRouteParameter(dashManifestUrl, "signature", sig);
                }

                // Get the manifest
                var response = await _httpClient.GetStringAsync(dashManifestUrl).ConfigureAwait(false);

                var dashManifestXml = XElement.Parse(response).StripNamespaces();
                var streamsXml      = dashManifestXml.Descendants("Representation");

                // Parse streams
                foreach (var streamXml in streamsXml)
                {
                    // Skip partial streams
                    if (streamXml.Descendants("Initialization").FirstOrDefault()?.Attribute("sourceURL")?.Value
                        .Contains("sq/") == true)
                    {
                        continue;
                    }

                    // Extract values
                    var itag    = (int)streamXml.Attribute("id");
                    var url     = (string)streamXml.Element("BaseURL");
                    var bitrate = (long)streamXml.Attribute("bandwidth");

#if RELEASE
                    if (!MediaStreamInfo.IsKnown(itag))
                    {
                        continue;
                    }
#endif

                    // Parse content length
                    var contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong();

                    // Check if audio stream
                    var isAudio = streamXml.Element("AudioChannelConfiguration") != null;

                    // If audio stream
                    if (isAudio)
                    {
                        var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate);
                        audioStreamInfoMap[itag] = streamInfo;
                    }
                    // If video stream
                    else
                    {
                        // Parse additional data
                        var width      = (int)streamXml.Attribute("width");
                        var height     = (int)streamXml.Attribute("height");
                        var resolution = new VideoResolution(width, height);
                        var framerate  = (int)streamXml.Attribute("frameRate");

                        var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate);
                        videoStreamInfoMap[itag] = streamInfo;
                    }
                }
            }

            // Get the raw HLS stream playlist (*.m3u8)
            var hlsLiveStreamUrl = videoInfo.GetOrDefault("hlsvp");

            // Finalize stream info collections
            var muxedStreamInfos = muxedStreamInfoMap.Values.OrderByDescending(s => s.VideoQuality).ToArray();
            var audioStreamInfos = audioStreamInfoMap.Values.OrderByDescending(s => s.Bitrate).ToArray();
            var videoStreamInfos = videoStreamInfoMap.Values.OrderByDescending(s => s.VideoQuality).ToArray();

            return(new MediaStreamInfoSet(muxedStreamInfos, audioStreamInfos, videoStreamInfos, hlsLiveStreamUrl));
        }
        /// <inheritdoc />
        public async Task <MediaStreamInfoSet> GetVideoMediaStreamInfosAsync(string videoId)
        {
            videoId.GuardNotNull(nameof(videoId));

            if (!ValidateVideoId(videoId))
            {
                throw new ArgumentException($"Invalid YouTube video ID [{videoId}].", nameof(videoId));
            }

            // Get player context
            var playerContext = await GetVideoPlayerContextAsync(videoId).ConfigureAwait(false);

            // Get parser
            var parser = await GetVideoInfoParserAsync(videoId, playerContext.Sts).ConfigureAwait(false);

            // Check if video requires purchase
            var previewVideoId = parser.ParsePreviewVideoId();

            if (previewVideoId.IsNotBlank())
            {
                throw new VideoRequiresPurchaseException(videoId, previewVideoId);
            }

            // Prepare stream info maps
            var muxedStreamInfoMap = new Dictionary <int, MuxedStreamInfo>();
            var audioStreamInfoMap = new Dictionary <int, AudioStreamInfo>();
            var videoStreamInfoMap = new Dictionary <int, VideoStreamInfo>();

            // Parse muxed stream infos
            foreach (var muxedStreamInfoParser in parser.GetMuxedStreamInfos())
            {
                // Extract itag
                var itag = muxedStreamInfoParser.ParseItag();

#if RELEASE
                // Skip unknown itags
                if (!ItagHelper.IsKnown(itag))
                {
                    continue;
                }
#endif

                // Extract URL
                var url = muxedStreamInfoParser.ParseUrl();

                // Decipher signature if needed
                var signature = muxedStreamInfoParser.ParseSignature();
                if (signature.IsNotBlank())
                {
                    var playerSource =
                        await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    signature = playerSource.Decipher(signature);
                    url       = UrlEx.SetQueryParameter(url, "signature", signature);
                }

                // Probe stream and get content length
                var contentLength = await _httpClient.GetContentLengthAsync(url, false).ConfigureAwait(false) ?? -1;

                // If probe failed or content length is 0, it means the stream is gone or faulty
                if (contentLength <= 0)
                {
                    continue;
                }

                var streamInfo = new MuxedStreamInfo(itag, url, contentLength);
                muxedStreamInfoMap[itag] = streamInfo;
            }

            // Parse adaptive stream infos
            foreach (var adaptiveStreamInfoParser in parser.GetAdaptiveStreamInfos())
            {
                // Extract info
                var itag = adaptiveStreamInfoParser.ParseItag();

#if RELEASE
                // Skip unknown itags
                if (!ItagHelper.IsKnown(itag))
                {
                    continue;
                }
#endif

                // Extract content length
                var contentLength = adaptiveStreamInfoParser.ParseContentLength();

                // If content length is 0, it means that the stream is gone or faulty
                if (contentLength <= 0)
                {
                    continue;
                }

                // Extract URL
                var url = adaptiveStreamInfoParser.ParseUrl();

                // Decipher signature if needed
                var signature = adaptiveStreamInfoParser.ParseSignature();
                if (signature.IsNotBlank())
                {
                    var playerSource =
                        await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    signature = playerSource.Decipher(signature);
                    url       = UrlEx.SetQueryParameter(url, "signature", signature);
                }

                // Extract bitrate
                var bitrate = adaptiveStreamInfoParser.ParseBitrate();

                // If audio-only
                if (adaptiveStreamInfoParser.ParseIsAudioOnly())
                {
                    var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate);
                    audioStreamInfoMap[itag] = streamInfo;
                }
                // If video-only
                else
                {
                    // Extract info
                    var width        = adaptiveStreamInfoParser.ParseWidth();
                    var height       = adaptiveStreamInfoParser.ParseHeight();
                    var framerate    = adaptiveStreamInfoParser.ParseFramerate();
                    var qualityLabel = adaptiveStreamInfoParser.ParseQualityLabel();

                    var resolution = new VideoResolution(width, height);
                    var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate,
                                                         qualityLabel);
                    videoStreamInfoMap[itag] = streamInfo;
                }
            }

            // Parse dash manifest
            var dashManifestUrl = parser.ParseDashManifestUrl();
            if (dashManifestUrl.IsNotBlank())
            {
                // Parse signature
                var signature = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value;

                // Decipher signature if needed
                if (signature.IsNotBlank())
                {
                    var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    signature       = playerSource.Decipher(signature);
                    dashManifestUrl = UrlEx.SetRouteParameter(dashManifestUrl, "signature", signature);
                }

                // Get the dash manifest parser
                var dashManifestParser = await GetDashManifestParserAsync(dashManifestUrl).ConfigureAwait(false);

                // Parse dash stream infos
                foreach (var dashStreamInfoParser in dashManifestParser.GetStreamInfos())
                {
                    // Extract itag
                    var itag = dashStreamInfoParser.ParseItag();

#if RELEASE
                    // Skip unknown itags
                    if (!ItagHelper.IsKnown(itag))
                    {
                        continue;
                    }
#endif

                    // Extract info
                    var url           = dashStreamInfoParser.ParseUrl();
                    var contentLength = dashStreamInfoParser.ParseContentLength();
                    var bitrate       = dashStreamInfoParser.ParseBitrate();

                    // If audio-only
                    if (dashStreamInfoParser.ParseIsAudioOnly())
                    {
                        var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate);
                        audioStreamInfoMap[itag] = streamInfo;
                    }
                    // If video-only
                    else
                    {
                        // Parse additional data
                        var width     = dashStreamInfoParser.ParseWidth();
                        var height    = dashStreamInfoParser.ParseHeight();
                        var framerate = dashStreamInfoParser.ParseFramerate();

                        var resolution = new VideoResolution(width, height);
                        var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate);
                        videoStreamInfoMap[itag] = streamInfo;
                    }
                }
            }

            // Get the raw HLS stream playlist (*.m3u8)
            var hlsPlaylistUrl = parser.ParseHlsPlaylistUrl();

            // Finalize stream info collections
            var muxedStreamInfos = muxedStreamInfoMap.Values.OrderByDescending(s => s.VideoQuality).ToArray();
            var audioStreamInfos = audioStreamInfoMap.Values.OrderByDescending(s => s.Bitrate).ToArray();
            var videoStreamInfos = videoStreamInfoMap.Values.OrderByDescending(s => s.VideoQuality).ToArray();

            return(new MediaStreamInfoSet(muxedStreamInfos, audioStreamInfos, videoStreamInfos, hlsPlaylistUrl));
        }