/// <summary>
        /// Translates the given JSON object extracted from YouTube to its managed representation.
        /// </summary>
        protected virtual YouTubeMuxedStream TranslateMuxedStream(JObject input)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            var result = new YouTubeMuxedStream
            {
                iTag            = input.Value <int?>("itag"),
                Type            = input.Value <string>("mimeType"),
                AudioSampleRate = input.Value <long>("audioSampleRate"),
                ContentLength   = input.Value <long>("contentLength"),
                Quality         = input.Value <string>("quality"),
                QualityLabel    = input.Value <string>("qualityLabel"),
                Url             = input.Value <string>("url"),
                FrameSize       = input.ContainsKey("width") && input.ContainsKey("height")
                    ? new Size(input.Value <int>("width"), input.Value <int>("height"))
                    : null
            };

            result.Mime = ExtractActualMime(result.Type);

            // get cipher info (+signatureCipher)
            var cipher = input.Value <string>("cipher") ?? input.Value <string>("signatureCipher");

            if (!string.IsNullOrEmpty(cipher))
            {
                UpdateStreamCipherInfo(result, cipher);
            }

            return(result);
        }
        /// <summary>
        /// Gets invoked by <see cref="DownloadMetadata"/> to extract muxed streams from metadata.
        /// </summary>
        protected virtual List <YouTubeMuxedStream> ExtractMuxedStreamsMetadata(List <KeyValuePair <string, string> > fmtStreamMapEntries)
        {
            var list        = new List <YouTubeMuxedStream>();
            var propertySet = new HashSet <string>();
            var draft       = new YouTubeMuxedStream();

            void CommitDraft()
            {
                if (draft?.iTag != null && draft.Url != null)
                {
                    list.Add(draft);
                }
                propertySet.Clear();
                draft = new YouTubeMuxedStream();
            }

            void Feed(string key, string value)
            {
                // check for force commit
                if (key == null)
                {
                    CommitDraft();
                    return;
                }

                // check for rotation
                if (propertySet.Contains(key))
                {
                    CommitDraft();
                }
                propertySet.Add(key);

                // update draft
                switch (key.ToLowerInvariant())
                {
                case "itag":
                    draft.iTag = int.Parse(value);
                    break;

                case "type":
                    draft.Type = value;
                    var parts = value.Split(new[] { ';' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    draft.Mime = parts[0];
                    break;

                case "quality":
                    draft.Quality = value;
                    break;

                case "url":
                    draft.Url = value;
                    break;

                case "s":
                    draft.Signature = value;
                    break;
                }
            }

            UnmangleEntries(fmtStreamMapEntries, Feed);
            return(list);
        }