aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-10-14 20:04:44 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-10-14 20:04:44 -0400
commit52776df0129f73f7d0f87e9c51629241c5c4a7de (patch)
tree27d9e26dacf6a761d72e1f25665686c157ff0073
parentdaacf3f3db3af0938ea08c3ac7ebdea61d2c5ef7 (diff)
produce valid mpeg dash manifest
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs46
-rw-r--r--MediaBrowser.Api/Playback/Hls/MpegDashService.cs113
2 files changed, 136 insertions, 23 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index fa6f88cc4..c188376fe 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -825,6 +825,23 @@ namespace MediaBrowser.Api.Playback
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)
{
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))
{
state.MediaPath = streamInfo.Path;
- state.InputProtocol = MediaProtocol.File;
-
- await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(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);
checkCodecs = true;
}
@@ -869,16 +885,15 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(streamInfo.Path))
{
state.MediaPath = streamInfo.Path;
- state.InputProtocol = MediaProtocol.File;
-
- await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(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);
checkCodecs = true;
}
@@ -991,6 +1006,16 @@ namespace MediaBrowser.Api.Playback
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;
}
@@ -1610,11 +1635,6 @@ namespace MediaBrowser.Api.Playback
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
-
- if (video.IsShortcut)
- {
- state.MediaPath = File.ReadAllText(video.Path);
- }
}
state.RunTimeTicks = mediaSource.RunTimeTicks;
diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
index caddbd9a1..115022015 100644
--- a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
+++ b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Security;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -41,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <value>The segment id.</value>
public string SegmentId { get; set; }
}
-
+
public class MpegDashService : BaseHlsService
{
protected INetworkManager NetworkManager { get; private set; }
@@ -104,8 +105,9 @@ namespace MediaBrowser.Api.Playback.Hls
var duration = "PT0H02M11.00S";
+ builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
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,
state.SegmentLength.ToString(CultureInfo.InvariantCulture));
@@ -116,9 +118,13 @@ namespace MediaBrowser.Api.Playback.Hls
builder.Append("<AdaptationSet segmentAlignment=\"true\">");
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);
@@ -131,10 +137,97 @@ namespace MediaBrowser.Api.Playback.Hls
return builder.ToString();
}
- private string GetRepresentationOpenElement(StreamState state)
+ private string GetRepresentationOpenElement(StreamState state, string language)
{
- return
- "<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 codecs = GetVideoCodecDescriptor(state) + "," + GetAudioCodecDescriptor(state);
+
+ 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)
@@ -147,7 +240,7 @@ namespace MediaBrowser.Api.Playback.Hls
var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
builder.Append("<SegmentList timescale=\"1000\" duration=\"10000\">");
-
+
var queryStringIndex = Request.RawUrl.IndexOf('?');
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
@@ -155,7 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
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;
index++;