/// <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)); }