diff options
| author | Michalis Adamidis <gsnerf@gsnerf.de> | 2014-08-06 21:06:34 +0200 |
|---|---|---|
| committer | Michalis Adamidis <gsnerf@gsnerf.de> | 2014-08-06 21:06:34 +0200 |
| commit | b957e7c7b91257d7f33f9890b9a14445a80164ea (patch) | |
| tree | 3a6082b1bdf717d0f28ef96f14a70c1265071ac4 /MediaBrowser.Api | |
| parent | 7994f0dcd9082cc657e07dbff6ecc4e638f1f527 (diff) | |
| parent | 284bd3e9f562ae499ad8d8b778392196ac99ed3a (diff) | |
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Api')
| -rw-r--r-- | MediaBrowser.Api/Playback/BaseStreamingService.cs | 7 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 71 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/StreamRequest.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/StreamState.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Api/PlaylistService.cs | 7 | ||||
| -rw-r--r-- | MediaBrowser.Api/Subtitles/SubtitleService.cs | 82 | ||||
| -rw-r--r-- | MediaBrowser.Api/SystemService.cs | 2 |
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) { |
