mirror of
https://github.com/NohamR/N_m3u8DL-RE.git
synced 2025-05-25 14:51:52 +00:00
564 lines
30 KiB
C#
564 lines
30 KiB
C#
using N_m3u8DL_RE.Common.Entity;
|
||
using N_m3u8DL_RE.Common.Enum;
|
||
using N_m3u8DL_RE.Common.Util;
|
||
using N_m3u8DL_RE.Parser.Config;
|
||
using N_m3u8DL_RE.Parser.Constants;
|
||
using N_m3u8DL_RE.Parser.Util;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Xml;
|
||
using System.Xml.Linq;
|
||
|
||
namespace N_m3u8DL_RE.Parser.Extractor
|
||
{
|
||
//https://blog.csdn.net/leek5533/article/details/117750191
|
||
internal class DASHExtractor2 : IExtractor
|
||
{
|
||
private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC;
|
||
|
||
public ExtractorType ExtractorType => ExtractorType.MPEG_DASH;
|
||
|
||
private string MpdUrl = string.Empty;
|
||
private string BaseUrl = string.Empty;
|
||
private string MpdContent = string.Empty;
|
||
public ParserConfig ParserConfig { get; set; }
|
||
|
||
public DASHExtractor2(ParserConfig parserConfig)
|
||
{
|
||
this.ParserConfig = parserConfig;
|
||
SetInitUrl();
|
||
}
|
||
|
||
|
||
private void SetInitUrl()
|
||
{
|
||
this.MpdUrl = ParserConfig.Url ?? string.Empty;
|
||
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl))
|
||
this.BaseUrl = ParserConfig.BaseUrl;
|
||
else
|
||
this.BaseUrl = this.MpdUrl;
|
||
}
|
||
|
||
private string ExtendBaseUrl(XElement element, string oriBaseUrl)
|
||
{
|
||
var target = element.Elements().Where(e => e.Name.LocalName == "BaseURL").FirstOrDefault();
|
||
if (target != null)
|
||
{
|
||
oriBaseUrl = ParserUtil.CombineURL(oriBaseUrl, target.Value);
|
||
}
|
||
|
||
return oriBaseUrl;
|
||
}
|
||
|
||
private double? GetFrameRate(XElement element)
|
||
{
|
||
var frameRate = element.Attribute("frameRate")?.Value;
|
||
if (frameRate != null && frameRate.Contains("/"))
|
||
{
|
||
var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]);
|
||
frameRate = d.ToString("0.000");
|
||
return Convert.ToDouble(frameRate);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||
{
|
||
var streamList = new List<StreamSpec>();
|
||
|
||
this.MpdContent = rawText;
|
||
this.PreProcessContent();
|
||
|
||
|
||
var xmlDocument = XDocument.Parse(MpdContent);
|
||
|
||
//选中第一个MPD节点
|
||
var mpdElement = xmlDocument.Elements().First(e => e.Name.LocalName == "MPD");
|
||
|
||
//类型 static点播, dynamic直播
|
||
var type = mpdElement.Attribute("type")?.Value;
|
||
bool isLive = type == "dynamic";
|
||
|
||
//分片最大时长
|
||
var maxSegmentDuration = mpdElement.Attribute("maxSegmentDuration")?.Value;
|
||
//分片从该时间起可用
|
||
var availabilityStartTime = mpdElement.Attribute("availabilityStartTime")?.Value;
|
||
//在availabilityStartTime的前XX段时间,分片有效
|
||
var timeShiftBufferDepth = mpdElement.Attribute("timeShiftBufferDepth")?.Value;
|
||
//MPD发布时间
|
||
var publishTime = mpdElement.Attribute("publishTime")?.Value;
|
||
//MPD总时长
|
||
var mediaPresentationDuration = mpdElement.Attribute("mediaPresentationDuration")?.Value;
|
||
|
||
//读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
||
var baseUrlElement = mpdElement.Elements().Where(e => e.Name.LocalName == "BaseURL").FirstOrDefault();
|
||
if (baseUrlElement != null)
|
||
{
|
||
var baseUrl = baseUrlElement.Value;
|
||
if (baseUrl.Contains("kkbox.com.tw/")) baseUrl = baseUrl.Replace("//https:%2F%2F", "//");
|
||
this.BaseUrl = ParserUtil.CombineURL(this.MpdUrl, baseUrl);
|
||
}
|
||
|
||
//全部Period
|
||
var periods = mpdElement.Elements().Where(e => e.Name.LocalName == "Period");
|
||
foreach (var period in periods)
|
||
{
|
||
//本Period时长
|
||
var periodDuration = period.Attribute("duration")?.Value;
|
||
|
||
//本Period ID
|
||
var periodId = period.Attribute("id")?.Value;
|
||
|
||
//最终分片会使用的baseurl
|
||
var segBaseUrl = this.BaseUrl;
|
||
|
||
//处理baseurl嵌套
|
||
segBaseUrl = ExtendBaseUrl(period, segBaseUrl);
|
||
|
||
var adaptationSetsBaseUrl = segBaseUrl;
|
||
|
||
//本Period中的全部AdaptationSet
|
||
var adaptationSets = period.Elements().Where(e => e.Name.LocalName == "AdaptationSet");
|
||
foreach (var adaptationSet in adaptationSets)
|
||
{
|
||
//处理baseurl嵌套
|
||
segBaseUrl = ExtendBaseUrl(adaptationSet, segBaseUrl);
|
||
|
||
var representationsBaseUrl = segBaseUrl;
|
||
|
||
var mimeType = adaptationSet.Attribute("contentType")?.Value ?? adaptationSet.Attribute("mimeType")?.Value;
|
||
var frameRate = GetFrameRate(adaptationSet);
|
||
//本AdaptationSet中的全部Representation
|
||
var representations = adaptationSet.Elements().Where(e => e.Name.LocalName == "Representation");
|
||
foreach (var representation in representations)
|
||
{
|
||
//处理baseurl嵌套
|
||
segBaseUrl = ExtendBaseUrl(representation, segBaseUrl);
|
||
|
||
if (mimeType == null)
|
||
{
|
||
mimeType = representation.Attribute("contentType")?.Value ?? representation.Attribute("mimeType")?.Value ?? "";
|
||
}
|
||
var bandwidth = representation.Attribute("bandwidth");
|
||
StreamSpec streamSpec = new();
|
||
streamSpec.OriginalUrl = ParserConfig.OriginalUrl;
|
||
streamSpec.PeriodId = periodId;
|
||
streamSpec.Playlist = new Playlist();
|
||
streamSpec.Playlist.MediaParts.Add(new MediaPart());
|
||
streamSpec.GroupId = representation.Attribute("id")?.Value;
|
||
streamSpec.Bandwidth = Convert.ToInt32(bandwidth?.Value ?? "0");
|
||
streamSpec.Codecs = representation.Attribute("codecs")?.Value ?? adaptationSet.Attribute("codecs")?.Value;
|
||
streamSpec.Language = representation.Attribute("lang")?.Value ?? adaptationSet.Attribute("lang")?.Value;
|
||
streamSpec.FrameRate = frameRate ?? GetFrameRate(representation);
|
||
streamSpec.Resolution = representation.Attribute("width")?.Value != null ? $"{representation.Attribute("width")?.Value}x{representation.Attribute("height")?.Value}" : null;
|
||
streamSpec.Url = MpdUrl;
|
||
streamSpec.MediaType = mimeType.Split('/')[0] switch
|
||
{
|
||
"text" => MediaType.SUBTITLES,
|
||
"audio" => MediaType.AUDIO,
|
||
_ => null
|
||
};
|
||
//推测后缀名
|
||
var mType = representation.Attribute("mimeType")?.Value ?? adaptationSet.Attribute("mimeType")?.Value;
|
||
if (mType != null)
|
||
{
|
||
var mTypeSplit = mType.Split('/');
|
||
streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null;
|
||
}
|
||
//优化字幕场景识别
|
||
if (streamSpec.Codecs == "stpp" || streamSpec.Codecs == "wvtt")
|
||
{
|
||
streamSpec.MediaType = MediaType.SUBTITLES;
|
||
}
|
||
//优化字幕场景识别
|
||
var role = representation.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault() ?? adaptationSet.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault();
|
||
if (role != null)
|
||
{
|
||
var v = role.Attribute("value")?.Value;
|
||
if (v == "subtitle")
|
||
streamSpec.MediaType = MediaType.SUBTITLES;
|
||
if (mType != null && mType.Contains("ttml"))
|
||
streamSpec.Extension = "ttml";
|
||
}
|
||
streamSpec.Playlist.IsLive = isLive;
|
||
//设置刷新间隔 timeShiftBufferDepth / 2
|
||
if (timeShiftBufferDepth != null)
|
||
{
|
||
streamSpec.Playlist.RefreshIntervalMs = XmlConvert.ToTimeSpan(timeShiftBufferDepth).TotalMilliseconds / 2;
|
||
}
|
||
|
||
//读取声道数量
|
||
var audioChannelConfiguration = adaptationSet.Elements().Concat(representation.Elements()).Where(e => e.Name.LocalName == "AudioChannelConfiguration").FirstOrDefault();
|
||
if (audioChannelConfiguration != null)
|
||
{
|
||
streamSpec.Channels = audioChannelConfiguration.Attribute("value")?.Value;
|
||
}
|
||
|
||
//发布时间
|
||
if (!string.IsNullOrEmpty(publishTime))
|
||
{
|
||
streamSpec.PublishTime = DateTime.Parse(publishTime);
|
||
}
|
||
|
||
|
||
//第一种形式 SegmentBase
|
||
var segmentBaseElement = representation.Elements().Where(e => e.Name.LocalName == "SegmentBase").FirstOrDefault();
|
||
if (segmentBaseElement != null)
|
||
{
|
||
//处理init url
|
||
var initialization = segmentBaseElement.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
||
if (initialization != null)
|
||
{
|
||
var sourceURL = initialization.Attribute("sourceURL")?.Value;
|
||
if (sourceURL == null)
|
||
{
|
||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add
|
||
(
|
||
new MediaSegment()
|
||
{
|
||
Index = 0,
|
||
Url = segBaseUrl,
|
||
Duration = XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds
|
||
}
|
||
);
|
||
}
|
||
else
|
||
{
|
||
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
||
var initRange = initialization.Attribute("range")?.Value;
|
||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||
streamSpec.Playlist.MediaInit.Url = initUrl;
|
||
if (initRange != null)
|
||
{
|
||
var (start, expect) = ParserUtil.ParseRange(initRange);
|
||
streamSpec.Playlist.MediaInit.StartRange = start;
|
||
streamSpec.Playlist.MediaInit.ExpectLength = expect;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//第二种形式 SegmentList.SegmentList
|
||
var segmentList = representation.Elements().Where(e => e.Name.LocalName == "SegmentList").FirstOrDefault();
|
||
if (segmentList != null)
|
||
{
|
||
var durationStr = segmentList.Attribute("duration")?.Value;
|
||
//处理init url
|
||
var initialization = segmentList.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
||
if (initialization != null)
|
||
{
|
||
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
||
var initRange = initialization.Attribute("range")?.Value;
|
||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||
streamSpec.Playlist.MediaInit.Url = initUrl;
|
||
if (initRange != null)
|
||
{
|
||
var (start, expect) = ParserUtil.ParseRange(initRange);
|
||
streamSpec.Playlist.MediaInit.StartRange = start;
|
||
streamSpec.Playlist.MediaInit.ExpectLength = expect;
|
||
}
|
||
}
|
||
//处理分片
|
||
var segmentURLs = segmentList.Elements().Where(e => e.Name.LocalName == "SegmentURL");
|
||
var timescaleStr = segmentList.Attribute("timescale")?.Value ?? "1";
|
||
for (int segmentIndex = 0; segmentIndex < segmentURLs.Count(); segmentIndex++)
|
||
{
|
||
var segmentURL = segmentURLs.ElementAt(segmentIndex);
|
||
var mediaUrl = ParserUtil.CombineURL(segBaseUrl, segmentURL.Attribute("media")?.Value!);
|
||
var mediaRange = segmentURL.Attribute("mediaRange")?.Value;
|
||
var timesacle = Convert.ToInt32(timescaleStr);
|
||
var duration = Convert.ToInt64(durationStr);
|
||
MediaSegment mediaSegment = new();
|
||
mediaSegment.Duration = duration / (double)timesacle;
|
||
mediaSegment.Url = mediaUrl;
|
||
mediaSegment.Index = segmentIndex;
|
||
if (mediaRange != null)
|
||
{
|
||
var (start, expect) = ParserUtil.ParseRange(mediaRange);
|
||
mediaSegment.StartRange = start;
|
||
mediaSegment.ExpectLength = expect;
|
||
}
|
||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||
}
|
||
}
|
||
|
||
//第三种形式 SegmentTemplate+SegmentTimeline
|
||
//通配符有$RepresentationID$ $Bandwidth$ $Number$ $Time$
|
||
|
||
//adaptationSets中的segmentTemplate
|
||
var segmentTemplateElementsOuter = adaptationSet.Elements().Where(e => e.Name.LocalName == "SegmentTemplate");
|
||
//representation中的segmentTemplate
|
||
var segmentTemplateElements = representation.Elements().Where(e => e.Name.LocalName == "SegmentTemplate");
|
||
if (segmentTemplateElements.Any() || segmentTemplateElementsOuter.Any())
|
||
{
|
||
//优先使用最近的元素
|
||
var segmentTemplate = (segmentTemplateElements.FirstOrDefault() ?? segmentTemplateElementsOuter.FirstOrDefault())!;
|
||
var segmentTemplateOuter = (segmentTemplateElementsOuter.FirstOrDefault() ?? segmentTemplateElements.FirstOrDefault())!;
|
||
var varDic = new Dictionary<string, object?>();
|
||
varDic[DASHTags.TemplateRepresentationID] = streamSpec.GroupId;
|
||
varDic[DASHTags.TemplateBandwidth] = bandwidth?.Value;
|
||
//timesacle
|
||
var timescaleStr = segmentTemplate.Attribute("timescale")?.Value ?? segmentTemplateOuter.Attribute("timescale")?.Value ?? "1";
|
||
var durationStr = segmentTemplate.Attribute("duration")?.Value ?? segmentTemplateOuter.Attribute("duration")?.Value;
|
||
var startNumberStr = segmentTemplate.Attribute("startNumber")?.Value ?? segmentTemplateOuter.Attribute("startNumber")?.Value ?? "1";
|
||
//处理init url
|
||
var initialization = segmentTemplate.Attribute("initialization")?.Value ?? segmentTemplateOuter.Attribute("initialization")?.Value;
|
||
if (initialization != null)
|
||
{
|
||
var initUrl = ParserUtil.ReplaceVars(ParserUtil.CombineURL(segBaseUrl, initialization), varDic);
|
||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||
streamSpec.Playlist.MediaInit.Url = initUrl;
|
||
}
|
||
//处理分片
|
||
var media = segmentTemplate.Attribute("media")?.Value ?? segmentTemplateOuter.Attribute("media")?.Value;
|
||
var segmentTimeline = segmentTemplate.Elements().Where(e => e.Name.LocalName == "SegmentTimeline").FirstOrDefault();
|
||
if (segmentTimeline != null)
|
||
{
|
||
//使用了SegmentTimeline 结果精确
|
||
var segNumber = Convert.ToInt32(startNumberStr);
|
||
var Ss = segmentTimeline.Elements().Where(e => e.Name.LocalName == "S");
|
||
var currentTime = 0L;
|
||
var segIndex = 0;
|
||
foreach (var S in Ss)
|
||
{
|
||
//每个S元素包含三个属性:@t(start time)\@r(repeat count)\@d(duration)
|
||
var _startTimeStr = S.Attribute("t")?.Value;
|
||
var _durationStr = S.Attribute("d")?.Value;
|
||
var _repeatCountStr = S.Attribute("r")?.Value;
|
||
|
||
if (_startTimeStr != null) currentTime = Convert.ToInt64(_startTimeStr);
|
||
var _duration = Convert.ToInt64(_durationStr);
|
||
var timescale = Convert.ToInt32(timescaleStr);
|
||
var _repeatCount = Convert.ToInt64(_repeatCountStr);
|
||
varDic[DASHTags.TemplateTime] = currentTime;
|
||
varDic[DASHTags.TemplateNumber] = segNumber++;
|
||
var mediaUrl = ParserUtil.ReplaceVars(ParserUtil.CombineURL(segBaseUrl, media!), varDic);
|
||
MediaSegment mediaSegment = new();
|
||
mediaSegment.Url = mediaUrl;
|
||
mediaSegment.Duration = _duration / (double)timescale;
|
||
mediaSegment.Index = segIndex++;
|
||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||
if (_repeatCount < 0)
|
||
{
|
||
//负数表示一直重复 直到period结束 注意减掉已经加入的1个片段
|
||
_repeatCount = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / _duration) - 1;
|
||
}
|
||
for (long i = 0; i < _repeatCount; i++)
|
||
{
|
||
currentTime += _duration;
|
||
MediaSegment _mediaSegment = new();
|
||
varDic[DASHTags.TemplateTime] = currentTime;
|
||
varDic[DASHTags.TemplateNumber] = segNumber++;
|
||
var _mediaUrl = ParserUtil.ReplaceVars(ParserUtil.CombineURL(segBaseUrl, media!), varDic);
|
||
_mediaSegment.Url = _mediaUrl;
|
||
_mediaSegment.Index = segIndex++;
|
||
_mediaSegment.Duration = _duration / (double)timescale;
|
||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(_mediaSegment);
|
||
}
|
||
currentTime += _duration;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//没用SegmentTimeline 需要计算总分片数量 不精确
|
||
var timescale = Convert.ToInt32(timescaleStr);
|
||
var startNumber = Convert.ToInt64(startNumberStr);
|
||
var duration = Convert.ToInt32(durationStr);
|
||
var totalNumber = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / duration);
|
||
//直播的情况,需要自己计算totalNumber
|
||
if (totalNumber == 0 && isLive)
|
||
{
|
||
var now = publishTime == null ? DateTime.Now : DateTime.Parse(publishTime);
|
||
var availableTime = DateTime.Parse(availabilityStartTime!);
|
||
var ts = now - availableTime;
|
||
var updateTs = XmlConvert.ToTimeSpan(timeShiftBufferDepth!);
|
||
//(当前时间到发布时间的时间差 - 最小刷新间隔) / 分片时长
|
||
startNumber += (long)((ts.TotalSeconds - updateTs.TotalSeconds) * timescale / duration);
|
||
totalNumber = (long)(updateTs.TotalSeconds * timescale / duration);
|
||
}
|
||
for (long index = startNumber, segIndex = 0; index < startNumber + totalNumber; index++, segIndex++)
|
||
{
|
||
varDic[DASHTags.TemplateNumber] = index;
|
||
var mediaUrl = ParserUtil.ReplaceVars(ParserUtil.CombineURL(segBaseUrl, media!), varDic);
|
||
MediaSegment mediaSegment = new();
|
||
mediaSegment.Url = mediaUrl;
|
||
mediaSegment.Index = isLive ? index : segIndex; //直播直接用startNumber
|
||
mediaSegment.Duration = duration / (double)timescale;
|
||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||
}
|
||
}
|
||
}
|
||
|
||
//如果依旧没被添加分片,直接把BaseUrl塞进去就好
|
||
if (streamSpec.Playlist.MediaParts[0].MediaSegments.Count == 0)
|
||
{
|
||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add
|
||
(
|
||
new MediaSegment()
|
||
{
|
||
Index = 0,
|
||
Url = segBaseUrl,
|
||
Duration = XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds
|
||
}
|
||
);
|
||
}
|
||
|
||
//判断加密情况
|
||
if (adaptationSet.Elements().Concat(representation.Elements()).Any(e => e.Name.LocalName == "ContentProtection"))
|
||
{
|
||
if (streamSpec.Playlist.MediaInit != null)
|
||
{
|
||
streamSpec.Playlist.MediaInit.EncryptInfo.Method = DEFAULT_METHOD;
|
||
}
|
||
foreach (var item in streamSpec.Playlist.MediaParts[0].MediaSegments)
|
||
{
|
||
item.EncryptInfo.Method = DEFAULT_METHOD;
|
||
}
|
||
}
|
||
|
||
//处理同一ID分散在不同Period的情况
|
||
var _index = streamList.FindIndex(_f => _f.PeriodId != streamSpec.PeriodId && _f.GroupId == streamSpec.GroupId && _f.Resolution == streamSpec.Resolution && _f.MediaType == streamSpec.MediaType);
|
||
if (_index > -1)
|
||
{
|
||
if (isLive)
|
||
{
|
||
//直播,这种情况直接略过新的
|
||
}
|
||
else
|
||
{
|
||
//点播,这种情况作为新的part出现
|
||
var startIndex = streamList[_index].Playlist!.MediaParts.Last().MediaSegments.Last().Index + 1;
|
||
var enumerator = streamSpec.Playlist.MediaParts[0].MediaSegments.GetEnumerator();
|
||
while (enumerator.MoveNext())
|
||
{
|
||
enumerator.Current.Index += startIndex;
|
||
}
|
||
streamList[_index].Playlist!.MediaParts.Add(new MediaPart()
|
||
{
|
||
MediaSegments = streamSpec.Playlist.MediaParts[0].MediaSegments
|
||
});
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//修复mp4类型字幕
|
||
if (streamSpec.MediaType == MediaType.SUBTITLES && streamSpec.Extension == "mp4")
|
||
{
|
||
streamSpec.Extension = "m4s";
|
||
}
|
||
//分片默认后缀m4s
|
||
if (streamSpec.MediaType != MediaType.SUBTITLES && (streamSpec.Extension == null || streamSpec.Playlist.MediaParts.Sum(x => x.MediaSegments.Count) > 1))
|
||
{
|
||
streamSpec.Extension = "m4s";
|
||
}
|
||
streamList.Add(streamSpec);
|
||
}
|
||
//恢复BaseURL相对位置
|
||
segBaseUrl = representationsBaseUrl;
|
||
}
|
||
//恢复BaseURL相对位置
|
||
segBaseUrl = adaptationSetsBaseUrl;
|
||
}
|
||
}
|
||
|
||
//为视频设置默认轨道
|
||
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
||
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
||
foreach (var item in streamList)
|
||
{
|
||
if (!string.IsNullOrEmpty(item.Resolution))
|
||
{
|
||
if (aL.Any())
|
||
{
|
||
item.AudioId = aL.First().GroupId;
|
||
}
|
||
if (sL.Any())
|
||
{
|
||
item.SubtitleId = sL.First().GroupId;
|
||
}
|
||
}
|
||
}
|
||
|
||
return streamList;
|
||
}
|
||
|
||
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
|
||
{
|
||
if (streamSpecs.Count == 0) return;
|
||
var (rawText, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
||
ParserConfig.Url = url;
|
||
SetInitUrl();
|
||
var newStreams = await ExtractStreamsAsync(rawText);
|
||
foreach (var streamSpec in streamSpecs)
|
||
{
|
||
//有的网站每次请求MPD返回的码率不一致,导致ToShortString()无法匹配 无法更新playlist
|
||
//故增加通过init url来匹配 (如果有的话)
|
||
var match = newStreams.Where(n => n.ToShortString() == streamSpec.ToShortString());
|
||
if (!match.Any())
|
||
match = newStreams.Where(n => n.Playlist?.MediaInit?.Url == streamSpec.Playlist?.MediaInit?.Url);
|
||
|
||
if (match.Any())
|
||
streamSpec.Playlist!.MediaParts = match.First().Playlist!.MediaParts; //不更新init
|
||
}
|
||
//这里才调用URL预处理器,节省开销
|
||
await ProcessUrlAsync(streamSpecs);
|
||
}
|
||
|
||
private async Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
||
{
|
||
for (int i = 0; i < streamSpecs.Count; i++)
|
||
{
|
||
var playlist = streamSpecs[i].Playlist;
|
||
if (playlist != null)
|
||
{
|
||
if (playlist.MediaInit != null)
|
||
{
|
||
playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
|
||
}
|
||
for (int ii = 0; ii < playlist!.MediaParts.Count; ii++)
|
||
{
|
||
var part = playlist.MediaParts[ii];
|
||
for (int iii = 0; iii < part.MediaSegments.Count; iii++)
|
||
{
|
||
part.MediaSegments[iii].Url = PreProcessUrl(part.MediaSegments[iii].Url);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||
{
|
||
//这里才调用URL预处理器,节省开销
|
||
await ProcessUrlAsync(streamSpecs);
|
||
}
|
||
|
||
public string PreProcessUrl(string url)
|
||
{
|
||
foreach (var p in ParserConfig.UrlProcessors)
|
||
{
|
||
if (p.CanProcess(ExtractorType, url, ParserConfig))
|
||
{
|
||
url = p.Process(url, ParserConfig);
|
||
}
|
||
}
|
||
|
||
return url;
|
||
}
|
||
|
||
public void PreProcessContent()
|
||
{
|
||
foreach (var p in ParserConfig.ContentProcessors)
|
||
{
|
||
if (p.CanProcess(ExtractorType, MpdContent, ParserConfig))
|
||
{
|
||
MpdContent = p.Process(MpdContent, ParserConfig);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|