private async Task <StreamManifest> GetManifestAsync(StreamContext streamContext)
        {
            // To make sure there are no duplicates streams, group them by tag
            var streams = new Dictionary <int, IStreamInfo>();

            foreach (var streamInfo in streamContext.StreamInfoProviders)
            {
                var tag = streamInfo.GetTag();
                var url = streamInfo.GetUrl();

                // Signature
                var signature          = streamInfo.TryGetSignature();
                var signatureParameter = streamInfo.TryGetSignatureParameter() ?? "signature";

                if (!string.IsNullOrWhiteSpace(signature))
                {
                    signature = streamContext.CipherOperations.Decipher(signature);
                    url       = Url.SetQueryParameter(url, signatureParameter, signature);
                }

                // Content length
                var contentLength = streamInfo.TryGetContentLength() ??
                                    await _httpClient.TryGetContentLengthAsync(url, false) ??
                                    0;

                if (contentLength <= 0)
                {
                    continue; // broken stream URL?
                }
                // Common
                var container = Container.Parse(streamInfo.GetContainer());
                var fileSize  = new FileSize(contentLength);
                var bitrate   = new Bitrate(streamInfo.GetBitrate());

                var audioCodec = streamInfo.TryGetAudioCodec();
                var videoCodec = streamInfo.TryGetVideoCodec();

                // Muxed or Video-only
                if (!string.IsNullOrWhiteSpace(videoCodec))
                {
                    var framerate = new Framerate(streamInfo.TryGetFramerate() ?? 24);

                    var videoQualityLabel = streamInfo.TryGetVideoQualityLabel() ??
                                            Heuristics.GetVideoQualityLabel(tag, framerate.FramesPerSecond);

                    var videoQuality = Heuristics.GetVideoQuality(videoQualityLabel);

                    var videoWidth      = streamInfo.TryGetVideoWidth();
                    var videoHeight     = streamInfo.TryGetVideoHeight();
                    var videoResolution = videoWidth != null && videoHeight != null
                        ? new VideoResolution(videoWidth.Value, videoHeight.Value)
                        : Heuristics.GetVideoResolution(videoQuality);

                    // Muxed
                    if (!string.IsNullOrWhiteSpace(audioCodec))
                    {
                        streams[tag] = new MuxedStreamInfo(
                            tag,
                            url,
                            container,
                            fileSize,
                            bitrate,
                            audioCodec,
                            videoCodec,
                            videoQualityLabel,
                            videoQuality,
                            videoResolution,
                            framerate
                            );
                    }
                    // Video-only
                    else
                    {
                        streams[tag] = new VideoOnlyStreamInfo(
                            tag,
                            url,
                            container,
                            fileSize,
                            bitrate,
                            videoCodec,
                            videoQualityLabel,
                            videoQuality,
                            videoResolution,
                            framerate
                            );
                    }
                }
                // Audio-only
                else if (!string.IsNullOrWhiteSpace(audioCodec))
                {
                    streams[tag] = new AudioOnlyStreamInfo(
                        tag,
                        url,
                        container,
                        fileSize,
                        bitrate,
                        audioCodec
                        );
                }
                else
                {
#if DEBUG
                    throw FatalFailureException.Generic("Stream info doesn't contain audio/video codec information.");
#endif
                }
            }

            return(new StreamManifest(streams.Values.ToArray()));
        }
        private async ValueTask PopulateStreamInfosAsync(
            ICollection <IStreamInfo> streamInfos,
            IEnumerable <IStreamInfoExtractor> streamInfoExtractors,
            SignatureScrambler signatureScrambler,
            CancellationToken cancellationToken = default)
        {
            foreach (var streamInfoExtractor in streamInfoExtractors)
            {
                var itag =
                    streamInfoExtractor.TryGetItag() ??
                    throw new YoutubeExplodeException("Could not extract stream itag.");

                var urlRaw =
                    streamInfoExtractor.TryGetUrl() ??
                    throw new YoutubeExplodeException("Could not extract stream URL.");

                // Unscramble URL
                var signature          = streamInfoExtractor.TryGetSignature();
                var signatureParameter = streamInfoExtractor.TryGetSignatureParameter();
                var url = UnscrambleStreamUrl(signatureScrambler, urlRaw, signature, signatureParameter);

                // Get content length
                var contentLength =
                    streamInfoExtractor.TryGetContentLength() ??
                    await _httpClient.TryGetContentLengthAsync(url, false, cancellationToken) ??
                    0;

                if (contentLength <= 0)
                {
                    continue; // broken stream URL?
                }
                var fileSize = new FileSize(contentLength);

                var container =
                    streamInfoExtractor.TryGetContainer()?.Pipe(s => new Container(s)) ??
                    throw new YoutubeExplodeException("Could not extract stream container.");

                var bitrate =
                    streamInfoExtractor.TryGetBitrate()?.Pipe(s => new Bitrate(s)) ??
                    throw new YoutubeExplodeException("Could not extract stream bitrate.");

                var audioCodec = streamInfoExtractor.TryGetAudioCodec();
                var videoCodec = streamInfoExtractor.TryGetVideoCodec();

                // Muxed or video-only stream
                if (!string.IsNullOrWhiteSpace(videoCodec))
                {
                    var framerate = streamInfoExtractor.TryGetFramerate() ?? 24;

                    var videoQualityLabel = streamInfoExtractor.TryGetVideoQualityLabel();

                    var videoQuality = !string.IsNullOrWhiteSpace(videoQualityLabel)
                        ? VideoQuality.FromLabel(videoQualityLabel, framerate)
                        : VideoQuality.FromItag(itag, framerate);

                    var videoWidth  = streamInfoExtractor.TryGetVideoWidth();
                    var videoHeight = streamInfoExtractor.TryGetVideoHeight();

                    var videoResolution = videoWidth is not null && videoHeight is not null
                        ? new Resolution(videoWidth.Value, videoHeight.Value)
                        : videoQuality.GetDefaultVideoResolution();

                    // Muxed
                    if (!string.IsNullOrWhiteSpace(audioCodec))
                    {
                        var streamInfo = new MuxedStreamInfo(
                            url,
                            container,
                            fileSize,
                            bitrate,
                            audioCodec,
                            videoCodec,
                            videoQuality,
                            videoResolution
                            );

                        streamInfos.Add(streamInfo);
                    }
                    // Video-only
                    else
                    {
                        var streamInfo = new VideoOnlyStreamInfo(
                            url,
                            container,
                            fileSize,
                            bitrate,
                            videoCodec,
                            videoQuality,
                            videoResolution
                            );

                        streamInfos.Add(streamInfo);
                    }
                }
                // Audio-only
                else if (!string.IsNullOrWhiteSpace(audioCodec))
                {
                    var streamInfo = new AudioOnlyStreamInfo(
                        url,
                        container,
                        fileSize,
                        bitrate,
                        audioCodec
                        );

                    streamInfos.Add(streamInfo);
                }
                else
                {
                    Debug.Fail("Stream doesn't contain neither audio nor video codec information.");
                }
            }
        }