diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 2a17b2fe8f..379b69710a 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -1,8 +1,11 @@ -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; using ServiceStack; using System; using System.Collections.Generic; @@ -13,7 +16,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Playback { [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")] - public class GetLiveMediaInfo : IReturn + public class GetLiveMediaInfo : IReturn { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } @@ -23,7 +26,17 @@ namespace MediaBrowser.Api.Playback } [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")] - public class GetPlaybackInfo : IReturn + public class GetPlaybackInfo : IReturn + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } + } + + [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] + public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } @@ -36,26 +49,55 @@ namespace MediaBrowser.Api.Playback public class MediaInfoService : BaseApiService { private readonly IMediaSourceManager _mediaSourceManager; + private readonly IDeviceManager _deviceManager; + private readonly ILibraryManager _libraryManager; - public MediaInfoService(IMediaSourceManager mediaSourceManager) + public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager) { _mediaSourceManager = mediaSourceManager; + _deviceManager = deviceManager; + _libraryManager = libraryManager; + } + + public async Task Get(GetPlaybackInfo request) + { + var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + return ToOptimizedResult(result); } - public Task Get(GetPlaybackInfo request) + public async Task Get(GetLiveMediaInfo request) { - return GetPlaybackInfo(request.Id, request.UserId); + var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + return ToOptimizedResult(result); } - public Task Get(GetLiveMediaInfo request) + public async Task Post(GetPostedPlaybackInfo request) { - return GetPlaybackInfo(request.Id, request.UserId); + var info = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); + + var profile = request.DeviceProfile; + //if (profile == null) + //{ + // var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); + // if (caps != null) + // { + // profile = caps.DeviceProfile; + // } + //} + + if (profile != null) + { + SetDeviceSpecificData(request.Id, info, profile, authInfo, null); + } + + return ToOptimizedResult(info); } - private async Task GetPlaybackInfo(string id, string userId) + private async Task GetPlaybackInfo(string id, string userId) { IEnumerable mediaSources; - var result = new LiveMediaInfoResult(); + var result = new PlaybackInfoResponse(); try { @@ -68,9 +110,89 @@ namespace MediaBrowser.Api.Playback } result.MediaSources = mediaSources.ToList(); - result.StreamId = Guid.NewGuid().ToString("N"); - return ToOptimizedResult(result); + if (result.MediaSources.Count == 0) + { + if (!result.ErrorCode.HasValue) + { + result.ErrorCode = PlaybackErrorCode.NoCompatibleStream; + } + } + else + { + result.StreamId = Guid.NewGuid().ToString("N"); + } + + return result; + } + + private void SetDeviceSpecificData(string itemId, PlaybackInfoResponse result, DeviceProfile profile, AuthorizationInfo auth, int? maxBitrate) + { + var streamBuilder = new StreamBuilder(); + + var item = _libraryManager.GetItemById(itemId); + + foreach (var mediaSource in result.MediaSources) + { + var options = new VideoOptions + { + MediaSources = new List { mediaSource }, + Context = EncodingContext.Streaming, + DeviceId = auth.DeviceId, + ItemId = item.Id.ToString("N"), + Profile = profile, + MaxBitrate = maxBitrate + }; + + if (mediaSource.SupportsDirectPlay) + { + var supportsDirectStream = mediaSource.SupportsDirectStream; + + // Dummy this up to fool StreamBuilder + mediaSource.SupportsDirectStream = true; + + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = item is Video ? + streamBuilder.BuildVideoItem(options) : + streamBuilder.BuildAudioItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) + { + mediaSource.SupportsDirectPlay = false; + } + + // Set this back to what it was + mediaSource.SupportsDirectStream = supportsDirectStream; + } + + if (mediaSource.SupportsDirectStream) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = item is Video ? + streamBuilder.BuildVideoItem(options) : + streamBuilder.BuildAudioItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) + { + mediaSource.SupportsDirectStream = false; + } + } + + if (mediaSource.SupportsTranscoding) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = item is Video ? + streamBuilder.BuildVideoItem(options) : + streamBuilder.BuildAudioItem(options); + + if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) + { + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); + mediaSource.TranscodingContainer = streamInfo.Container; + mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + } + } + } } } } diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs index 0c2e30923c..94fa1ac02f 100644 --- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs @@ -62,7 +62,8 @@ namespace MediaBrowser.Controller.Channels RunTimeTicks = RunTimeTicks, Name = id, Id = id, - ReadAtNativeFramerate = ReadAtNativeFramerate + ReadAtNativeFramerate = ReadAtNativeFramerate, + SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http }; var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index ba84beca34..00dc5dc672 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -501,7 +501,8 @@ namespace MediaBrowser.Controller.Entities Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), Timestamp = i.Timestamp, Type = type, - PlayableStreamFileNames = i.PlayableStreamFileNames.ToList() + PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(), + SupportsDirectStream = i.VideoType == VideoType.VideoFile }; if (i.IsShortcut) diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index c2d46b9b3f..118e5659ad 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -800,12 +800,15 @@ MediaInfo\IBlurayExaminer.cs - - MediaInfo\LiveMediaInfoResult.cs - MediaInfo\MediaProtocol.cs + + MediaInfo\PlaybackInfoRequest.cs + + + MediaInfo\PlaybackInfoResponse.cs + MediaInfo\SubtitleFormat.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 52745b5dee..a97bf2a8c6 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -756,12 +756,15 @@ MediaInfo\IBlurayExaminer.cs - - MediaInfo\LiveMediaInfoResult.cs - MediaInfo\MediaProtocol.cs + + MediaInfo\PlaybackInfoRequest.cs + + + MediaInfo\PlaybackInfoResponse.cs + MediaInfo\SubtitleFormat.cs diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 14cdbdc9ab..19d6acf339 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -251,7 +251,7 @@ namespace MediaBrowser.Model.ApiClient /// The item identifier. /// The user identifier. /// Task<LiveMediaInfoResult>. - Task GetPlaybackInfo(string itemId, string userId); + Task GetPlaybackInfo(string itemId, string userId); /// /// Gets the users async. diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 3f88f9ac06..7338f604c0 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -118,9 +118,7 @@ namespace MediaBrowser.Model.Dlna return stream; } - PlaybackException error = new PlaybackException(); - error.ErrorCode = PlaybackErrorCode.NoCompatibleStream; - throw error; + return null; } private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) @@ -221,7 +219,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength; playlistItem.Container = transcodingProfile.Container; playlistItem.AudioCodec = transcodingProfile.AudioCodec; - playlistItem.Protocol = transcodingProfile.Protocol; + playlistItem.SubProtocol = transcodingProfile.Protocol; List audioCodecProfiles = new List(); foreach (CodecProfile i in options.Profile.CodecProfiles) @@ -374,7 +372,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0]; playlistItem.VideoCodec = transcodingProfile.VideoCodec; - playlistItem.Protocol = transcodingProfile.Protocol; + playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.AudioStreamIndex = audioStreamIndex; List videoTranscodingConditions = new List(); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 4eb1c0a8ef..63ddd07cd7 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna public string Container { get; set; } - public string Protocol { get; set; } + public string SubProtocol { get; set; } public long StartPositionTicks { get; set; } @@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public string SubtitleFormat { get; set; } - public LiveMediaInfoResult PlaybackInfo { get; set; } + public PlaybackInfoResponse PlaybackInfo { get; set; } public string MediaSourceId { @@ -115,7 +115,7 @@ namespace MediaBrowser.Model.Dlna return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand); } - if (StringHelper.EqualsIgnoreCase(Protocol, "hls")) + if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls")) { return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, dlnaCommand); } @@ -207,7 +207,7 @@ namespace MediaBrowser.Model.Dlna List list = new List(); // HLS will preserve timestamps so we can just grab the full subtitle stream - long startPositionTicks = StringHelper.EqualsIgnoreCase(Protocol, "hls") + long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls") ? 0 : StartPositionTicks; diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 5495a1f692..31d310acda 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.Model.Dto public bool ReadAtNativeFramerate { get; set; } public bool SupportsTranscoding { get; set; } public bool SupportsDirectStream { get; set; } + public bool SupportsDirectPlay { get; set; } public VideoType? VideoType { get; set; } @@ -39,7 +40,11 @@ namespace MediaBrowser.Model.Dto public int? Bitrate { get; set; } public TransportStreamTimestamp? Timestamp { get; set; } - public Dictionary RequiredHttpHeaders { get; set; } + public Dictionary RequiredHttpHeaders { get; set; } + + public string TranscodingUrl { get; set; } + public string TranscodingSubProtocol { get; set; } + public string TranscodingContainer { get; set; } public MediaSourceInfo() { @@ -49,6 +54,7 @@ namespace MediaBrowser.Model.Dto PlayableStreamFileNames = new List(); SupportsTranscoding = true; SupportsDirectStream = true; + SupportsDirectPlay = true; } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 3a6eda6207..30eec434db 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -140,7 +140,8 @@ - + + diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs new file mode 100644 index 0000000000..ffd4995ad9 --- /dev/null +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -0,0 +1,9 @@ +using MediaBrowser.Model.Dlna; + +namespace MediaBrowser.Model.MediaInfo +{ + public class PlaybackInfoRequest + { + public DeviceProfile DeviceProfile { get; set; } + } +} diff --git a/MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs similarity index 91% rename from MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs rename to MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs index 16f4f76ee4..ed0824e8a4 100644 --- a/MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace MediaBrowser.Model.MediaInfo { - public class LiveMediaInfoResult + public class PlaybackInfoResponse { /// /// Gets or sets the media sources. @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.MediaInfo /// The error code. public PlaybackErrorCode? ErrorCode { get; set; } - public LiveMediaInfoResult() + public PlaybackInfoResponse() { MediaSources = new List(); }