/// <summary>
        ///     Returns VideoInfo of the highest convertiable url of this youtube video
        /// </summary>
        public static async Task<VideoInfo> GetHighestAudioQualityDownloadUrlAsync(YoutubeContext context) {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (string.IsNullOrEmpty(context.Url))
                throw new ArgumentNullException(nameof(context.Url));
            var urls = (await GetDownloadUrlsAsync(context))._orderByQuality();
            var video = urls.FirstOrDefault();

            if (video?.RequiresDecryption == true)
                DecryptDownloadUrl(video);
            return video;
        }
        /// <summary>
        ///     Gets a list of <see cref="VideoInfo" />s for the specified URL.
        /// </summary>
        /// <param name="context">The context, must contain a url to the video in the Url property</param>
        /// <param name="decryptSignature">
        ///     A value indicating whether the video signatures should be decrypted or not. Decrypting
        ///     consists of a HTTP request for each <see cref="VideoInfo" />, so you may want to set
        ///     this to false and call <see cref="DecryptDownloadUrl" /> on your selected
        ///     <see
        ///         cref="VideoInfo" />
        ///     later.
        /// </param>
        /// <returns>A list of <see cref="VideoInfo" />s that can be used to download the video.</returns>
        /// <exception cref="VideoNotAvailableException">The video is not available.</exception>
        /// <exception cref="WebException">
        ///     An error occurred while downloading the YouTube page html.
        /// </exception>
        /// <exception cref="YoutubeParseException">The Youtube page could not be parsed.</exception>
        public static IEnumerable<VideoInfo> GetDownloadUrls(YoutubeContext context, bool decryptSignature = true) {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (context.Url == null)
                throw new ArgumentNullException(nameof(context.Url));
            context.OnProgresStateChanged(YoutubeStage.ProcessingUrls);
            string ytb;
            var isYoutubeUrl = TryNormalizeYoutubeUrl(context.Url, out ytb);
            context.Url = ytb;
            if (!isYoutubeUrl)
                throw new ArgumentException("URL is not a valid youtube URL!");
            _retry:
            try {
                var rpf = new RetryableProcessFailed("ParseHtml5Version") {Tag = context.Url};
                _redownload:
                var json = LoadJson(context.Url);
                var videoTitle = GetVideoTitle(json);
                var n = 0;
                var downloadUrls = ExtractDownloadUrls(json);
                var infos = GetVideoInfos(downloadUrls, videoTitle, context.Url).ToArray();

                try {
                    var htmlPlayerVersion = GetHtml5PlayerVersion(json);


                    foreach (var info in infos) {
                        info.HtmlPlayerVersion = htmlPlayerVersion;

                        if (decryptSignature && info.RequiresDecryption)
                            DecryptDownloadUrl(info);
                    }

                    return infos;
                } catch (Exception e) {
                    rpf.Defaultize(e);
                    context.OnDownloadFailed(rpf);
                    Console.WriteLine(e);
                    if (rpf.ShouldRetry)
                        goto _redownload;
                    return null;
                }
            } catch (Exception ex) when (ex.Message == "Result cannot be called on a failed Match.") {
                goto _retry;
            } catch (Exception ex) {
                if (ex is WebException || ex is VideoNotAvailableException)
                    throw;

                ThrowYoutubeParseException(ex, context.Url);
            } //Message 

            return null; // Will never happen, but the compiler requires it
        }
 public YoutubeThumbnail(YoutubeContext context) : this(context.Url) {
     _thumbnail_task.ContinueWith(task => 
     context.OnProgresStateChanged(YoutubeStage.ThumbnailFound));
 }
 /// <summary>
 ///     Initializes a new instance of the <see cref="VideoDownloader" /> class.
 /// </summary>
 public VideoDownloader(YoutubeContext context, bool findBestVideoInfo = false) : base(context) {
     if (findBestVideoInfo)
         DownloadUrlResolver.FindHighestVideoQualityDownloadUrl(context);
 }
        /// <summary>
        ///     Returns VideoInfo of the highest convertiable url of this youtube video
        /// </summary>
        public static async Task FindHighestVideoQualityDownloadUrlAsync(YoutubeContext context, VideoType type = VideoType.Mp4) {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            var urls = await DownloadUrlResolver.GetDownloadUrlsAsync(context);
            context.VideoInfo = urls.Where(vi=>vi.VideoType==type).OrderByDescending(info => info.Resolution).FirstOrDefault();

            if (context.VideoInfo?.RequiresDecryption == true)
                DownloadUrlResolver.DecryptDownloadUrl(context.VideoInfo);
        }
        /// <summary>
        ///     Returns VideoInfo of the highest convertiable url of this youtube video
        /// </summary>
        public static async Task FindHighestAudioQualityDownloadUrlAsync(YoutubeContext context) {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            var urls = await DownloadUrlResolver.GetDownloadUrlsAsync(context);
            context.VideoInfo = urls.Where(vid => vid.CanExtractAudio).OrderByDescending(info => info.AudioBitrate).FirstOrDefault();

            if (context.VideoInfo?.RequiresDecryption == true)
                DownloadUrlResolver.DecryptDownloadUrl(context.VideoInfo);
        }
        /// <summary>
        ///     Gets a list of <see cref="VideoInfo" />s for the specified URL.
        /// </summary>
        /// <param name="context">The context, must contain a url to the video in the Url property</param>
        /// <param name="decryptSignature">
        ///     A value indicating whether the video signatures should be decrypted or not. Decrypting
        ///     consists of a HTTP request for each <see cref="VideoInfo" />, so you may want to set
        ///     this to false and call <see cref="DecryptDownloadUrl" /> on your selected
        ///     <see
        ///         cref="VideoInfo" />
        ///     later.
        /// </param>
        /// <returns>A list of <see cref="VideoInfo" />s that can be used to download the video.</returns>
        /// <exception cref="VideoNotAvailableException">The video is not available.</exception>
        /// <exception cref="WebException">
        ///     An error occurred while downloading the YouTube page html.
        /// </exception>
        /// <exception cref="YoutubeParseException">The Youtube page could not be parsed.</exception>
        public static async Task<IEnumerable<VideoInfo>> GetDownloadUrlsAsync(YoutubeContext context, bool decryptSignature = true) {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (context.Url == null)
                throw new ArgumentNullException(nameof(context.Url));

            context.OnProgresStateChanged(YoutubeStage.ProcessingUrls);
            string ytb;
            var isYoutubeUrl = TryNormalizeYoutubeUrl(context.Url, out ytb);
            context.Url = ytb;

            if (!isYoutubeUrl)
                throw new ArgumentException("URL is not a valid youtube URL!");

            try {
                var rpf = new RetryableProcessFailed("ParseHtml5Version") {Tag = context.Url };
                _redownload:
                var json = await LoadJsonAsync(context.Url);
                var videoTitle = GetVideoTitle(json);
                int n = 0;
                var downloadUrls = ExtractDownloadUrls(json);
                var infos = GetVideoInfos(downloadUrls, videoTitle, context.Url);
                string htmlPlayerVersion;

                try {
                    htmlPlayerVersion = GetHtml5PlayerVersion(json);
                } catch (Exception e) {
                    rpf.Defaultize(e);
                    context.OnDownloadFailed(rpf);
                    if (rpf.ShouldRetry)
                        goto _redownload;
                    return null;
                }

                foreach (var info in infos) {
                    info.HtmlPlayerVersion = htmlPlayerVersion;

                    if (decryptSignature && info.RequiresDecryption)
                        await DecryptDownloadUrlAsync(info);
                }

                return infos;
            } catch (Exception ex) {
                if (ex is WebException || ex is VideoNotAvailableException)
                    throw;

                ThrowYoutubeParseException(ex, context.Url);
            }

            return null; // Will never happen, but the compiler requires it
        }
 protected bool Equals(YoutubeContext other)
 {
     return _contextid.Equals(other._contextid) && string.Equals(_url, other._url) && Equals(VideoInfo, other.VideoInfo);
 }
 /// <summary>
 ///     Initializes a new instance of the <see cref="AudioDownloader" /> class.
 /// </summary>
 /// <param name="context">The current context</param>
 /// <param name="findBestVideoInfo">Will execute FindBestQualityVideoInfo to the current context</param>
 public AudioDownloader(YoutubeContext context, bool findBestVideoInfo = false) : base(context) {
     if (findBestVideoInfo)
         context.FindHighestAudioQualityDownloadUrl();
 }