/// <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 configuration
            var playerConfiguration = await GetPlayerConfigurationAsync(videoId).ConfigureAwait(false);

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

            // Get muxed stream infos
            var muxedStreamInfoDics = playerConfiguration.MuxedStreamInfosUrlEncoded.EmptyIfNull().Split(",").Select(Url.SplitQuery);

            foreach (var streamInfoDic in muxedStreamInfoDics)
            {
                // Extract info
                var itag = streamInfoDic["itag"].ParseInt();
                var url  = streamInfoDic["url"];

                // Decipher signature if needed
                var signature = streamInfoDic.GetValueOrDefault("s");
                if (!signature.IsNullOrWhiteSpace())
                {
                    // Get cipher operations (cached)
                    var cipherOperations = await GetCipherOperationsAsync(playerConfiguration.PlayerSourceUrl).ConfigureAwait(false);

                    // Decipher signature
                    signature = cipherOperations.Decipher(signature);

                    // Set the corresponding parameter in the URL
                    var signatureParameter = streamInfoDic.GetValueOrDefault("sp") ?? "signature";
                    url = Url.SetQueryParameter(url, signatureParameter, signature);
                }

                // Try to extract content length, otherwise get it manually
                var contentLength = Regex.Match(url, @"clen=(\d+)").Groups[1].Value.ParseLongOrDefault();
                if (contentLength <= 0)
                {
                    // Send HEAD request and get content length
                    contentLength = await _httpClient.GetContentLengthAsync(url, false).ConfigureAwait(false) ?? 0;

                    // If content length is still not available - stream is gone or faulty
                    if (contentLength <= 0)
                    {
                        continue;
                    }
                }

                // Extract container
                var containerRaw = streamInfoDic["type"].SubstringUntil(";").SubstringAfter("/");
                var container    = Heuristics.ContainerFromString(containerRaw);

                // Extract audio encoding
                var audioEncodingRaw = streamInfoDic["type"].SubstringAfter("codecs=\"").SubstringUntil("\"").Split(", ").Last();
                var audioEncoding    = Heuristics.AudioEncodingFromString(audioEncodingRaw);

                // Extract video encoding
                var videoEncodingRaw = streamInfoDic["type"].SubstringAfter("codecs=\"").SubstringUntil("\"").Split(", ").First();
                var videoEncoding    = Heuristics.VideoEncodingFromString(videoEncodingRaw);

                // Determine video quality from itag
                var videoQuality = Heuristics.VideoQualityFromItag(itag);

                // Determine video quality label from video quality
                var videoQualityLabel = Heuristics.VideoQualityToLabel(videoQuality);

                // Determine video resolution from video quality
                var resolution = Heuristics.VideoQualityToResolution(videoQuality);

                // Add to list
                muxedStreamInfoMap[itag] = new MuxedStreamInfo(itag, url, container, contentLength, audioEncoding, videoEncoding,
                                                               videoQualityLabel, videoQuality, resolution);
            }

            // Get adaptive stream infos
            var adaptiveStreamInfoDics = playerConfiguration.AdaptiveStreamInfosUrlEncoded.EmptyIfNull().Split(",").Select(Url.SplitQuery);

            foreach (var streamInfoDic in adaptiveStreamInfoDics)
            {
                // Extract info
                var itag    = streamInfoDic["itag"].ParseInt();
                var url     = streamInfoDic["url"];
                var bitrate = streamInfoDic["bitrate"].ParseLong();

                // Decipher signature if needed
                var signature = streamInfoDic.GetValueOrDefault("s");
                if (!signature.IsNullOrWhiteSpace())
                {
                    // Get cipher operations (cached)
                    var cipherOperations = await GetCipherOperationsAsync(playerConfiguration.PlayerSourceUrl).ConfigureAwait(false);

                    // Decipher signature
                    signature = cipherOperations.Decipher(signature);

                    // Set the corresponding parameter in the URL
                    var signatureParameter = streamInfoDic.GetValueOrDefault("sp") ?? "signature";
                    url = Url.SetQueryParameter(url, signatureParameter, signature);
                }

                // Try to extract content length, otherwise get it manually
                var contentLength = streamInfoDic.GetValueOrDefault("clen").ParseLongOrDefault();
                if (contentLength <= 0)
                {
                    // Send HEAD request and get content length
                    contentLength = await _httpClient.GetContentLengthAsync(url, false).ConfigureAwait(false) ?? 0;

                    // If content length is still not available - stream is gone or faulty
                    if (contentLength <= 0)
                    {
                        continue;
                    }
                }

                // Extract container
                var containerRaw = streamInfoDic["type"].SubstringUntil(";").SubstringAfter("/");
                var container    = Heuristics.ContainerFromString(containerRaw);

                // If audio-only
                if (streamInfoDic["type"].StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
                {
                    // Extract audio encoding
                    var audioEncodingRaw = streamInfoDic["type"].SubstringAfter("codecs=\"").SubstringUntil("\"");
                    var audioEncoding    = Heuristics.AudioEncodingFromString(audioEncodingRaw);

                    // Add stream
                    audioStreamInfoMap[itag] = new AudioStreamInfo(itag, url, container, contentLength, bitrate, audioEncoding);
                }
                // If video-only
                else
                {
                    // Extract video encoding
                    var videoEncodingRaw = streamInfoDic["type"].SubstringAfter("codecs=\"").SubstringUntil("\"");
                    var videoEncoding    = !videoEncodingRaw.Equals("unknown", StringComparison.OrdinalIgnoreCase)
                        ? Heuristics.VideoEncodingFromString(videoEncodingRaw)
                        : VideoEncoding.Av1; // HACK: issue 246

                    // Extract video quality label and video quality
                    var videoQualityLabel = streamInfoDic["quality_label"];
                    var videoQuality      = Heuristics.VideoQualityFromLabel(videoQualityLabel);

                    // Extract resolution
                    var width      = streamInfoDic["size"].SubstringUntil("x").ParseInt();
                    var height     = streamInfoDic["size"].SubstringAfter("x").ParseInt();
                    var resolution = new VideoResolution(width, height);

                    // Extract framerate
                    var framerate = streamInfoDic["fps"].ParseInt();

                    // Add to list
                    videoStreamInfoMap[itag] = new VideoStreamInfo(itag, url, container, contentLength, bitrate, videoEncoding,
                                                                   videoQualityLabel, videoQuality, resolution, framerate);
                }
            }

            // Get dash manifest
            var dashManifestUrl = playerConfiguration.DashManifestUrl;

            if (!dashManifestUrl.IsNullOrWhiteSpace())
            {
                // Extract signature
                var signature = Regex.Match(dashManifestUrl, "/s/(.*?)(?:/|$)").Groups[1].Value;

                // Decipher signature if needed
                if (!signature.IsNullOrWhiteSpace())
                {
                    // Get cipher operations (cached)
                    var cipherOperations = await GetCipherOperationsAsync(playerConfiguration.PlayerSourceUrl).ConfigureAwait(false);

                    // Decipher signature
                    signature = cipherOperations.Decipher(signature);

                    // Set the corresponding parameter in the URL
                    dashManifestUrl = Url.SetRouteParameter(dashManifestUrl, "signature", signature);
                }

                // Get DASH manifest XML
                var dashManifestXml = await GetDashManifestXmlAsync(dashManifestUrl).ConfigureAwait(false);

                // Get representation nodes (skip partial streams)
                var streamInfoXmls = dashManifestXml.Descendants("Representation").Where(s =>
                                                                                         s.Descendants("Initialization").FirstOrDefault()?.Attribute("sourceURL")?.Value.Contains("sq/") != true);

                // Get DASH stream infos
                foreach (var streamInfoXml in streamInfoXmls)
                {
                    // Extract info
                    var itag          = (int)streamInfoXml.Attribute("id");
                    var url           = (string)streamInfoXml.Element("BaseURL");
                    var contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong();
                    var bitrate       = (long)streamInfoXml.Attribute("bandwidth");

                    // Extract container
                    var containerRaw = Regex.Match(url, @"mime[/=]\w*%2F([\w\d]*)").Groups[1].Value.UrlDecode();
                    var container    = Heuristics.ContainerFromString(containerRaw);

                    // If audio-only
                    if (streamInfoXml.Element("AudioChannelConfiguration") != null)
                    {
                        // Extract audio encoding
                        var audioEncodingRaw = (string)streamInfoXml.Attribute("codecs");
                        var audioEncoding    = Heuristics.AudioEncodingFromString(audioEncodingRaw);

                        // Add to list
                        audioStreamInfoMap[itag] = new AudioStreamInfo(itag, url, container, contentLength, bitrate, audioEncoding);
                    }
                    // If video-only
                    else
                    {
                        // Extract video encoding
                        var videoEncodingRaw = (string)streamInfoXml.Attribute("codecs");
                        var videoEncoding    = Heuristics.VideoEncodingFromString(videoEncodingRaw);

                        // Extract resolution
                        var width      = (int)streamInfoXml.Attribute("width");
                        var height     = (int)streamInfoXml.Attribute("height");
                        var resolution = new VideoResolution(width, height);

                        // Extract framerate
                        var framerate = (int)streamInfoXml.Attribute("frameRate");

                        // Determine video quality from itag
                        var videoQuality = Heuristics.VideoQualityFromItag(itag);

                        // Determine video quality label from video quality and framerate
                        var videoQualityLabel = Heuristics.VideoQualityToLabel(videoQuality, framerate);

                        // Add to list
                        videoStreamInfoMap[itag] = new VideoStreamInfo(itag, url, container, contentLength, bitrate, videoEncoding,
                                                                       videoQualityLabel, videoQuality, resolution, framerate);
                    }
                }
            }

            // 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,
                                          playerConfiguration.HlsManifestUrl, playerConfiguration.ValidUntil));
        }