重写MPD解析(点播)
This commit is contained in:
parent
a8bf2f5320
commit
37e3e6217a
|
@ -9,14 +9,12 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
public class MediaSegment
|
||||
{
|
||||
public int Index { get; set; }
|
||||
|
||||
public int TargetDuration { get; set; }
|
||||
public double Duration { get; set; }
|
||||
public string? Title { get; set; }
|
||||
|
||||
public long StartRange { get; set; } = 0L;
|
||||
public long StopRange { get => StartRange + ExpectLength - 1; }
|
||||
public long ExpectLength { get; set; } = -1L;
|
||||
public long? StartRange { get; set; }
|
||||
public long? StopRange { get => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null; }
|
||||
public long? ExpectLength { get; set; }
|
||||
|
||||
public EncryptInfo EncryptInfo { get; set; } = new EncryptInfo();
|
||||
|
||||
|
|
|
@ -13,6 +13,12 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
public string Url { get; set; }
|
||||
//是否直播
|
||||
public bool IsLive { get; set; } = false;
|
||||
//直播刷新间隔毫秒(默认15秒)
|
||||
public double RefreshIntervalMs { get; set; } = 15000;
|
||||
//所有分片时长总和
|
||||
public double TotalDuration { get => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); }
|
||||
//所有分片中最长时长
|
||||
public double? TargetDuration { get; set; }
|
||||
//INIT信息
|
||||
public MediaSegment? MediaInit { get; set; }
|
||||
//分片信息
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
|
||||
public string Url { get; set; }
|
||||
|
||||
public Playlist Playlist { get; set; }
|
||||
public Playlist? Playlist { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
@ -75,8 +75,8 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
//计算时长
|
||||
if (Playlist != null)
|
||||
{
|
||||
var total = Playlist.MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration));
|
||||
returnStr += " | " + GlobalUtil.FormatTime((int)total);
|
||||
var total = Playlist.TotalDuration;
|
||||
returnStr += " | ~" + GlobalUtil.FormatTime((int)total);
|
||||
}
|
||||
|
||||
return returnStr;
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.Parser.Constants
|
||||
{
|
||||
internal class DASHTags
|
||||
{
|
||||
public static string TemplateRepresentationID = "$RepresentationID$";
|
||||
public static string TemplateBandwidth = "$Bandwidth$";
|
||||
public static string TemplateNumber = "$Number$";
|
||||
public static string TemplateTime = "$Time$";
|
||||
}
|
||||
}
|
|
@ -57,15 +57,28 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
}
|
||||
}
|
||||
|
||||
TimeSpan updateTs = TimeSpan.FromSeconds(0); //更新时长
|
||||
TimeSpan totalTs = TimeSpan.FromSeconds(0); //总时长
|
||||
|
||||
var type = ((XmlElement)xn).GetAttribute("type"); //static dynamic
|
||||
|
||||
if (type == "static")
|
||||
{
|
||||
var mediaPresentationDuration = ((XmlElement)xn).GetAttribute("mediaPresentationDuration");
|
||||
totalTs = XmlConvert.ToTimeSpan(mediaPresentationDuration);
|
||||
}
|
||||
else if (type == "dynamic")
|
||||
{
|
||||
var minimumUpdatePeriod = ((XmlElement)xn).GetAttribute("minimumUpdatePeriod");
|
||||
updateTs = XmlConvert.ToTimeSpan(minimumUpdatePeriod);
|
||||
}
|
||||
|
||||
|
||||
var ns = ((XmlElement)xn).GetAttribute("xmlns");
|
||||
|
||||
XmlNamespaceManager nsMgr = new XmlNamespaceManager(mpdDoc.NameTable);
|
||||
nsMgr.AddNamespace("ns", ns);
|
||||
|
||||
TimeSpan ts = XmlConvert.ToTimeSpan(mediaPresentationDuration); //时长
|
||||
|
||||
//读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
||||
var baseNode = xn.SelectSingleNode("ns:BaseURL", nsMgr);
|
||||
|
@ -90,7 +103,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
foreach (XmlElement period in xn.SelectNodes("ns:Period", nsMgr))
|
||||
{
|
||||
periodIndex++;
|
||||
var periodDuration = string.IsNullOrEmpty(period.GetAttribute("duration")) ? XmlConvert.ToTimeSpan(mediaPresentationDuration) : XmlConvert.ToTimeSpan(period.GetAttribute("duration"));
|
||||
var periodDuration = string.IsNullOrEmpty(period.GetAttribute("duration")) ? totalTs : XmlConvert.ToTimeSpan(period.GetAttribute("duration"));
|
||||
var periodMsInfo = ExtractMultisegmentInfo(period, nsMgr, new JsonObject()
|
||||
{
|
||||
["StartNumber"] = 1,
|
||||
|
@ -410,7 +423,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
new JsonObject()
|
||||
{
|
||||
["url"] = baseUrl,
|
||||
["duration"] = ts.TotalSeconds
|
||||
["duration"] = totalTs.TotalSeconds
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -463,6 +476,14 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
|
||||
//组装分片
|
||||
Playlist playlist = new();
|
||||
playlist.IsLive = type == "static" ? false : true;
|
||||
|
||||
//直播刷新间隔
|
||||
if (playlist.IsLive)
|
||||
{
|
||||
playlist.RefreshIntervalMs = updateTs.TotalMilliseconds;
|
||||
}
|
||||
|
||||
List<MediaSegment> segments = new();
|
||||
//Initial URL
|
||||
if (item.ContainsKey("InitializationUrl"))
|
||||
|
@ -725,7 +746,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
/// <summary>
|
||||
/// 预处理URL
|
||||
/// </summary>
|
||||
private string PreProcessUrl(string url)
|
||||
public string PreProcessUrl(string url)
|
||||
{
|
||||
foreach (var p in ParserConfig.UrlProcessors)
|
||||
{
|
||||
|
@ -738,7 +759,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
return url;
|
||||
}
|
||||
|
||||
private void PreProcessContent()
|
||||
public void PreProcessContent()
|
||||
{
|
||||
foreach (var p in ParserConfig.ContentProcessors)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
using N_m3u8DL_RE.Common.Entity;
|
||||
using N_m3u8DL_RE.Common.Enum;
|
||||
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
|
||||
{
|
||||
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;
|
||||
this.MpdUrl = parserConfig.Url ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(parserConfig.BaseUrl))
|
||||
this.BaseUrl = parserConfig.BaseUrl;
|
||||
}
|
||||
|
||||
private string ExtendBaseUrl(XElement element, string oriBaseUrl)
|
||||
{
|
||||
var target = element.Elements().Where(e => e.Name.LocalName == "BaseURL");
|
||||
if (target.Any())
|
||||
{
|
||||
oriBaseUrl = ParserUtil.CombineURL(oriBaseUrl, target.First().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;
|
||||
//MPD更新间隔
|
||||
var minimumUpdatePeriod = mpdElement.Attribute("minimumUpdatePeriod")?.Value;
|
||||
//分片从该时间起可用
|
||||
var availabilityStartTime = mpdElement.Attribute("availabilityStartTime")?.Value;
|
||||
//MPD发布时间
|
||||
var publishTime = mpdElement.Attribute("publishTime")?.Value;
|
||||
//MPD总时长
|
||||
var mediaPresentationDuration = mpdElement.Attribute("mediaPresentationDuration")?.Value;
|
||||
|
||||
//读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
||||
var baseUrlElements = mpdElement.Elements().Where(e => e.Name.LocalName == "BaseURL");
|
||||
if (baseUrlElements.Any())
|
||||
{
|
||||
var baseUrl = baseUrlElements.First().Value;
|
||||
if (baseUrl.Contains("kkbox.com.tw/")) baseUrl = baseUrl.Replace("//https:%2F%2F", "//");
|
||||
this.BaseUrl = ParserUtil.CombineURL(this.MpdUrl, baseUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.BaseUrl = this.MpdUrl;
|
||||
}
|
||||
|
||||
//全部Period
|
||||
var periods = mpdElement.Elements().Where(e => e.Name.LocalName == "Period");
|
||||
foreach (var period in periods)
|
||||
{
|
||||
//本Period时长
|
||||
var periodDuration = period.Attribute("duration")?.Value;
|
||||
|
||||
//最终分片会使用的baseurl
|
||||
var segBaseUrl = this.BaseUrl;
|
||||
|
||||
//处理baseurl嵌套
|
||||
segBaseUrl = ExtendBaseUrl(period, segBaseUrl);
|
||||
|
||||
//本Period中的全部AdaptationSet
|
||||
var adaptationSets = period.Elements().Where(e => e.Name.LocalName == "AdaptationSet");
|
||||
foreach (var adaptationSet in adaptationSets)
|
||||
{
|
||||
//处理baseurl嵌套
|
||||
segBaseUrl = ExtendBaseUrl(adaptationSet, segBaseUrl);
|
||||
|
||||
var mimeType = 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("mimeType")?.Value ?? "";
|
||||
}
|
||||
var bandwidth = representation.Attribute("bandwidth");
|
||||
StreamSpec streamSpec = new();
|
||||
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;
|
||||
streamSpec.Language = representation.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
|
||||
};
|
||||
streamSpec.Playlist.IsLive = isLive;
|
||||
//设置刷新间隔
|
||||
if (minimumUpdatePeriod != null)
|
||||
{
|
||||
streamSpec.Playlist.RefreshIntervalMs = XmlConvert.ToTimeSpan(minimumUpdatePeriod).TotalMilliseconds;
|
||||
}
|
||||
|
||||
//读取声道数量
|
||||
var audioChannelConfiguration = adaptationSet.Elements().Where(e => e.Name.LocalName == "AudioChannelConfiguration");
|
||||
if (audioChannelConfiguration.Any())
|
||||
{
|
||||
streamSpec.Channels = audioChannelConfiguration.First().Attribute("value")?.Value;
|
||||
}
|
||||
|
||||
|
||||
//第一种形式 SegmentBase
|
||||
var segmentBaseElements = representation.Elements().Where(e => e.Name.LocalName == "SegmentBase");
|
||||
if (segmentBaseElements.Any())
|
||||
{
|
||||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add
|
||||
(
|
||||
new MediaSegment()
|
||||
{
|
||||
Index = 0,
|
||||
Url = PreProcessUrl(segBaseUrl),
|
||||
Duration = XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//第二种形式 SegmentList.SegmentList
|
||||
var segmentListElements = representation.Elements().Where(e => e.Name.LocalName == "SegmentList");
|
||||
if (segmentListElements.Any())
|
||||
{
|
||||
var segmentList = segmentListElements.First();
|
||||
//处理init url
|
||||
var initialization = segmentList.Elements().First(e => e.Name.LocalName == "Initialization");
|
||||
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value);
|
||||
var initRange = initialization.Attribute("range")?.Value;
|
||||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||||
streamSpec.Playlist.MediaInit.Url = PreProcessUrl(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");
|
||||
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("range")?.Value;
|
||||
MediaSegment mediaSegment = new();
|
||||
mediaSegment.Url = PreProcessUrl(mediaUrl);
|
||||
if (mediaRange != null)
|
||||
{
|
||||
var (start, expect) = ParserUtil.ParseRange(initRange);
|
||||
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 ?? "1000";
|
||||
var durationStr = segmentTemplate.Attribute("duration")?.Value ?? segmentTemplateOuter.Attribute("duration")?.Value;
|
||||
var startNumberStr = segmentTemplate.Attribute("startNumber")?.Value ?? segmentTemplateOuter.Attribute("startNumber")?.Value ?? "0";
|
||||
//处理init url
|
||||
var initialization = segmentTemplate.Attribute("initialization")?.Value ?? segmentTemplateOuter.Attribute("initialization")?.Value;
|
||||
var initUrl = ParserUtil.ReplaceVars(ParserUtil.CombineURL(segBaseUrl, initialization), varDic);
|
||||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||||
streamSpec.Playlist.MediaInit.Url = PreProcessUrl(initUrl);
|
||||
//处理分片
|
||||
var media = segmentTemplate.Attribute("media")?.Value ?? segmentTemplateOuter.Attribute("media")?.Value;
|
||||
var segmentTimelineElements = segmentTemplate.Elements().Where(e => e.Name.LocalName == "SegmentTimeline");
|
||||
if (segmentTimelineElements.Any())
|
||||
{
|
||||
//使用了SegmentTimeline 结果精确
|
||||
var segmentTimeline = segmentTimelineElements.First();
|
||||
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 = PreProcessUrl(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 = PreProcessUrl(_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.ToInt32(startNumberStr);
|
||||
var duration = Convert.ToInt32(durationStr);
|
||||
var totalNumber = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / Convert.ToInt32(durationStr));
|
||||
for (int 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 = PreProcessUrl(mediaUrl);
|
||||
mediaSegment.Index = segIndex;
|
||||
mediaSegment.Duration = duration / (double)timescale;
|
||||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//判断加密情况
|
||||
if (adaptationSet.Elements().Any(e => e.Name.LocalName == "ContentProtection"))
|
||||
{
|
||||
if (streamSpec.Playlist.MediaInit != null)
|
||||
{
|
||||
streamSpec.Playlist.MediaInit.EncryptInfo.Method = EncryptMethod.UNKNOWN;
|
||||
}
|
||||
foreach (var item in streamSpec.Playlist.MediaParts[0].MediaSegments)
|
||||
{
|
||||
item.EncryptInfo.Method = EncryptMethod.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
//处理同一ID分散在不同Period的情况 这种情况作为新的part出现
|
||||
var _index = streamList.FindIndex(_f => _f.GroupId == streamSpec.GroupId && _f.Resolution == streamSpec.Resolution && _f.MediaType == streamSpec.MediaType);
|
||||
if (_index > -1)
|
||||
{
|
||||
var startIndex = streamList[_index].Playlist?.MediaParts.Last().MediaSegments.Last().Index + 1;
|
||||
foreach (var item in streamSpec.Playlist.MediaParts[0].MediaSegments)
|
||||
{
|
||||
item.Index = item.Index + startIndex.Value;
|
||||
}
|
||||
streamList[_index].Playlist?.MediaParts.Add(new MediaPart()
|
||||
{
|
||||
MediaSegments = streamSpec.Playlist.MediaParts[0].MediaSegments
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
streamList.Add(streamSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//为视频设置默认轨道
|
||||
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 FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
/// <summary>
|
||||
/// 预处理m3u8内容
|
||||
/// </summary>
|
||||
private void PreProcessContent()
|
||||
public void PreProcessContent()
|
||||
{
|
||||
M3u8Content = M3u8Content.Trim();
|
||||
if (!M3u8Content.StartsWith(HLSTags.ext_m3u))
|
||||
|
@ -64,7 +64,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
/// <summary>
|
||||
/// 预处理URL
|
||||
/// </summary>
|
||||
private string PreProcessUrl(string url)
|
||||
public string PreProcessUrl(string url)
|
||||
{
|
||||
foreach (var p in ParserConfig.UrlProcessors)
|
||||
{
|
||||
|
@ -262,7 +262,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
//解析定义的分段长度
|
||||
else if (line.StartsWith(HLSTags.ext_x_targetduration))
|
||||
{
|
||||
segment.Duration = Convert.ToDouble(ParserUtil.GetAttribute(line));
|
||||
playlist.TargetDuration = Convert.ToDouble(ParserUtil.GetAttribute(line));
|
||||
}
|
||||
//解析起始编号
|
||||
else if (line.StartsWith(HLSTags.ext_x_media_sequence))
|
||||
|
@ -439,6 +439,13 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
playlist.MediaParts = mediaParts;
|
||||
playlist.IsLive = !isEndlist;
|
||||
|
||||
//直播刷新间隔
|
||||
if (playlist.IsLive)
|
||||
{
|
||||
//由于播放器默认从最后3个分片开始播放 此处设置刷新间隔为TargetDuration的2倍
|
||||
playlist.RefreshIntervalMs = (int)((playlist.TargetDuration ?? 5) * 2 * 1000);
|
||||
}
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,5 +18,9 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
Task<List<StreamSpec>> ExtractStreamsAsync(string rawText);
|
||||
|
||||
Task FetchPlayListAsync(List<StreamSpec> streamSpecs);
|
||||
|
||||
string PreProcessUrl(string url);
|
||||
|
||||
void PreProcessContent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,8 @@ namespace N_m3u8DL_RE.Parser
|
|||
else if (rawText.Contains("<MPD "))
|
||||
{
|
||||
Logger.InfoMarkUp(ResString.matchDASH);
|
||||
extractor = new DASHExtractor(parserConfig);
|
||||
//extractor = new DASHExtractor(parserConfig);
|
||||
extractor = new DASHExtractor2(parserConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using N_m3u8DL_RE.Parser.Constants;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
@ -57,6 +58,43 @@ namespace N_m3u8DL_RE.Parser.Util
|
|||
return (0, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从100-300这种字符串中获取StartRange, ExpectLength信息
|
||||
/// </summary>
|
||||
/// <param name="range"></param>
|
||||
/// <returns>StartRange, ExpectLength</returns>
|
||||
public static (long, long) ParseRange(string range)
|
||||
{
|
||||
var start = Convert.ToInt64(range.Split('-')[0]);
|
||||
var end = Convert.ToInt64(range.Split('-')[1]);
|
||||
return (start, end - start + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MPD SegmentTemplate替换
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="keyValuePairs"></param>
|
||||
/// <returns></returns>
|
||||
public static string ReplaceVars(string text, Dictionary<string, object?> keyValuePairs)
|
||||
{
|
||||
foreach (var item in keyValuePairs)
|
||||
if (text.Contains(item.Key))
|
||||
text = text.Replace(item.Key, item.Value.ToString());
|
||||
|
||||
//处理特殊形式数字 如 $Number%05d$
|
||||
var regex = new Regex("\\$Number%([^$]+)d\\$");
|
||||
if (regex.IsMatch(text) && keyValuePairs.ContainsKey(DASHTags.TemplateNumber))
|
||||
{
|
||||
foreach (Match m in regex.Matches(text))
|
||||
{
|
||||
text = text.Replace(m.Value, keyValuePairs[DASHTags.TemplateNumber]?.ToString()?.PadLeft(Convert.ToInt32(m.Groups[1].Value), '0'));
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拼接Baseurl和RelativeUrl
|
||||
/// </summary>
|
||||
|
|
|
@ -16,4 +16,8 @@
|
|||
<ProjectReference Include="..\N_m3u8DL-RE.Parser\N_m3u8DL-RE.Parser.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Downloader\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue