public override async Task<MediaInfo[]> GetMediaInfosAsync(Uri Url) { if (!this.IsMatch(Url)) throw new NotSupportedException("不正確的網址"); HtmlDocument youtubePage = await this.DownloadHtmlAsync(Url); OnProcess?.Invoke(this,0.2); JObject mediaJObject = this.GetMediaJObject(youtubePage); if(mediaJObject["args"]["livestream"]?.Value<string>() == "1") { throw new NotSupportedException("不支援直播串流解析"); } string description = youtubePage.DocumentNode.SelectSingleNode("//meta[@name='description']").GetAttributeValue("content", null); JsFactory.Func decoding = await this.GetDecodingSignature("https:" + (string)mediaJObject["assets"]["js"]); Dictionary<string, string> streamFormatList = this.GetStreamFormatList(mediaJObject); JObject[] streamMap = this.GetStreamMap(mediaJObject); Dictionary<string, MediaTypes> MIME = new Dictionary<string, MediaTypes>() { ["audio"] = MediaTypes.Audio, ["video"] = MediaTypes.Video }; OnProcess?.Invoke(this, 0.5); double processValue = 0.5; List<MediaInfo> result = new List<MediaInfo>(); foreach(var item in streamMap) { MediaInfo resultItem = new MediaInfo(); #region 通用屬性 resultItem.SourceUrl = Url; resultItem.ExtractorType = this.GetType(); resultItem.Name = mediaJObject["args"]["title"].Value<string>(); resultItem.Duration = mediaJObject["args"]["length_seconds"].Value<int>(); resultItem.Description = description; resultItem.Thumbnail = new Uri(mediaJObject["args"]["thumbnail_url"].Value<string>()); resultItem.Type = (MediaTypes)Enum.Parse( typeof(MediaTypes), new string( item["type"]["mime"].Value<string>() .TakeWhile(Ch => Ch != '/').ToArray() ), true ); #region 連結解密 UriBuilder realUrlBuilder = new UriBuilder(item["url"].Value<string>()); var UrlSignature = realUrlBuilder.GetQueryParam("s") ?? realUrlBuilder.GetQueryParam("sig") ?? realUrlBuilder.GetQueryParam("signature"); var ItemSignature = item["s"]?.Value<string>() ?? item["sig"]?.Value<string>() ?? item["signature"]?.Value<string>(); realUrlBuilder.SetQueryParam("signature",decoding(UrlSignature ?? ItemSignature , UrlSignature != null)); resultItem.RealUrl = realUrlBuilder.Uri; #endregion #endregion #region 擴充屬性 resultItem.Attributes["mime"] = item["type"]["mime"].Value<string>(); resultItem.Attributes["codecs"] = item["type"]["codecs"]?.Value<string>(); resultItem.Attributes["author"] = mediaJObject["args"]["author"].Value<string>(); if (resultItem.Type == MediaTypes.Video) { resultItem.Attributes["size"] = item["size"]?.Value<string>() ?? streamFormatList[item["itag"]?.Value<string>()]; resultItem.Attributes["quality"] = item["quality"]?.Value<string>(); } else if(resultItem.Type == MediaTypes.Audio) { resultItem.Attributes["bitrate"] = item["bitrate"].Value<string>(); } #endregion result.Add(resultItem); processValue = processValue + 0.5 / streamMap.Count(); OnProcess?.Invoke(this, processValue); } MediaInfo[] output = result.ToArray(); OnCompleted?.Invoke(this, output); return output; }