mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-05 21:37:10 -04:00
produce valid mpeg dash manifest
This commit is contained in:
parent
daacf3f3db
commit
52776df012
@ -825,6 +825,23 @@ namespace MediaBrowser.Api.Playback
|
|||||||
return MediaEncoder.GetInputArgument(inputPath, protocol);
|
return MediaEncoder.GetInputArgument(inputPath, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MediaProtocol GetProtocol(string path)
|
||||||
|
{
|
||||||
|
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return MediaProtocol.Http;
|
||||||
|
}
|
||||||
|
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return MediaProtocol.Rtsp;
|
||||||
|
}
|
||||||
|
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return MediaProtocol.Rtmp;
|
||||||
|
}
|
||||||
|
return MediaProtocol.File;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
|
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
|
||||||
{
|
{
|
||||||
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
|
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
|
||||||
@ -845,16 +862,15 @@ namespace MediaBrowser.Api.Playback
|
|||||||
if (!string.IsNullOrEmpty(streamInfo.Path))
|
if (!string.IsNullOrEmpty(streamInfo.Path))
|
||||||
{
|
{
|
||||||
state.MediaPath = streamInfo.Path;
|
state.MediaPath = streamInfo.Path;
|
||||||
state.InputProtocol = MediaProtocol.File;
|
|
||||||
|
|
||||||
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
||||||
{
|
{
|
||||||
state.MediaPath = streamInfo.Url;
|
state.MediaPath = streamInfo.Url;
|
||||||
state.InputProtocol = MediaProtocol.Http;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
state.InputProtocol = GetProtocol(state.MediaPath);
|
||||||
AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
|
AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
|
||||||
checkCodecs = true;
|
checkCodecs = true;
|
||||||
}
|
}
|
||||||
@ -869,16 +885,15 @@ namespace MediaBrowser.Api.Playback
|
|||||||
if (!string.IsNullOrEmpty(streamInfo.Path))
|
if (!string.IsNullOrEmpty(streamInfo.Path))
|
||||||
{
|
{
|
||||||
state.MediaPath = streamInfo.Path;
|
state.MediaPath = streamInfo.Path;
|
||||||
state.InputProtocol = MediaProtocol.File;
|
|
||||||
|
|
||||||
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
||||||
{
|
{
|
||||||
state.MediaPath = streamInfo.Url;
|
state.MediaPath = streamInfo.Url;
|
||||||
state.InputProtocol = MediaProtocol.Http;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
state.InputProtocol = GetProtocol(state.MediaPath);
|
||||||
AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
|
AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
|
||||||
checkCodecs = true;
|
checkCodecs = true;
|
||||||
}
|
}
|
||||||
@ -991,6 +1006,16 @@ namespace MediaBrowser.Api.Playback
|
|||||||
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.IsInputVideo && transcodingJob.Type == Api.TranscodingJobType.Progressive)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (state.ReadInputAtNativeFramerate)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return transcodingJob;
|
return transcodingJob;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1610,11 +1635,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video.IsShortcut)
|
|
||||||
{
|
|
||||||
state.MediaPath = File.ReadAllText(video.Path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.IO;
|
using System.Security;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@ -104,8 +105,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
var duration = "PT0H02M11.00S";
|
var duration = "PT0H02M11.00S";
|
||||||
|
|
||||||
|
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||||
builder.AppendFormat(
|
builder.AppendFormat(
|
||||||
"<MPD type=\"static\" minBufferTime=\"PT2S\" mediaPresentationDuration=\"{0}\" profiles=\"urn:mpeg:dash:profile:mp2t-simple:2011\" xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" maxSegmentDuration=\"PT{1}S\">",
|
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" minBufferTime=\"PT2.00S\" mediaPresentationDuration=\"{0}\" maxSegmentDuration=\"PT{1}S\" type=\"static\" profiles=\"urn:mpeg:dash:profile:mp2t-simple:2011\">",
|
||||||
duration,
|
duration,
|
||||||
state.SegmentLength.ToString(CultureInfo.InvariantCulture));
|
state.SegmentLength.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
@ -116,9 +118,13 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
builder.Append("<AdaptationSet segmentAlignment=\"true\">");
|
builder.Append("<AdaptationSet segmentAlignment=\"true\">");
|
||||||
|
|
||||||
builder.Append("<ContentComponent id=\"1\" contentType=\"video\"/>");
|
builder.Append("<ContentComponent id=\"1\" contentType=\"video\"/>");
|
||||||
builder.Append("<ContentComponent id=\"2\" contentType=\"audio\" lang=\"eng\"/>");
|
|
||||||
|
|
||||||
builder.Append(GetRepresentationOpenElement(state));
|
var lang = state.AudioStream != null ? state.AudioStream.Language : null;
|
||||||
|
if (string.IsNullOrWhiteSpace(lang)) lang = "und";
|
||||||
|
|
||||||
|
builder.AppendFormat("<ContentComponent id=\"2\" contentType=\"audio\" lang=\"{0}\"/>", lang);
|
||||||
|
|
||||||
|
builder.Append(GetRepresentationOpenElement(state, lang));
|
||||||
|
|
||||||
AppendSegmentList(state, builder);
|
AppendSegmentList(state, builder);
|
||||||
|
|
||||||
@ -131,10 +137,97 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetRepresentationOpenElement(StreamState state)
|
private string GetRepresentationOpenElement(StreamState state, string language)
|
||||||
{
|
{
|
||||||
return
|
var codecs = GetVideoCodecDescriptor(state) + "," + GetAudioCodecDescriptor(state);
|
||||||
"<Representation id=\"1\" mimeType=\"video/mp2t\" codecs=\"avc1.640028,mp4a.40.02\" width=\"1280\" height=\"1024\" sampleRate=\"44100\" numChannels=\"2\" lang=\"und\" startWithSAP=\"1\" bandwidth=\"317599\">";
|
|
||||||
|
var xml = "<Representation id=\"1\" mimeType=\"video/mp2t\" startWithSAP=\"1\" codecs=\"" + codecs + "\"";
|
||||||
|
|
||||||
|
if (state.OutputWidth.HasValue)
|
||||||
|
{
|
||||||
|
xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
|
||||||
|
}
|
||||||
|
if (state.OutputHeight.HasValue)
|
||||||
|
{
|
||||||
|
xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
|
||||||
|
}
|
||||||
|
if (state.OutputAudioSampleRate.HasValue)
|
||||||
|
{
|
||||||
|
xml += " sampleRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.TotalOutputBitrate.HasValue)
|
||||||
|
{
|
||||||
|
xml += " bandwidth=\"" + state.TotalOutputBitrate.Value.ToString(UsCulture) + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
xml += ">";
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetVideoCodecDescriptor(StreamState state)
|
||||||
|
{
|
||||||
|
// https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
|
||||||
|
|
||||||
|
var level = state.TargetVideoLevel ?? 0;
|
||||||
|
var profile = state.TargetVideoProfile ?? string.Empty;
|
||||||
|
|
||||||
|
if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
if (level >= 4.1)
|
||||||
|
{
|
||||||
|
return "avc1.640028";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level >= 4)
|
||||||
|
{
|
||||||
|
return "avc1.640028";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "avc1.64001f";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
if (level >= 4)
|
||||||
|
{
|
||||||
|
return "avc1.4d0028";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level >= 3.1)
|
||||||
|
{
|
||||||
|
return "avc1.4d001f";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "avc1.4d001e";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level >= 3.1)
|
||||||
|
{
|
||||||
|
return "avc1.42001f";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "avc1.42001e";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetAudioCodecDescriptor(StreamState state)
|
||||||
|
{
|
||||||
|
// https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
|
||||||
|
|
||||||
|
if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "mp4a.40.34";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAC 5ch
|
||||||
|
if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
|
||||||
|
{
|
||||||
|
return "mp4a.40.5";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAC 2ch
|
||||||
|
return "mp4a.40.2";
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetDashSegment request)
|
public object Get(GetDashSegment request)
|
||||||
@ -155,7 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
while (seconds > 0)
|
while (seconds > 0)
|
||||||
{
|
{
|
||||||
builder.AppendFormat("<SegmentURL media=\"{0}.ts{1}\"/>", index.ToString(UsCulture), queryString);
|
builder.AppendFormat("<SegmentURL media=\"{0}.ts{1}\"/>", index.ToString(UsCulture), SecurityElement.Escape(queryString));
|
||||||
|
|
||||||
seconds -= state.SegmentLength;
|
seconds -= state.SegmentLength;
|
||||||
index++;
|
index++;
|
||||||
|
Loading…
Reference in New Issue
Block a user