diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 503f9927ce..14d0f13fb7 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -79,7 +79,8 @@
-
+
+
diff --git a/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs b/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs
new file mode 100644
index 0000000000..20ea7893c5
--- /dev/null
+++ b/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Globalization;
+using System.Security;
+using System.Text;
+
+namespace MediaBrowser.Api.Playback.Dash
+{
+ public class ManifestBuilder
+ {
+ protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ public string GetManifestText(StreamState state, string playlistUrl)
+ {
+ var builder = new StringBuilder();
+
+ var time = TimeSpan.FromTicks(state.RunTimeTicks.Value);
+
+ var duration = "PT" + time.Hours.ToString("00", UsCulture) + "H" + time.Minutes.ToString("00", UsCulture) + "M" + time.Seconds.ToString("00", UsCulture) + ".00S";
+
+ builder.Append("");
+
+ builder.AppendFormat(
+ "",
+ duration);
+
+ builder.Append("");
+ builder.Append("");
+
+ builder.Append("");
+ builder.Append(GetVideoAdaptationSet(state, playlistUrl));
+ builder.Append(GetAudioAdaptationSet(state, playlistUrl));
+ builder.Append("");
+
+ builder.Append("");
+
+ return builder.ToString();
+ }
+
+ private string GetVideoAdaptationSet(StreamState state, string playlistUrl)
+ {
+ var builder = new StringBuilder();
+
+ builder.Append("");
+ builder.Append(GetVideoRepresentationOpenElement(state));
+
+ AppendSegmentList(state, builder, "video", playlistUrl);
+
+ builder.Append("");
+ builder.Append("");
+
+ return builder.ToString();
+ }
+
+ private string GetAudioAdaptationSet(StreamState state, string playlistUrl)
+ {
+ var builder = new StringBuilder();
+
+ builder.Append("");
+ builder.Append(GetAudioRepresentationOpenElement(state));
+
+ builder.Append("");
+
+ AppendSegmentList(state, builder, "audio", playlistUrl);
+
+ builder.Append("");
+ builder.Append("");
+
+ return builder.ToString();
+ }
+
+ private string GetVideoRepresentationOpenElement(StreamState state)
+ {
+ var codecs = GetVideoCodecDescriptor(state);
+
+ var mime = "video/mp4";
+
+ var xml = "";
+
+ return xml;
+ }
+
+ private string GetAudioRepresentationOpenElement(StreamState state)
+ {
+ var codecs = GetAudioCodecDescriptor(state);
+
+ var mime = "audio/mp4";
+
+ var xml = "";
+
+ return xml;
+ }
+
+ private string GetVideoCodecDescriptor(StreamState state)
+ {
+ // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
+ // http://www.chipwreck.de/blog/2010/02/25/html-5-video-tag-and-attributes/
+
+ 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.42E01E";
+ }
+
+ 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";
+ }
+
+ private void AppendSegmentList(StreamState state, StringBuilder builder, string type, string playlistUrl)
+ {
+ var extension = ".m4s";
+
+ var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
+
+ var queryStringIndex = playlistUrl.IndexOf('?');
+ var queryString = queryStringIndex == -1 ? string.Empty : playlistUrl.Substring(queryStringIndex);
+
+ var index = 0;
+ var duration = 1000000 * state.SegmentLength;
+ builder.AppendFormat("", duration.ToString(CultureInfo.InvariantCulture));
+
+ while (seconds > 0)
+ {
+ var segmentUrl = string.Format("dash/{3}/{0}{1}{2}",
+ index.ToString(UsCulture),
+ extension,
+ SecurityElement.Escape(queryString),
+ type);
+
+ if (index == 0)
+ {
+ builder.AppendFormat("", segmentUrl);
+ }
+ else
+ {
+ builder.AppendFormat("", segmentUrl);
+ }
+
+ seconds -= state.SegmentLength;
+ index++;
+ }
+ builder.Append("");
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs
similarity index 70%
rename from MediaBrowser.Api/Playback/Hls/MpegDashService.cs
rename to MediaBrowser.Api/Playback/Dash/MpegDashService.cs
index 3625b3cdfe..4ba32575c3 100644
--- a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
+++ b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Api.Playback.Hls;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
@@ -15,13 +16,11 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Security;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
-namespace MediaBrowser.Api.Playback.Hls
+namespace MediaBrowser.Api.Playback.Dash
{
///
/// Options is needed for chromecast. Threw Head in there since it's related
@@ -88,16 +87,6 @@ namespace MediaBrowser.Api.Playback.Hls
private async Task");
- builder.Append("");
-
- return builder.ToString();
- }
-
- private string GetAudioAdaptationSet(StreamState state)
- {
- var builder = new StringBuilder();
-
- builder.Append("");
- builder.Append(GetAudioRepresentationOpenElement(state));
-
- builder.Append("");
-
- AppendSegmentList(state, builder, "audio");
-
- builder.Append("");
- builder.Append("");
-
- return builder.ToString();
- }
-
- private string GetVideoRepresentationOpenElement(StreamState state)
- {
- var codecs = GetVideoCodecDescriptor(state);
-
- var mime = "video/mp4";
-
- var xml = "";
-
- return xml;
- }
-
- private string GetAudioRepresentationOpenElement(StreamState state)
- {
- var codecs = GetAudioCodecDescriptor(state);
-
- var mime = "audio/mp4";
-
- var xml = "";
-
- return xml;
- }
-
- private string GetVideoCodecDescriptor(StreamState state)
- {
- // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
- // http://www.chipwreck.de/blog/2010/02/25/html-5-video-tag-and-attributes/
-
- 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.42E01E";
- }
-
- 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)
{
return GetDynamicSegment(request, request.SegmentId, request.SegmentType).Result;
}
- private void AppendSegmentList(StreamState state, StringBuilder builder, string type)
- {
- var extension = GetSegmentFileExtension(state);
-
- var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
-
- var queryStringIndex = Request.RawUrl.IndexOf('?');
- var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
-
- var index = 0;
- var duration = 1000000 * state.SegmentLength;
- builder.AppendFormat("", duration.ToString(CultureInfo.InvariantCulture));
-
- while (seconds > 0)
- {
- var segmentUrl = string.Format("dash/{3}/{0}{1}{2}",
- index.ToString(UsCulture),
- extension,
- SecurityElement.Escape(queryString),
- type);
-
- if (index == 0)
- {
- builder.AppendFormat("", segmentUrl);
- }
- else
- {
- builder.AppendFormat("", segmentUrl);
- }
-
- seconds -= state.SegmentLength;
- index++;
- }
- builder.Append("");
- }
-
private async Task