aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2015-03-15 23:41:12 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2015-03-15 23:41:12 -0400
commite8161cf8acfb1ec6925cc670808e9d66c279daec (patch)
tree8d7ce1061738039d6e0fe2c4d94b37ac78a1b985
parenta7dfd1f6befce473b969029a98a49e803b48b54b (diff)
update dynamic images
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj3
-rw-r--r--MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs220
-rw-r--r--MediaBrowser.Api/Playback/Dash/MpegDashService.cs (renamed from MediaBrowser.Api/Playback/Hls/MpegDashService.cs)260
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs10
6 files changed, 252 insertions, 248 deletions
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 503f9927c..14d0f13fb 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -79,7 +79,8 @@
<Compile Include="FilterService.cs" />
<Compile Include="IHasDtoOptions.cs" />
<Compile Include="Library\ChapterService.cs" />
- <Compile Include="Playback\Hls\MpegDashService.cs" />
+ <Compile Include="Playback\Dash\ManifestBuilder.cs" />
+ <Compile Include="Playback\Dash\MpegDashService.cs" />
<Compile Include="Playback\MediaInfoService.cs" />
<Compile Include="Playback\TranscodingThrottler.cs" />
<Compile Include="PlaylistService.cs" />
diff --git a/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs b/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs
new file mode 100644
index 000000000..20ea7893c
--- /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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+
+ builder.AppendFormat(
+ "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"{0}\" minBufferTime=\"PT5.0S\">",
+ duration);
+
+ builder.Append("<ProgramInformation>");
+ builder.Append("</ProgramInformation>");
+
+ builder.Append("<Period start=\"PT0S\">");
+ builder.Append(GetVideoAdaptationSet(state, playlistUrl));
+ builder.Append(GetAudioAdaptationSet(state, playlistUrl));
+ builder.Append("</Period>");
+
+ builder.Append("</MPD>");
+
+ return builder.ToString();
+ }
+
+ private string GetVideoAdaptationSet(StreamState state, string playlistUrl)
+ {
+ var builder = new StringBuilder();
+
+ builder.Append("<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
+ builder.Append(GetVideoRepresentationOpenElement(state));
+
+ AppendSegmentList(state, builder, "video", playlistUrl);
+
+ builder.Append("</Representation>");
+ builder.Append("</AdaptationSet>");
+
+ return builder.ToString();
+ }
+
+ private string GetAudioAdaptationSet(StreamState state, string playlistUrl)
+ {
+ var builder = new StringBuilder();
+
+ builder.Append("<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
+ builder.Append(GetAudioRepresentationOpenElement(state));
+
+ builder.Append("<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"6\" />");
+
+ AppendSegmentList(state, builder, "audio", playlistUrl);
+
+ builder.Append("</Representation>");
+ builder.Append("</AdaptationSet>");
+
+ return builder.ToString();
+ }
+
+ private string GetVideoRepresentationOpenElement(StreamState state)
+ {
+ var codecs = GetVideoCodecDescriptor(state);
+
+ var mime = "video/mp4";
+
+ var xml = "<Representation id=\"0\" mimeType=\"" + mime + "\" 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.OutputVideoBitrate.HasValue)
+ {
+ xml += " bandwidth=\"" + state.OutputVideoBitrate.Value.ToString(UsCulture) + "\"";
+ }
+
+ xml += ">";
+
+ return xml;
+ }
+
+ private string GetAudioRepresentationOpenElement(StreamState state)
+ {
+ var codecs = GetAudioCodecDescriptor(state);
+
+ var mime = "audio/mp4";
+
+ var xml = "<Representation id=\"1\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
+
+ if (state.OutputAudioSampleRate.HasValue)
+ {
+ xml += " audioSamplingRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
+ }
+ if (state.OutputAudioBitrate.HasValue)
+ {
+ xml += " bandwidth=\"" + state.OutputAudioBitrate.Value.ToString(UsCulture) + "\"";
+ }
+
+ 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("<SegmentList timescale=\"1000000\" duration=\"{0}\" startNumber=\"1\">", 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("<Initialization sourceURL=\"{0}\"/>", segmentUrl);
+ }
+ else
+ {
+ builder.AppendFormat("<SegmentURL media=\"{0}\"/>", segmentUrl);
+ }
+
+ seconds -= state.SegmentLength;
+ index++;
+ }
+ builder.Append("</SegmentList>");
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs
index 3625b3cdf..4ba32575c 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
{
/// <summary>
/// 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<object> GetAsync(GetMasterManifest request, string method)
{
- if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- throw new ArgumentException("Audio codec copy is not allowed here.");
- }
-
- if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- throw new ArgumentException("Video codec copy is not allowed here.");
- }
-
if (string.IsNullOrEmpty(request.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required");
@@ -109,225 +98,17 @@ namespace MediaBrowser.Api.Playback.Hls
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
{
- playlistText = GetManifestText(state);
+ playlistText = new ManifestBuilder().GetManifestText(state, Request.RawUrl);
}
return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
}
- private string GetManifestText(StreamState state)
- {
- 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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-
- builder.AppendFormat(
- "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"{0}\" minBufferTime=\"PT5.0S\">",
- duration);
-
- builder.Append("<ProgramInformation>");
- builder.Append("</ProgramInformation>");
-
- builder.Append("<Period start=\"PT0S\">");
- builder.Append(GetVideoAdaptationSet(state));
- builder.Append(GetAudioAdaptationSet(state));
- builder.Append("</Period>");
-
- builder.Append("</MPD>");
-
- return builder.ToString();
- }
-
- private string GetVideoAdaptationSet(StreamState state)
- {
- var builder = new StringBuilder();
-
- builder.Append("<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
- builder.Append(GetVideoRepresentationOpenElement(state));
-
- AppendSegmentList(state, builder, "video");
-
- builder.Append("</Representation>");
- builder.Append("</AdaptationSet>");
-
- return builder.ToString();
- }
-
- private string GetAudioAdaptationSet(StreamState state)
- {
- var builder = new StringBuilder();
-
- builder.Append("<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
- builder.Append(GetAudioRepresentationOpenElement(state));
-
- builder.Append("<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"6\" />");
-
- AppendSegmentList(state, builder, "audio");
-
- builder.Append("</Representation>");
- builder.Append("</AdaptationSet>");
-
- return builder.ToString();
- }
-
- private string GetVideoRepresentationOpenElement(StreamState state)
- {
- var codecs = GetVideoCodecDescriptor(state);
-
- var mime = "video/mp4";
-
- var xml = "<Representation id=\"0\" mimeType=\"" + mime + "\" 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.OutputVideoBitrate.HasValue)
- {
- xml += " bandwidth=\"" + state.OutputVideoBitrate.Value.ToString(UsCulture) + "\"";
- }
-
- xml += ">";
-
- return xml;
- }
-
- private string GetAudioRepresentationOpenElement(StreamState state)
- {
- var codecs = GetAudioCodecDescriptor(state);
-
- var mime = "audio/mp4";
-
- var xml = "<Representation id=\"1\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- xml += " audioSamplingRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
- }
- if (state.OutputAudioBitrate.HasValue)
- {
- xml += " bandwidth=\"" + state.OutputAudioBitrate.Value.ToString(UsCulture) + "\"";
- }
-
- 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("<SegmentList timescale=\"1000000\" duration=\"{0}\" startNumber=\"1\">", 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("<Initialization sourceURL=\"{0}\"/>", segmentUrl);
- }
- else
- {
- builder.AppendFormat("<SegmentURL media=\"{0}\"/>", segmentUrl);
- }
-
- seconds -= state.SegmentLength;
- index++;
- }
- builder.Append("</SegmentList>");
- }
-
private async Task<object> GetDynamicSegment(VideoStreamRequest request, string segmentId, string segmentType)
{
if ((request.StartTimeTicks ?? 0) > 0)
@@ -616,24 +397,6 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- protected override int GetStartNumber(StreamState state)
- {
- return GetStartNumber(state.VideoRequest);
- }
-
- private int GetStartNumber(VideoStreamRequest request)
- {
- var segmentId = "0";
-
- var segmentRequest = request as GetDynamicHlsVideoSegment;
- if (segmentRequest != null)
- {
- segmentId = segmentRequest.SegmentId;
- }
-
- return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
- }
-
private string GetSegmentPath(string playlist, string segmentType, string segmentExtension, int index)
{
var folder = Path.GetDirectoryName(playlist);
@@ -751,6 +514,21 @@ namespace MediaBrowser.Api.Playback.Hls
return args;
}
+ //private string GetCurrentTranscodingIndex(string outputPath)
+ //{
+
+ //}
+
+ //private string GetNextTranscodingIndex(string outputPath)
+ //{
+
+ //}
+
+ //private string GetActualPlaylistPath(string outputPath, string transcodingIndex)
+ //{
+
+ //}
+
/// <summary>
/// Gets the segment file extension.
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 12eae9a75..2f182273b 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -112,6 +112,11 @@ namespace MediaBrowser.Controller.Entities
return GetResult(result, queryParent, query);
}
+ case CollectionType.Books:
+ case CollectionType.Photos:
+ case CollectionType.HomeVideos:
+ return GetResult(queryParent.GetChildren(user, true), queryParent, query);
+
case CollectionType.Folders:
return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 2c62798e1..43c985f33 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -1673,7 +1673,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("viewType");
}
- var id = GetNewItemId("31_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty), typeof(UserView));
+ var id = GetNewItemId("35_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty), typeof(UserView));
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
index 6ea3233f2..e213327ea 100644
--- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -237,14 +237,14 @@ namespace MediaBrowser.Server.Implementations.UserViews
protected override Task<Stream> CreateImageAsync(IHasImages item, List<BaseItem> itemsWithImages, ImageType imageType, int imageIndex)
{
- if (itemsWithImages.Count == 0)
- {
- return null;
- }
-
var view = (UserView)item;
if (imageType == ImageType.Primary && IsUsingCollectionStrip(view))
{
+ if (itemsWithImages.Count == 0 && !string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540);
return Task.FromResult(stream);
}