diff options
Diffstat (limited to 'MediaBrowser.Api')
| -rw-r--r-- | MediaBrowser.Api/Dlna/DlnaServerService.cs | 89 | ||||
| -rw-r--r-- | MediaBrowser.Api/Dlna/DlnaService.cs (renamed from MediaBrowser.Api/DlnaService.cs) | 2 | ||||
| -rw-r--r-- | MediaBrowser.Api/Library/LibraryService.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.Api/MediaBrowser.Api.csproj | 3 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/BaseStreamingService.cs | 44 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 51 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/VideoHlsService.cs | 8 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/StreamRequest.cs | 8 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/StreamState.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Api/SessionsService.cs | 18 |
10 files changed, 164 insertions, 64 deletions
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs new file mode 100644 index 0000000000..922c67aa2d --- /dev/null +++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs @@ -0,0 +1,89 @@ +using MediaBrowser.Controller.Dlna; +using ServiceStack; +using ServiceStack.Web; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Dlna +{ + [Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")] + [Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")] + public class GetDescriptionXml + { + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string UuId { get; set; } + } + + [Route("/Dlna/{UuId}/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")] + [Route("/Dlna/{UuId}/contentdirectory", "GET", Summary = "Gets the content directory xml")] + public class GetContentDirectory + { + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string UuId { get; set; } + } + + [Route("/Dlna/{UuId}/control", "POST", Summary = "Processes a control request")] + public class ProcessControlRequest : IRequiresRequestStream + { + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string UuId { get; set; } + + public Stream RequestStream { get; set; } + } + + public class DlnaServerService : BaseApiService + { + private readonly IDlnaManager _dlnaManager; + + public DlnaServerService(IDlnaManager dlnaManager) + { + _dlnaManager = dlnaManager; + } + + public object Get(GetDescriptionXml request) + { + var xml = _dlnaManager.GetServerDescriptionXml(GetRequestHeaders(), request.UuId); + + return ResultFactory.GetResult(xml, "text/xml"); + } + + public object Get(GetContentDirectory request) + { + var xml = _dlnaManager.GetContentDirectoryXml(GetRequestHeaders()); + + return ResultFactory.GetResult(xml, "text/xml"); + } + + public object Post(ProcessControlRequest request) + { + var response = PostAsync(request).Result; + + return ResultFactory.GetResult(response.Xml, "text/xml"); + } + + private async Task<ControlResponse> PostAsync(ProcessControlRequest request) + { + using (var reader = new StreamReader(request.RequestStream)) + { + return _dlnaManager.ProcessControlRequest(new ControlRequest + { + Headers = GetRequestHeaders(), + InputXml = await reader.ReadToEndAsync().ConfigureAwait(false) + }); + } + } + + private IDictionary<string, string> GetRequestHeaders() + { + var headers = new Dictionary<string, string>(); + + foreach (var key in Request.Headers.AllKeys) + { + headers[key] = Request.Headers[key]; + } + + return headers; + } + } +} diff --git a/MediaBrowser.Api/DlnaService.cs b/MediaBrowser.Api/Dlna/DlnaService.cs index 792a7ff43f..9e6ca3aea9 100644 --- a/MediaBrowser.Api/DlnaService.cs +++ b/MediaBrowser.Api/Dlna/DlnaService.cs @@ -4,7 +4,7 @@ using ServiceStack; using System.Collections.Generic; using System.Linq; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Dlna { [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")] public class GetProfileInfos : IReturn<List<DeviceProfileInfo>> diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 6667ba25c6..967bc1fbac 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } - [Route("/Videos/{Id}/Subtitle/{Index}", "GET")] + [Route("/Videos/{Id}/Subtitles/{Index}", "GET")] [Api(Description = "Gets an external subtitle file")] public class GetSubtitle { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index c03eddf99e..edbae3903e 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,7 +66,8 @@ <Link>Properties\SharedVersion.cs</Link> </Compile> <Compile Include="ChannelService.cs" /> - <Compile Include="DlnaService.cs" /> + <Compile Include="Dlna\DlnaServerService.cs" /> + <Compile Include="Dlna\DlnaService.cs" /> <Compile Include="Movies\CollectionService.cs" /> <Compile Include="Music\AlbumsService.cs" /> <Compile Include="AppThemeService.cs" /> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index bb55b893a2..e588068d0d 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -185,9 +185,9 @@ namespace MediaBrowser.Api.Playback { var args = string.Empty; - if (state.IsRemote || !state.HasMediaStreams) + if (!state.HasMediaStreams) { - return string.Empty; + return state.IsInputVideo ? "-sn" : string.Empty; } if (state.VideoStream != null) @@ -1341,6 +1341,12 @@ namespace MediaBrowser.Api.Playback RequestedUrl = url }; + if (!string.IsNullOrWhiteSpace(request.AudioCodec)) + { + state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); + } + var item = string.IsNullOrEmpty(request.MediaSourceId) ? DtoService.GetItemByDtoId(request.Id) : DtoService.GetItemByDtoId(request.MediaSourceId); @@ -1487,33 +1493,27 @@ namespace MediaBrowser.Api.Playback if (videoRequest != null) { - if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream, state.VideoType)) + if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) { videoRequest.VideoCodec = "copy"; } - //if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream)) - //{ - // request.AudioCodec = "copy"; - //} + if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs)) + { + request.AudioCodec = "copy"; + } } return state; } - private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream, VideoType videoType) + private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) { if (videoStream.IsInterlaced) { return false; } - // Not going to attempt this with folder rips - if (videoType != VideoType.VideoFile) - { - return false; - } - // Source and target codecs must match if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { @@ -1584,13 +1584,13 @@ namespace MediaBrowser.Api.Playback } } - return SupportsAutomaticVideoStreamCopy; + return request.EnableAutoStreamCopy; } - private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream) + private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs) { // Source and target codecs must match - if (string.IsNullOrEmpty(request.AudioCodec) || !string.Equals(request.AudioCodec, audioStream.Codec, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) { return false; } @@ -1623,15 +1623,7 @@ namespace MediaBrowser.Api.Playback } } - return SupportsAutomaticVideoStreamCopy; - } - - protected virtual bool SupportsAutomaticVideoStreamCopy - { - get - { - return false; - } + return true; } private void ApplyDeviceProfileSettings(StreamState state) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 6e71e503f3..b5cd1bd409 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -24,7 +24,8 @@ namespace MediaBrowser.Api.Playback.Hls /// </summary> public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) { } @@ -77,6 +78,7 @@ namespace MediaBrowser.Api.Playback.Hls return ProcessRequestAsync(request).Result; } + private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); /// <summary> /// Processes the request async. /// </summary> @@ -103,32 +105,41 @@ namespace MediaBrowser.Api.Playback.Hls } var playlist = GetOutputFilePath(state); - var isPlaylistNewlyCreated = false; - // If the playlist doesn't already exist, startup ffmpeg - if (!File.Exists(playlist)) + if (File.Exists(playlist)) { - isPlaylistNewlyCreated = true; - + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); + } + else + { + await FfmpegStartLock.WaitAsync().ConfigureAwait(false); try { - await StartFfMpeg(state, playlist).ConfigureAwait(false); + if (File.Exists(playlist)) + { + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); + } + else + { + // If the playlist doesn't already exist, startup ffmpeg + try + { + await StartFfMpeg(state, playlist).ConfigureAwait(false); + } + catch + { + state.Dispose(); + throw; + } + } + + await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); } - catch + finally { - state.Dispose(); - throw; + FfmpegStartLock.Release(); } } - else - { - ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); - } - - if (isPlaylistNewlyCreated) - { - await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); - } int audioBitrate; int videoBitrate; @@ -295,7 +306,7 @@ namespace MediaBrowser.Api.Playback.Hls // If performSubtitleConversions is true we're actually starting ffmpeg var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0"; - + var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number {9} -hls_list_size {10} \"{11}\"", itsOffset, inputModifier, diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index bedf742c2c..1bca4cae9a 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -98,14 +98,6 @@ namespace MediaBrowser.Api.Playback.Hls ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); } - protected override bool SupportsAutomaticVideoStreamCopy - { - get - { - return true; - } - } - /// <summary> /// Gets the specified request. /// </summary> diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index add517b5da..d5355783e8 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -171,5 +171,13 @@ namespace MediaBrowser.Api.Playback return Width.HasValue || Height.HasValue; } } + + [ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool EnableAutoStreamCopy { get; set; } + + public VideoStreamRequest() + { + EnableAutoStreamCopy = true; + } } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index ce7d79917a..e41f296638 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -67,10 +67,13 @@ namespace MediaBrowser.Api.Playback public string AudioSync = "1"; public string VideoSync = "vfr"; + public List<string> SupportedAudioCodecs { get; set; } + public StreamState(ILiveTvManager liveTvManager, ILogger logger) { _liveTvManager = liveTvManager; _logger = logger; + SupportedAudioCodecs = new List<string>(); } public string InputAudioSync { get; set; } diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 5c1c716417..b71cf67b66 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Session; using ServiceStack; @@ -32,10 +31,10 @@ namespace MediaBrowser.Api } /// <summary> - /// Class BrowseTo + /// Class DisplayContent /// </summary> [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] - public class BrowseTo : IReturnVoid + public class DisplayContent : IReturnVoid { /// <summary> /// Gets or sets the id. @@ -218,6 +217,7 @@ namespace MediaBrowser.Api public Guid UserId { get; set; } } + [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] [Route("/Sessions/{Id}/Capabilities", "POST", Summary = "Updates capabilities for a device")] public class PostCapabilities : IReturnVoid { @@ -226,7 +226,7 @@ namespace MediaBrowser.Api /// </summary> /// <value>The id.</value> [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } + public string Id { get; set; } [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string PlayableMediaTypes { get; set; } @@ -307,7 +307,7 @@ namespace MediaBrowser.Api /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> - public void Post(BrowseTo request) + public void Post(DisplayContent request) { var command = new BrowseRequest { @@ -421,7 +421,11 @@ namespace MediaBrowser.Api public void Post(PostCapabilities request) { - _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities + if (string.IsNullOrWhiteSpace(request.Id)) + { + request.Id = GetSession().Id.ToString("N"); + } + _sessionManager.ReportCapabilities(new Guid(request.Id), new SessionCapabilities { PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), |
