aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Api
diff options
context:
space:
mode:
authorMichalis Adamidis <gsnerf@gsnerf.de>2014-08-06 21:06:34 +0200
committerMichalis Adamidis <gsnerf@gsnerf.de>2014-08-06 21:06:34 +0200
commitb957e7c7b91257d7f33f9890b9a14445a80164ea (patch)
tree3a6082b1bdf717d0f28ef96f14a70c1265071ac4 /MediaBrowser.Api
parent7994f0dcd9082cc657e07dbff6ecc4e638f1f527 (diff)
parent284bd3e9f562ae499ad8d8b778392196ac99ed3a (diff)
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Api')
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs7
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs71
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs3
-rw-r--r--MediaBrowser.Api/PlaylistService.cs7
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs82
-rw-r--r--MediaBrowser.Api/SystemService.cs2
7 files changed, 161 insertions, 17 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 1963ad10a5..9d54458a6e 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -1605,6 +1605,8 @@ namespace MediaBrowser.Api.Playback
{
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
}
+
+ state.AllMediaStreams = mediaStreams;
}
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
@@ -1640,7 +1642,10 @@ namespace MediaBrowser.Api.Playback
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue)
{
- return false;
+ if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
+ {
+ return false;
+ }
}
// Source and target codecs must match
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 10543351b9..42fa63fb7a 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -5,6 +5,8 @@ using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using ServiceStack;
using System;
@@ -18,8 +20,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls
{
- [Route("/Videos/{Id}/master.m3u8", "GET")]
- [Api(Description = "Gets a video stream using HTTP live streaming.")]
+ [Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
public class GetMasterHlsVideoStream : VideoStreamRequest
{
public bool EnableAdaptiveBitrateStreaming { get; set; }
@@ -30,8 +31,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- [Route("/Videos/{Id}/main.m3u8", "GET")]
- [Api(Description = "Gets a video stream using HTTP live streaming.")]
+ [Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
public class GetMainHlsVideoStream : VideoStreamRequest
{
}
@@ -359,7 +359,17 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
playlistUrl += queryString;
- AppendPlaylist(builder, playlistUrl, totalBitrate);
+ var request = (GetMasterHlsVideoStream) state.Request;
+
+ var subtitleStreams = state.AllMediaStreams
+ .Where(i => i.IsTextSubtitleStream)
+ .ToList();
+
+ var subtitleGroup = subtitleStreams.Count > 0 && request.SubtitleMethod == SubtitleDeliveryMethod.Hls ?
+ "subs" :
+ null;
+
+ AppendPlaylist(builder, playlistUrl, totalBitrate, subtitleGroup);
if (EnableAdaptiveBitrateStreaming(state))
{
@@ -369,16 +379,52 @@ namespace MediaBrowser.Api.Playback.Hls
var variation = GetBitrateVariation(totalBitrate);
var newBitrate = totalBitrate - variation;
- AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
+ AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate, subtitleGroup);
variation *= 2;
newBitrate = totalBitrate - variation;
- AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
+ AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate, subtitleGroup);
+ }
+
+ if (!string.IsNullOrWhiteSpace(subtitleGroup))
+ {
+ AddSubtitles(state, subtitleStreams, builder);
}
return builder.ToString();
}
+ private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder)
+ {
+ var selectedIndex = state.SubtitleStream == null ? (int?)null : state.SubtitleStream.Index;
+
+ foreach (var stream in subtitles)
+ {
+ const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},URI=\"{3}\",LANGUAGE=\"{4}\"";
+
+ var name = stream.Language;
+
+ var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
+ var isForced = stream.IsForced;
+
+ if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown";
+
+ var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}",
+ state.Request.MediaSourceId,
+ stream.Index.ToString(UsCulture),
+ 30.ToString(UsCulture));
+
+ var line = string.Format(format,
+ name,
+ isDefault ? "YES" : "NO",
+ isForced ? "YES" : "NO",
+ url,
+ stream.Language ?? "Unknown");
+
+ builder.AppendLine(line);
+ }
+ }
+
private bool EnableAdaptiveBitrateStreaming(StreamState state)
{
var request = state.Request as GetMasterHlsVideoStream;
@@ -397,9 +443,16 @@ namespace MediaBrowser.Api.Playback.Hls
return state.VideoRequest.VideoBitRate.HasValue;
}
- private void AppendPlaylist(StringBuilder builder, string url, int bitrate)
+ private void AppendPlaylist(StringBuilder builder, string url, int bitrate, string subtitleGroup)
{
- builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture));
+ var header = "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture);
+
+ if (!string.IsNullOrWhiteSpace(subtitleGroup))
+ {
+ header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup);
+ }
+
+ builder.AppendLine(header);
builder.AppendLine(url);
}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index dfb57ef0d7..c72ead949d 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -1,4 +1,5 @@
-using ServiceStack;
+using MediaBrowser.Model.Dlna;
+using ServiceStack;
namespace MediaBrowser.Api.Playback
{
@@ -160,6 +161,9 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Level { get; set; }
+ [ApiMember(Name = "SubtitleDeliveryMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public SubtitleDeliveryMethod SubtitleMethod { get; set; }
+
/// <summary>
/// Gets a value indicating whether this instance has fixed resolution.
/// </summary>
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index c6f4544477..1d3ff939af 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -38,6 +38,8 @@ namespace MediaBrowser.Api.Playback
public string InputContainer { get; set; }
+ public List<MediaStream> AllMediaStreams { get; set; }
+
public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; }
@@ -78,6 +80,7 @@ namespace MediaBrowser.Api.Playback
SupportedAudioCodecs = new List<string>();
PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ AllMediaStreams = new List<MediaStream>();
}
public string InputAudioSync { get; set; }
diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs
index b4d2e2f0f2..2e3d38f465 100644
--- a/MediaBrowser.Api/PlaylistService.cs
+++ b/MediaBrowser.Api/PlaylistService.cs
@@ -41,6 +41,9 @@ namespace MediaBrowser.Api
{
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public string Id { get; set; }
+
+ [ApiMember(Name = "EntryIds", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+ public string EntryIds { get; set; }
}
[Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")]
@@ -122,9 +125,9 @@ namespace MediaBrowser.Api
public void Delete(RemoveFromPlaylist request)
{
- //var task = _playlistManager.RemoveFromPlaylist(request.Id, request.Ids.Split(',').Select(i => new Guid(i)));
+ var task = _playlistManager.RemoveFromPlaylist(request.Id, request.EntryIds.Split(','));
- //Task.WaitAll(task);
+ Task.WaitAll(task);
}
public object Get(GetPlaylistItems request)
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index 3e692cb22f..dc5799239a 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -1,6 +1,4 @@
-using System.IO;
-using System.Linq;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
@@ -11,6 +9,10 @@ using MediaBrowser.Model.Providers;
using ServiceStack;
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -69,7 +71,8 @@ namespace MediaBrowser.Api.Subtitles
public string Id { get; set; }
}
- [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")]
+ [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")]
+ [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/{StartPositionTicks}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")]
public class GetSubtitle
{
/// <summary>
@@ -90,6 +93,29 @@ namespace MediaBrowser.Api.Subtitles
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public long StartPositionTicks { get; set; }
+
+ [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public long? EndPositionTicks { get; set; }
+ }
+
+ [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
+ public class GetSubtitlePlaylist
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+
+ [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string MediaSourceId { get; set; }
+
+ [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+ public int Index { get; set; }
+
+ [ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+ public int SegmentLength { get; set; }
}
public class SubtitleService : BaseApiService
@@ -105,6 +131,53 @@ namespace MediaBrowser.Api.Subtitles
_subtitleEncoder = subtitleEncoder;
}
+ public object Get(GetSubtitlePlaylist request)
+ {
+ var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
+
+ var mediaSource = item.GetMediaSources(false)
+ .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
+
+ var builder = new StringBuilder();
+
+ var runtime = mediaSource.RunTimeTicks ?? -1;
+
+ if (runtime <= 0)
+ {
+ throw new ArgumentException("HLS Subtitles are not supported for this media.");
+ }
+
+ builder.AppendLine("#EXTM3U");
+ builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
+ builder.AppendLine("#EXT-X-VERSION:3");
+ builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
+
+ long positionTicks = 0;
+ var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
+
+ while (positionTicks < runtime)
+ {
+ var remaining = runtime - positionTicks;
+ var lengthTicks = Math.Min(remaining, segmentLengthTicks);
+
+ builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
+
+ var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
+
+ var url = string.Format("stream.srt?StartPositionTicks={0}&EndPositionTicks={1}",
+ positionTicks.ToString(CultureInfo.InvariantCulture),
+ endPositionTicks.ToString(CultureInfo.InvariantCulture));
+
+ builder.AppendLine(url);
+
+ positionTicks += segmentLengthTicks;
+ }
+
+ builder.AppendLine("#EXT-X-ENDLIST");
+
+ return ResultFactory.GetResult(builder.ToString(), Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+ }
+
public object Get(GetSubtitle request)
{
if (string.IsNullOrEmpty(request.Format))
@@ -132,6 +205,7 @@ namespace MediaBrowser.Api.Subtitles
request.Index,
request.Format,
request.StartPositionTicks,
+ request.EndPositionTicks,
CancellationToken.None).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs
index e31e66d19d..259b1d8921 100644
--- a/MediaBrowser.Api/SystemService.cs
+++ b/MediaBrowser.Api/SystemService.cs
@@ -71,6 +71,8 @@ namespace MediaBrowser.Api
/// Initializes a new instance of the <see cref="SystemService" /> class.
/// </summary>
/// <param name="appHost">The app host.</param>
+ /// <param name="appPaths">The application paths.</param>
+ /// <param name="fileSystem">The file system.</param>
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem)
{