private async Task <List <Hashtable> > DecipherFormats(
            List <Hashtable> formats,
            string videoHtmlPage)
        {
            var decodedFormats  = new List <Hashtable>();
            var cipheredFormats = new List <Hashtable>();

            Parallel.ForEach(formats, format =>
            {
                if (format.ContainsKey("signatureCipher"))
                {
                    var signatureCipher = format["signatureCipher"] as string;
                    signatureCipher?.Split('&')
                    .Select(pair => pair.Split('='))
                    .ToList()
                    .ForEach(keyAndValue => format.Add(keyAndValue[0], keyAndValue[1]));
                }

                var undecodedUrl = format["url"] as string;
                if (undecodedUrl != null)
                {
                    format["url"] = HttpUtility.UrlDecode(undecodedUrl);
                }

                var url = format["url"] as string ?? String.Empty;

                if (url.Contains("signature") ||
                    (!format.ContainsKey("s") &&
                     (url.Contains("&sig=") || url.Contains("&lsig="))
                    )
                    )
                {
                    decodedFormats.Add(format);
                    return;
                }
                cipheredFormats.Add(format);
            });

            if (cipheredFormats.Count != 0)
            {
                var baseJsUrl = ExtractBaseJsUrl(videoHtmlPage);

                // TODO add exception handling
                // wrapped within a Lazy to prevent multiple invocations
                var signatureDecoderFunc = SignatureDecoderCache.GetOrAdd(baseJsUrl, new Lazy <Task <SignatureDecoder> >(() => SignatureDecoderExtractor.ExtractDecoder(baseJsUrl)));

                var signatureDecoder = await signatureDecoderFunc.Value;

                var uncipheredFormats = cipheredFormats.Select(format =>
                {
                    format["url"] = (format["url"] as string)
                                    + "&sig="
                                    + signatureDecoder.Decode(format["s"] as string ?? String.Empty);
                    return(format);
                });

                decodedFormats.AddRange(uncipheredFormats);
            }

            return(decodedFormats);
        }
 public StreamingDataDecoder(IYoutubeApiConfig youtubeApiConfig)
 {
     YoutubeApiConfig          = youtubeApiConfig;
     SignatureDecoderExtractor = new SignatureDecoderExtractor(youtubeApiConfig.Client);
     SignatureDecoderCache     = new ConcurrentDictionary <string, Lazy <Task <SignatureDecoder> > >();
 }