diff options
52 files changed, 1254 insertions, 402 deletions
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index b15e67ffa..1e9f2f7a2 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Api var item = _dtoService.GetItemByDtoId(request.ItemId); var newLockData = request.LockData ?? false; - var dontFetchMetaChanged = item.DontFetchMeta != newLockData; + var dontFetchMetaChanged = item.IsLocked != newLockData; UpdateItem(request, item); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index e1f4799f1..1001980ec 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1308,7 +1308,9 @@ namespace MediaBrowser.Api.Playback RequestedUrl = url }; - var item = DtoService.GetItemByDtoId(request.Id); + var item = string.IsNullOrEmpty(request.MediaSourceId) ? + DtoService.GetItemByDtoId(request.Id) : + DtoService.GetItemByDtoId(request.MediaSourceId); if (user != null && item.GetPlayAccess(user) != PlayAccess.Full) { diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 6b0375e2d..df52e5e3e 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -15,6 +15,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } + [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string MediaSourceId { get; set; } + [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceId { get; set; } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index e026aec03..c6051c02c 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -241,6 +241,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } + [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MediaSourceId { get; set; } + /// <summary> /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes. /// </summary> @@ -277,6 +280,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } + [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MediaSourceId { get; set; } + /// <summary> /// Gets or sets the position ticks. /// </summary> @@ -312,6 +318,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } + [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string MediaSourceId { get; set; } + /// <summary> /// Gets or sets the position ticks. /// </summary> @@ -736,7 +745,8 @@ namespace MediaBrowser.Api.UserLibrary CanSeek = request.CanSeek, Item = item, SessionId = GetSession().Id, - QueueableMediaTypes = queueableMediaTypes.Split(',').ToList() + QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(), + MediaSourceId = request.MediaSourceId }; _sessionManager.OnPlaybackStart(info); @@ -758,7 +768,8 @@ namespace MediaBrowser.Api.UserLibrary PositionTicks = request.PositionTicks, IsMuted = request.IsMuted, IsPaused = request.IsPaused, - SessionId = GetSession().Id + SessionId = GetSession().Id, + MediaSourceId = request.MediaSourceId }; var task = _sessionManager.OnPlaybackProgress(info); @@ -782,7 +793,8 @@ namespace MediaBrowser.Api.UserLibrary { Item = item, PositionTicks = request.PositionTicks, - SessionId = session.Id + SessionId = session.Id, + MediaSourceId = request.MediaSourceId }; var task = _sessionManager.OnPlaybackStopped(info); diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index b8b0aa9e9..8a3bc12b2 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -14,8 +14,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetYears /// </summary> - [Route("/Years", "GET")] - [Api(Description = "Gets all years from a given item, folder, or the entire library")] + [Route("/Years", "GET", Summary = "Gets all years from a given item, folder, or the entire library")] public class GetYears : GetItemsByName { } @@ -23,8 +22,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetYear /// </summary> - [Route("/Years/{Year}", "GET")] - [Api(Description = "Gets a year")] + [Route("/Years/{Year}", "GET", Summary = "Gets a year")] public class GetYear : IReturn<BaseItemDto> { /// <summary> diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index fa4b22cea..94432871c 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -13,8 +13,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Api { - [Route("/Videos/{Id}/AdditionalParts", "GET")] - [Api(Description = "Gets additional parts for a video.")] + [Route("/Videos/{Id}/AdditionalParts", "GET", Summary = "Gets additional parts for a video.")] public class GetAdditionalParts : IReturn<ItemsResult> { [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -28,16 +27,14 @@ namespace MediaBrowser.Api public string Id { get; set; } } - [Route("/Videos/{Id}/AlternateVersions", "DELETE")] - [Api(Description = "Assigns videos as alternates of antoher.")] - public class DeleteAlternateVersions : IReturnVoid + [Route("/Videos/{Id}/AlternateSources", "DELETE", Summary = "Removes alternate video sources.")] + public class DeleteAlternateSources : IReturnVoid { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } - [Route("/Videos/MergeVersions", "POST")] - [Api(Description = "Merges videos into a single record")] + [Route("/Videos/MergeVersions", "POST", Summary = "Merges videos into a single record")] public class MergeVersions : IReturnVoid { [ApiMember(Name = "Ids", Description = "Item id list. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] @@ -98,14 +95,14 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } - public void Delete(DeleteAlternateVersions request) + public void Delete(DeleteAlternateSources request) { var task = RemoveAlternateVersions(request); Task.WaitAll(task); } - private async Task RemoveAlternateVersions(DeleteAlternateVersions request) + private async Task RemoveAlternateVersions(DeleteAlternateSources request) { var video = (Video)_dtoService.GetItemByDtoId(request.Id); @@ -146,7 +143,7 @@ namespace MediaBrowser.Api var videos = items.Cast<Video>().ToList(); - var videosWithVersions = videos.Where(i => i.AlternateVersionCount > 0) + var videosWithVersions = videos.Where(i => i.MediaSourceCount > 1) .ToList(); if (videosWithVersions.Count > 1) @@ -158,14 +155,27 @@ namespace MediaBrowser.Api if (primaryVersion == null) { - primaryVersion = videos.OrderByDescending(i => + primaryVersion = videos.OrderBy(i => + { + if (i.Video3DFormat.HasValue) + { + return 1; + } + + if (i.VideoType != Model.Entities.VideoType.VideoFile) + { + return 1; + } + + return 0; + }) + .ThenByDescending(i => { var stream = i.GetDefaultVideoStream(); return stream == null || stream.Width == null ? 0 : stream.Width.Value; - }).ThenBy(i => i.Name.Length) - .First(); + }).First(); } foreach (var item in videos.Where(i => i.Id != primaryVersion.Id)) diff --git a/MediaBrowser.Controller/Dlna/CodecProfile.cs b/MediaBrowser.Controller/Dlna/CodecProfile.cs new file mode 100644 index 000000000..bff374298 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/CodecProfile.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Controller.Dlna +{ + public class CodecProfile + { + public CodecType Type { get; set; } + public List<ProfileCondition> Conditions { get; set; } + public string Codec { get; set; } + + public CodecProfile() + { + Conditions = new List<ProfileCondition>(); + } + + public List<string> GetCodecs() + { + return (Codec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + } + + public enum CodecType + { + VideoCodec = 0, + VideoAudioCodec = 1, + AudioCodec = 2 + } + + public class ProfileCondition + { + public ProfileConditionType Condition { get; set; } + public ProfileConditionValue Property { get; set; } + public string Value { get; set; } + } + + public enum ProfileConditionType + { + Equals = 0, + NotEquals = 1, + LessThanEqual = 2, + GreaterThanEqual = 3 + } + + public enum ProfileConditionValue + { + AudioChannels, + AudioBitrate, + Filesize, + Width, + Height, + VideoBitrate, + VideoFramerate, + VideoLevel + } +} diff --git a/MediaBrowser.Controller/Dlna/DeviceIdentification.cs b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs index 4ccba1106..20c94ad50 100644 --- a/MediaBrowser.Controller/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs @@ -67,5 +67,12 @@ namespace MediaBrowser.Controller.Dlna { public string Name { get; set; } public string Value { get; set; } + public HeaderMatchType Match { get; set; } + } + + public enum HeaderMatchType + { + Equals = 0, + Substring = 1 } } diff --git a/MediaBrowser.Controller/Dlna/DeviceProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs index ca5929d13..91be73bba 100644 --- a/MediaBrowser.Controller/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -55,10 +55,17 @@ namespace MediaBrowser.Controller.Dlna public string ProtocolInfo { get; set; } + public MediaProfile[] MediaProfiles { get; set; } + public CodecProfile[] CodecProfiles { get; set; } + + public int TimelineOffsetSeconds { get; set; } + public DeviceProfile() { DirectPlayProfiles = new DirectPlayProfile[] { }; TranscodingProfiles = new TranscodingProfile[] { }; + MediaProfiles = new MediaProfile[] { }; + CodecProfiles = new CodecProfile[] { }; } } } diff --git a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs index 68e25e2f7..53d32a2f8 100644 --- a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs @@ -1,60 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Xml.Serialization; +using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Controller.Dlna { public class DirectPlayProfile { - public string Container { get; set; } + public string[] Containers { get; set; } public string AudioCodec { get; set; } public string VideoCodec { get; set; } - [IgnoreDataMember] - [XmlIgnore] - public string[] Containers - { - get - { - return (Container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - set - { - Container = value == null ? null : string.Join(",", value); - } - } - - [IgnoreDataMember] - [XmlIgnore] - public string[] AudioCodecs - { - get - { - return (AudioCodec ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - set - { - AudioCodec = value == null ? null : string.Join(",", value); - } - } - - [IgnoreDataMember] - [XmlIgnore] - public string[] VideoCodecs - { - get - { - return (VideoCodec ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - set - { - VideoCodec = value == null ? null : string.Join(",", value); - } - } - - public string OrgPn { get; set; } - public string MimeType { get; set; } public DlnaProfileType Type { get; set; } public List<ProfileCondition> Conditions { get; set; } @@ -62,37 +16,25 @@ namespace MediaBrowser.Controller.Dlna public DirectPlayProfile() { Conditions = new List<ProfileCondition>(); + + Containers = new string[] { }; } - } - public class ProfileCondition - { - public ProfileConditionType Condition { get; set; } - public ProfileConditionValue Value { get; set; } + public List<string> GetAudioCodecs() + { + return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + + public List<string> GetVideoCodecs() + { + return (VideoCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } } public enum DlnaProfileType { Audio = 0, - Video = 1 - } - - public enum ProfileConditionType - { - Equals = 0, - NotEquals = 1, - LessThanEqual = 2, - GreaterThanEqual = 3 - } - - public enum ProfileConditionValue - { - AudioChannels, - AudioBitrate, - Filesize, - VideoWidth, - VideoHeight, - VideoBitrate, - VideoFramerate + Video = 1, + Photo = 2 } } diff --git a/MediaBrowser.Controller/Dlna/MediaProfile.cs b/MediaBrowser.Controller/Dlna/MediaProfile.cs new file mode 100644 index 000000000..5fa41b18a --- /dev/null +++ b/MediaBrowser.Controller/Dlna/MediaProfile.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Controller.Dlna +{ + public class MediaProfile + { + public string Container { get; set; } + public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + + public DlnaProfileType Type { get; set; } + public string OrgPn { get; set; } + public string MimeType { get; set; } + + public List<string> GetAudioCodecs() + { + return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + + public List<string> GetVideoCodecs() + { + return (VideoCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + } +} diff --git a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs index 3b0a513d1..530a44b8c 100644 --- a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs @@ -8,12 +8,7 @@ namespace MediaBrowser.Controller.Dlna public DlnaProfileType Type { get; set; } - public string MimeType { get; set; } - - public string OrgPn { get; set; } - public string VideoCodec { get; set; } - public string AudioCodec { get; set; } public List<TranscodingSetting> Settings { get; set; } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 03039dc83..a02851a9f 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -29,13 +29,6 @@ namespace MediaBrowser.Controller.Dto SessionInfoDto GetSessionInfoDto(SessionInfo session); /// <summary> - /// Gets the base item info. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>BaseItemInfo.</returns> - BaseItemInfo GetBaseItemInfo(BaseItem item); - - /// <summary> /// Gets the dto id. /// </summary> /// <param name="item">The item.</param> diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 76e0e1fc5..a6f501689 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -37,11 +37,11 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public int AlternateVersionCount + public int MediaSourceCount { get { - return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count; + return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count + 1; } } diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index 2ec3d308e..61984c795 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Controller.Library public List<User> Users { get; set; } public long? PlaybackPositionTicks { get; set; } public BaseItem Item { get; set; } + public string MediaSourceId { get; set; } public PlaybackProgressEventArgs() { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ac178ff53..b51824bdb 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -78,10 +78,12 @@ <Compile Include="Channels\Channel.cs" /> <Compile Include="Collections\CollectionCreationOptions.cs" /> <Compile Include="Collections\ICollectionManager.cs" /> + <Compile Include="Dlna\CodecProfile.cs" /> <Compile Include="Dlna\DeviceIdentification.cs" /> <Compile Include="Dlna\DirectPlayProfile.cs" /> <Compile Include="Dlna\IDlnaManager.cs" /> <Compile Include="Dlna\DeviceProfile.cs" /> + <Compile Include="Dlna\MediaProfile.cs" /> <Compile Include="Dlna\TranscodingProfile.cs" /> <Compile Include="Drawing\IImageProcessor.cs" /> <Compile Include="Drawing\ImageFormat.cs" /> diff --git a/MediaBrowser.Controller/Session/PlaybackInfo.cs b/MediaBrowser.Controller/Session/PlaybackInfo.cs index ab3111e76..a97f9e0d0 100644 --- a/MediaBrowser.Controller/Session/PlaybackInfo.cs +++ b/MediaBrowser.Controller/Session/PlaybackInfo.cs @@ -34,5 +34,11 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <value>The session id.</value> public Guid SessionId { get; set; } + + /// <summary> + /// Gets or sets the media version identifier. + /// </summary> + /// <value>The media version identifier.</value> + public string MediaSourceId { get; set; } } } diff --git a/MediaBrowser.Controller/Session/PlaybackProgressInfo.cs b/MediaBrowser.Controller/Session/PlaybackProgressInfo.cs index a07543260..3d402aa6f 100644 --- a/MediaBrowser.Controller/Session/PlaybackProgressInfo.cs +++ b/MediaBrowser.Controller/Session/PlaybackProgressInfo.cs @@ -34,5 +34,11 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <value>The position ticks.</value> public long? PositionTicks { get; set; } + + /// <summary> + /// Gets or sets the media version identifier. + /// </summary> + /// <value>The media version identifier.</value> + public string MediaSourceId { get; set; } } } diff --git a/MediaBrowser.Controller/Session/PlaybackStopInfo.cs b/MediaBrowser.Controller/Session/PlaybackStopInfo.cs index 5d1ce0131..063abf78c 100644 --- a/MediaBrowser.Controller/Session/PlaybackStopInfo.cs +++ b/MediaBrowser.Controller/Session/PlaybackStopInfo.cs @@ -22,5 +22,11 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <value>The position ticks.</value> public long? PositionTicks { get; set; } + + /// <summary> + /// Gets or sets the media version identifier. + /// </summary> + /// <value>The media version identifier.</value> + public string MediaSourceId { get; set; } } } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 73e33d78c..9dd57d12a 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -120,11 +120,23 @@ namespace MediaBrowser.Controller.Session public BaseItem NowPlayingItem { get; set; } /// <summary> + /// Gets or sets the now playing media version identifier. + /// </summary> + /// <value>The now playing media version identifier.</value> + public string NowPlayingMediaSourceId { get; set; } + + + /// <summary> + /// Gets or sets the now playing run time ticks. + /// </summary> + /// <value>The now playing run time ticks.</value> + public long? NowPlayingRunTimeTicks { get; set; } + + /// <summary> /// Gets or sets the now playing position ticks. /// </summary> /// <value>The now playing position ticks.</value> public long? NowPlayingPositionTicks { get; set; } - /// <summary> /// Gets or sets a value indicating whether this instance is paused. /// </summary> @@ -162,6 +174,18 @@ namespace MediaBrowser.Controller.Session public bool SupportsFullscreenToggle { get; set; } /// <summary> + /// Gets or sets a value indicating whether [supports osd toggle]. + /// </summary> + /// <value><c>true</c> if [supports osd toggle]; otherwise, <c>false</c>.</value> + public bool SupportsOsdToggle { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether [supports navigation commands]. + /// </summary> + /// <value><c>true</c> if [supports navigation commands]; otherwise, <c>false</c>.</value> + public bool SupportsNavigationControl { get; set; } + + /// <summary> /// Gets a value indicating whether this instance is active. /// </summary> /// <value><c>true</c> if this instance is active; otherwise, <c>false</c>.</value> diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 1ec193204..be7295e12 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -62,13 +62,11 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"mkv"}, - MimeType = "x-mkv", Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "x-msvideo", Type = DlnaProfileType.Video }, new DirectPlayProfile @@ -76,6 +74,23 @@ namespace MediaBrowser.Dlna Containers = new[]{"mp4"}, Type = DlnaProfileType.Video } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/x-msvideo", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="mkv", + MimeType = "video/x-mkv", + Type = DlnaProfileType.Video + } } }); @@ -114,13 +129,11 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"mkv"}, - MimeType = "x-mkv", Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "x-msvideo", Type = DlnaProfileType.Video }, new DirectPlayProfile @@ -128,6 +141,23 @@ namespace MediaBrowser.Dlna Containers = new[]{"mp4"}, Type = DlnaProfileType.Video } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/x-msvideo", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="mkv", + MimeType = "video/x-mkv", + Type = DlnaProfileType.Video + } } }); @@ -166,13 +196,11 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"mkv"}, - MimeType = "x-mkv", Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "x-msvideo", Type = DlnaProfileType.Video }, new DirectPlayProfile @@ -180,6 +208,23 @@ namespace MediaBrowser.Dlna Containers = new[]{"mp4"}, Type = DlnaProfileType.Video } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/x-msvideo", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="mkv", + MimeType = "video/x-mkv", + Type = DlnaProfileType.Video + } } }); @@ -217,7 +262,6 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "avi", Type = DlnaProfileType.Video }, new DirectPlayProfile @@ -225,6 +269,16 @@ namespace MediaBrowser.Dlna Containers = new[]{"mp4"}, Type = DlnaProfileType.Video } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/avi", + Type = DlnaProfileType.Video + } } }); @@ -263,7 +317,16 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "x-msvideo", + Type = DlnaProfileType.Video + } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/x-msvideo", Type = DlnaProfileType.Video } } @@ -303,14 +366,29 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - Type = DlnaProfileType.Video, - MimeType = "avi" + Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"asf"}, - Type = DlnaProfileType.Audio, - MimeType = "x-ms-wmv" + Type = DlnaProfileType.Audio + } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/avi", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="asf", + MimeType = "video/x-ms-wmv", + Type = DlnaProfileType.Audio } } }); @@ -335,8 +413,7 @@ namespace MediaBrowser.Dlna new TranscodingProfile { Container = "ts", - Type = DlnaProfileType.Video, - MimeType = "mpeg" + Type = DlnaProfileType.Video } }, @@ -350,20 +427,48 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"wma"}, - Type = DlnaProfileType.Audio, - MimeType = "x-ms-wma" + Type = DlnaProfileType.Audio }, new DirectPlayProfile { Containers = new[]{"avi"}, - Type = DlnaProfileType.Video, - MimeType = "avi" + Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"mp4"}, - Type = DlnaProfileType.Video, - MimeType = "mp4" + Type = DlnaProfileType.Video + } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/avi", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="mp4", + MimeType = "video/mp4", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="ts", + MimeType = "video/mpeg", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="wma", + MimeType = "video/x-ms-wma", + Type = DlnaProfileType.Audio } } }); @@ -450,13 +555,21 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - Type = DlnaProfileType.Video , - MimeType="divx" + Type = DlnaProfileType.Video + } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/divx", + Type = DlnaProfileType.Video } } }); - //WDTV does not need any transcoding of the formats we support statically list.Add(new DeviceProfile { Name = "Philips (2010-)", @@ -479,49 +592,222 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - Type = DlnaProfileType.Video, - MimeType = "avi" + Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"mkv"}, - Type = DlnaProfileType.Video, - MimeType = "x-matroska" + Type = DlnaProfileType.Video + } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="avi", + MimeType = "video/avi", + Type = DlnaProfileType.Video + }, + + new MediaProfile + { + Container ="mkv", + MimeType = "video/x-matroska", + Type = DlnaProfileType.Video } } }); - //WDTV does not need any transcoding of the formats we support statically list.Add(new DeviceProfile { Name = "WDTV Live", ClientType = "DLNA", + TimelineOffsetSeconds = 5, + Identification = new DeviceIdentification { - ModelName = "WD TV HD Live" + ModelName = "WD TV HD Live", + + Headers = new List<HttpHeaderInfo> + { + new HttpHeaderInfo{ Name="User-Agent", Value="alphanetworks", Match= HeaderMatchType.Substring}, + new HttpHeaderInfo{ Name="User-Agent", Value="ALPHA Networks", Match= HeaderMatchType.Substring} + } + }, + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio, + AudioCodec = "mp3" + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video, + VideoCodec = "h264", + AudioCodec = "aac" + }, + new TranscodingProfile + { + Container = "jpeg", + Type = DlnaProfileType.Photo + } }, DirectPlayProfiles = new[] { new DirectPlayProfile { - Containers = new[]{"mp3", "flac", "m4a", "wma"}, + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video, + VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1", + AudioCodec = "ac3,dca,mp2,mp3,pcm" + }, + + new DirectPlayProfile + { + Containers = new[]{"mpeg"}, + Type = DlnaProfileType.Video, + VideoCodec = "mpeg1video,mpeg2video", + AudioCodec = "ac3,dca,mp2,mp3,pcm" + }, + + new DirectPlayProfile + { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video, + VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1", + AudioCodec = "ac3,dca,aac,mp2,mp3,pcm" + }, + + new DirectPlayProfile + { + Containers = new[]{"ts"}, + Type = DlnaProfileType.Video, + VideoCodec = "mpeg1video,mpeg2video,h264,vc1", + AudioCodec = "ac3,dca,mp2,mp3" + }, + + new DirectPlayProfile + { + Containers = new[]{"mp4", "mov"}, + Type = DlnaProfileType.Video, + VideoCodec = "h264,mpeg4", + AudioCodec = "ac3,aac,mp2,mp3" + }, + + new DirectPlayProfile + { + Containers = new[]{"asf"}, + Type = DlnaProfileType.Video, + VideoCodec = "vc1", + AudioCodec = "wmav2,wmapro" + }, + + new DirectPlayProfile + { + Containers = new[]{"asf"}, + Type = DlnaProfileType.Video, + VideoCodec = "mpeg2video", + AudioCodec = "mp2,ac3" + }, + + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + AudioCodec = "mp2,mp3", Type = DlnaProfileType.Audio }, new DirectPlayProfile { - Containers = new[]{"avi", "mp4", "mkv", "ts"}, + Containers = new[]{"mp4"}, + AudioCodec = "mp4", + Type = DlnaProfileType.Audio + }, + + new DirectPlayProfile + { + Containers = new[]{"flac"}, + AudioCodec = "flac", + Type = DlnaProfileType.Audio + }, + + new DirectPlayProfile + { + Containers = new[]{"asf"}, + AudioCodec = "wmav2,wmapro,wmavoice", + Type = DlnaProfileType.Audio + }, + + new DirectPlayProfile + { + Containers = new[]{"ogg"}, + AudioCodec = "vorbis", + Type = DlnaProfileType.Audio + }, + + new DirectPlayProfile + { + Type = DlnaProfileType.Photo, + + Containers = new[]{"jpeg", "png", "gif", "bmp", "tiff"}, + + Conditions = new List<ProfileCondition> + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Width, Value = "1920"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080"} + } + } + }, + + MediaProfiles = new[] + { + new MediaProfile + { + Container ="ts", + OrgPn = "MPEG_TS_SD_NA", Type = DlnaProfileType.Video } + }, + + CodecProfiles = new[] + { + new CodecProfile + { + Type = CodecType.VideoCodec, + Codec= "h264", + + Conditions = new List<ProfileCondition> + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Width, Value = "1920"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoLevel, Value = "41"} + } + }, + + new CodecProfile + { + Type = CodecType.VideoAudioCodec, + Codec= "aac", + + Conditions = new List<ProfileCondition> + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.AudioChannels, Value = "2"} + } + } } }); list.Add(new DeviceProfile { - //Linksys DMA2100us does not need any transcoding of the formats we support statically + // Linksys DMA2100us does not need any transcoding of the formats we support statically Name = "Linksys DMA2100", ClientType = "DLNA", @@ -547,10 +833,10 @@ namespace MediaBrowser.Dlna }); list.Add(new DeviceProfile - { + { Name = "Denon AVR", - ClientType = "DLNA", - + ClientType = "DLNA", + Identification = new DeviceIdentification { FriendlyName = @"Denon:\[AVR:.*", @@ -612,7 +898,7 @@ namespace MediaBrowser.Dlna public DeviceProfile GetProfile(DeviceIdentification deviceInfo) { - return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ?? + return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ?? GetDefaultProfile(); } diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index a7ee05cf3..800fb1b23 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -66,6 +66,7 @@ <Compile Include="PlayTo\PlaylistItem.cs"> <SubType>Code</SubType> </Compile> + <Compile Include="PlayTo\PlaylistItemFactory.cs" /> <Compile Include="PlayTo\PlayToManager.cs" /> <Compile Include="PlayTo\PlayToServerEntryPoint.cs" /> <Compile Include="PlayTo\ServiceAction.cs" /> diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index cb4bda127..e94663802 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; @@ -69,12 +70,12 @@ namespace MediaBrowser.Dlna.PlayTo _device.CurrentIdChanged += Device_CurrentIdChanged; _device.Start(); - _updateTimer = new System.Threading.Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs); + _updateTimer = new Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs); } #region Device EventHandlers & Update Timer - System.Threading.Timer _updateTimer; + Timer _updateTimer; async void Device_PlaybackChanged(object sender, TransportStateEventArgs e) { @@ -88,7 +89,7 @@ namespace MediaBrowser.Dlna.PlayTo { _playbackStarted = false; - await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo + await _sessionManager.OnPlaybackStopped(new Controller.Session.PlaybackStopInfo { Item = _currentItem, SessionId = _session.Id, @@ -164,7 +165,7 @@ namespace MediaBrowser.Dlna.PlayTo var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); if (playlistItem != null && playlistItem.Transcode) { - await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo + await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo { Item = _currentItem, SessionId = _session.Id, @@ -176,7 +177,7 @@ namespace MediaBrowser.Dlna.PlayTo } else if (_currentItem != null) { - await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo + await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo { Item = _currentItem, SessionId = _session.Id, @@ -263,7 +264,7 @@ namespace MediaBrowser.Dlna.PlayTo case PlaystateCommand.Seek: var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); - if (playlistItem != null && playlistItem.Transcode && playlistItem.IsVideo && _currentItem != null) + if (playlistItem != null && playlistItem.Transcode && playlistItem.MediaType == DlnaProfileType.Video && _currentItem != null) { var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress()); playlistItem.StartPositionTicks = newItem.StartPositionTicks; @@ -394,11 +395,13 @@ namespace MediaBrowser.Dlna.PlayTo var deviceInfo = _device.Properties; - var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification())); + var playlistItem = GetPlaylistItem(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification())); playlistItem.StartPositionTicks = startPostionTicks; - if (playlistItem.IsAudio) + if (playlistItem.MediaType == DlnaProfileType.Audio) + { playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress); + } else { playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress); @@ -412,6 +415,32 @@ namespace MediaBrowser.Dlna.PlayTo return playlistItem; } + private PlaylistItem GetPlaylistItem(BaseItem item, DeviceProfile profile) + { + var video = item as Video; + + if (video != null) + { + return new PlaylistItemFactory(_itemRepository).Create(video, profile); + } + + var audio = item as Audio; + + if (audio != null) + { + return new PlaylistItemFactory(_itemRepository).Create(audio, profile); + } + + var photo = item as Photo; + + if (photo != null) + { + return new PlaylistItemFactory(_itemRepository).Create(photo, profile); + } + + throw new ArgumentException("Unrecognized item type."); + } + /// <summary> /// Plays the items. /// </summary> diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index 4f776807e..1304f61b1 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -1,9 +1,4 @@ using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; -using System; -using System.IO; -using System.Linq; namespace MediaBrowser.Dlna.PlayTo { @@ -11,13 +6,13 @@ namespace MediaBrowser.Dlna.PlayTo { public string ItemId { get; set; } + public string MediaSourceId { get; set; } + public bool Transcode { get; set; } - public bool IsVideo { get; set; } + public DlnaProfileType MediaType { get; set; } - public bool IsAudio { get; set; } - - public string FileFormat { get; set; } + public string Container { get; set; } public string MimeType { get; set; } @@ -30,72 +25,5 @@ namespace MediaBrowser.Dlna.PlayTo public string Didl { get; set; } public long StartPositionTicks { get; set; } - - public static PlaylistItem Create(BaseItem item, DeviceProfile profile) - { - var playlistItem = new PlaylistItem - { - ItemId = item.Id.ToString() - }; - - DlnaProfileType profileType; - if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - playlistItem.IsVideo = true; - profileType = DlnaProfileType.Video; - } - else - { - playlistItem.IsAudio = true; - profileType = DlnaProfileType.Audio; - } - - var path = item.Path; - - var directPlay = profile.DirectPlayProfiles.FirstOrDefault(i => i.Type == profileType && IsSupported(i, path)); - - if (directPlay != null) - { - playlistItem.Transcode = false; - playlistItem.FileFormat = Path.GetExtension(path); - playlistItem.MimeType = directPlay.MimeType; - return playlistItem; - } - - var transcodingProfile = profile.TranscodingProfiles.FirstOrDefault(i => i.Type == profileType && IsSupported(profile, i, path)); - - if (transcodingProfile != null) - { - playlistItem.Transcode = true; - //Just to make sure we have a "." for the url, remove it in case a user adds it or not - playlistItem.FileFormat = "." + transcodingProfile.Container.TrimStart('.'); - - playlistItem.MimeType = transcodingProfile.MimeType; - } - - return playlistItem; - } - - private static bool IsSupported(DirectPlayProfile profile, string path) - { - var mediaContainer = Path.GetExtension(path); - - if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - - // Placeholder for future conditions - - // TODO: Support codec list as additional restriction - - return true; - } - - private static bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, string path) - { - // Placeholder for future conditions - return true; - } } }
\ No newline at end of file diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs new file mode 100644 index 000000000..6817c4eaa --- /dev/null +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs @@ -0,0 +1,375 @@ +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using System; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Dlna.PlayTo +{ + public class PlaylistItemFactory + { + private readonly IItemRepository _itemRepo; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public PlaylistItemFactory(IItemRepository itemRepo) + { + _itemRepo = itemRepo; + } + + public PlaylistItem Create(Audio item, DeviceProfile profile) + { + var playlistItem = new PlaylistItem + { + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Audio + }; + + var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery + { + ItemId = item.Id, + Type = MediaStreamType.Audio + }); + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream)); + + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.Container = Path.GetExtension(item.Path); + + return playlistItem; + } + + var transcodingProfile = profile.TranscodingProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + + if (transcodingProfile != null) + { + playlistItem.Transcode = true; + + playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); + } + + AttachMediaProfile(playlistItem, profile); + + return playlistItem; + } + + public PlaylistItem Create(Photo item, DeviceProfile profile) + { + var playlistItem = new PlaylistItem + { + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Photo + }; + + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item)); + + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.Container = Path.GetExtension(item.Path); + + return playlistItem; + } + + var transcodingProfile = profile.TranscodingProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + + if (transcodingProfile != null) + { + playlistItem.Transcode = true; + + playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); + } + + AttachMediaProfile(playlistItem, profile); + + return playlistItem; + } + + public PlaylistItem Create(Video item, DeviceProfile profile) + { + var playlistItem = new PlaylistItem + { + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Video + }; + + var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery + { + ItemId = item.Id + + }).ToList(); + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, videoStream, audioStream)); + + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.Container = Path.GetExtension(item.Path); + + return playlistItem; + } + + var transcodingProfile = profile.TranscodingProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + + if (transcodingProfile != null) + { + playlistItem.Transcode = true; + playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); + } + + AttachMediaProfile(playlistItem, profile); + + return playlistItem; + } + + private void AttachMediaProfile(PlaylistItem item, DeviceProfile profile) + { + var mediaProfile = GetMediaProfile(item, profile); + + if (mediaProfile != null) + { + item.MimeType = (mediaProfile.MimeType ?? string.Empty).Split('/').LastOrDefault(); + + // TODO: Org_pn? + } + } + + private MediaProfile GetMediaProfile(PlaylistItem item, DeviceProfile profile) + { + return profile.MediaProfiles.FirstOrDefault(i => + { + if (i.Type == item.MediaType) + { + if (string.Equals(item.Container.TrimStart('.'), i.Container.TrimStart('.'), StringComparison.OrdinalIgnoreCase)) + { + // TODO: Enforce codecs + return true; + } + } + + return false; + }); + } + + private bool IsSupported(DirectPlayProfile profile, Photo item) + { + var mediaPath = item.Path; + + if (profile.Containers.Length > 0) + { + // Check container type + var mediaContainer = Path.GetExtension(mediaPath); + if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + } + + // Check additional conditions + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, null, null))) + { + return false; + } + + return true; + } + + private bool IsSupported(DirectPlayProfile profile, Audio item, MediaStream audioStream) + { + var mediaPath = item.Path; + + if (profile.Containers.Length > 0) + { + // Check container type + var mediaContainer = Path.GetExtension(mediaPath); + if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + } + + // Check additional conditions + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, null, audioStream))) + { + return false; + } + + return true; + } + + private bool IsSupported(DirectPlayProfile profile, Video item, MediaStream videoStream, MediaStream audioStream) + { + if (item.VideoType != VideoType.VideoFile) + { + return false; + } + + var mediaPath = item.Path; + + if (profile.Containers.Length > 0) + { + // Check container type + var mediaContainer = Path.GetExtension(mediaPath); + if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + } + + // Check video codec + var videoCodecs = profile.GetVideoCodecs(); + if (videoCodecs.Count > 0) + { + var videoCodec = videoStream == null ? null : videoStream.Codec; + if (string.IsNullOrWhiteSpace(videoCodec) || !videoCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + } + + var audioCodecs = profile.GetAudioCodecs(); + if (audioCodecs.Count > 0) + { + // Check audio codecs + var audioCodec = audioStream == null ? null : audioStream.Codec; + if (string.IsNullOrWhiteSpace(audioCodec) || !audioCodecs.Contains(audioCodec, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + } + + // Check additional conditions + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream))) + { + return false; + } + + return true; + } + + private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Audio item) + { + // Placeholder for future conditions + return true; + } + + private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Photo item) + { + // Placeholder for future conditions + return true; + } + + private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Video item) + { + // Placeholder for future conditions + return true; + } + + /// <summary> + /// Determines whether [is condition satisfied] [the specified condition]. + /// </summary> + /// <param name="condition">The condition.</param> + /// <param name="mediaPath">The media path.</param> + /// <param name="videoStream">The video stream.</param> + /// <param name="audioStream">The audio stream.</param> + /// <returns><c>true</c> if [is condition satisfied] [the specified condition]; otherwise, <c>false</c>.</returns> + /// <exception cref="System.InvalidOperationException">Unexpected ProfileConditionType</exception> + private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) + { + var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream); + + if (actualValue.HasValue) + { + long expected; + if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected)) + { + switch (condition.Condition) + { + case ProfileConditionType.Equals: + return actualValue.Value == expected; + case ProfileConditionType.GreaterThanEqual: + return actualValue.Value >= expected; + case ProfileConditionType.LessThanEqual: + return actualValue.Value <= expected; + case ProfileConditionType.NotEquals: + return actualValue.Value != expected; + default: + throw new InvalidOperationException("Unexpected ProfileConditionType"); + } + } + } + + return false; + } + + /// <summary> + /// Gets the condition value. + /// </summary> + /// <param name="condition">The condition.</param> + /// <param name="mediaPath">The media path.</param> + /// <param name="videoStream">The video stream.</param> + /// <param name="audioStream">The audio stream.</param> + /// <returns>System.Nullable{System.Int64}.</returns> + /// <exception cref="System.InvalidOperationException">Unexpected Property</exception> + private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) + { + switch (condition.Property) + { + case ProfileConditionValue.AudioBitrate: + return audioStream == null ? null : audioStream.BitRate; + case ProfileConditionValue.AudioChannels: + return audioStream == null ? null : audioStream.Channels; + case ProfileConditionValue.Filesize: + return new FileInfo(mediaPath).Length; + case ProfileConditionValue.VideoBitrate: + return videoStream == null ? null : videoStream.BitRate; + case ProfileConditionValue.VideoFramerate: + return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate)); + case ProfileConditionValue.Height: + return videoStream == null ? null : videoStream.Height; + case ProfileConditionValue.Width: + return videoStream == null ? null : videoStream.Width; + case ProfileConditionValue.VideoLevel: + return videoStream == null ? null : ConvertToLong(videoStream.Level); + default: + throw new InvalidOperationException("Unexpected Property"); + } + } + + /// <summary> + /// Converts to long. + /// </summary> + /// <param name="val">The value.</param> + /// <returns>System.Nullable{System.Int64}.</returns> + private long? ConvertToLong(float? val) + { + return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null; + } + + /// <summary> + /// Converts to long. + /// </summary> + /// <param name="val">The value.</param> + /// <returns>System.Nullable{System.Int64}.</returns> + private long? ConvertToLong(double? val) + { + return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null; + } + } +} diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs index 97d208aa3..cb2b72a03 100644 --- a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs +++ b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs @@ -24,43 +24,43 @@ namespace MediaBrowser.Dlna.PlayTo var contentFeatures = string.Empty; - if (string.Equals(item.FileFormat, "mp3", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(item.Container, "mp3", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=MP3"; } - else if (string.Equals(item.FileFormat, "wma", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "wma", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=WMABASE"; } - else if (string.Equals(item.FileFormat, "wmw", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "wmw", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=WMVMED_BASE"; } - else if (string.Equals(item.FileFormat, "asf", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "asf", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=WMVMED_BASE"; } - else if (string.Equals(item.FileFormat, "avi", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "avi", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=AVI"; } - else if (string.Equals(item.FileFormat, "mkv", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "mkv", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=MATROSKA"; } - else if (string.Equals(item.FileFormat, "mp4", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "mp4", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC"; } - else if (string.Equals(item.FileFormat, "mpeg", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "mpeg", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; } - else if (string.Equals(item.FileFormat, "ts", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(item.Container, "ts", StringComparison.OrdinalIgnoreCase)) { contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; } - else if (item.IsVideo) + else if (item.MediaType == Controller.Dlna.DlnaProfileType.Video) { //Default to AVI for video contentFeatures = "DLNA.ORG_PN=AVI"; @@ -85,7 +85,7 @@ namespace MediaBrowser.Dlna.PlayTo internal static string GetAudioUrl(PlaylistItem item, string serverAddress) { if (!item.Transcode) - return string.Format("{0}/audio/{1}/stream{2}?Static=True", serverAddress, item.ItemId, item.FileFormat); + return string.Format("{0}/audio/{1}/stream{2}?Static=True", serverAddress, item.ItemId, item.Container); return string.Format("{0}/audio/{1}/stream.mp3?AudioCodec=Mp3", serverAddress, item.ItemId); } @@ -108,7 +108,7 @@ namespace MediaBrowser.Dlna.PlayTo if (!item.Transcode) { dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, !item.Transcode, null, null, null, null, null, null, null, null, null, null, item.MimeType); - return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.FileFormat, dlnaCommand); + return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.Container, dlnaCommand); } var videostream = streams.Where(m => m.Type == MediaStreamType.Video).OrderBy(m => m.IsDefault).FirstOrDefault(); var audiostream = streams.Where(m => m.Type == MediaStreamType.Audio).OrderBy(m => m.IsDefault).FirstOrDefault(); @@ -129,7 +129,7 @@ namespace MediaBrowser.Dlna.PlayTo } dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, !item.Transcode, videoCodec, audioCodec, null, null, videoBitrate, audioChannels, audioBitrate, item.StartPositionTicks, "baseline", "3", item.MimeType); - return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.FileFormat, dlnaCommand); + return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.Container, dlnaCommand); } /// <summary> diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index c46e355df..960b0f635 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -428,6 +428,9 @@ <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs"> <Link>Session\MessageCommand.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Session\PlaybackReports.cs"> + <Link>Session\PlaybackReports.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs"> <Link>Session\PlayRequest.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 7ef891209..b010ad9c9 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -415,6 +415,9 @@ <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs"> <Link>Session\MessageCommand.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Session\PlaybackReports.cs"> + <Link>Session\PlaybackReports.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs"> <Link>Session\PlayRequest.cs</Link> </Compile> diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 46d3dcc3f..91ac67a1f 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -538,35 +538,26 @@ namespace MediaBrowser.Model.ApiClient /// <summary> /// Reports to the server that the user has begun playing an item /// </summary> - /// <param name="itemId">The item id.</param> - /// <param name="userId">The user id.</param> - /// <param name="isSeekable">if set to <c>true</c> [is seekable].</param> - /// <param name="queueableMediaTypes">The list of media types that the client is capable of queuing onto the playlist. See MediaType class.</param> + /// <param name="info">The information.</param> /// <returns>Task{UserItemDataDto}.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task ReportPlaybackStartAsync(string itemId, string userId, bool isSeekable, List<string> queueableMediaTypes); + Task ReportPlaybackStartAsync(PlaybackStartInfo info); /// <summary> /// Reports playback progress to the server /// </summary> - /// <param name="itemId">The item id.</param> - /// <param name="userId">The user id.</param> - /// <param name="positionTicks">The position ticks.</param> - /// <param name="isPaused">if set to <c>true</c> [is paused].</param> - /// <param name="isMuted">if set to <c>true</c> [is muted].</param> + /// <param name="info">The information.</param> /// <returns>Task{UserItemDataDto}.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task ReportPlaybackProgressAsync(string itemId, string userId, long? positionTicks, bool isPaused, bool isMuted); + Task ReportPlaybackProgressAsync(PlaybackProgressInfo info); /// <summary> /// Reports to the server that the user has stopped playing an item /// </summary> - /// <param name="itemId">The item id.</param> - /// <param name="userId">The user id.</param> - /// <param name="positionTicks">The position ticks.</param> + /// <param name="info">The information.</param> /// <returns>Task{UserItemDataDto}.</returns> /// <exception cref="ArgumentNullException">itemId</exception> - Task ReportPlaybackStoppedAsync(string itemId, string userId, long? positionTicks); + Task ReportPlaybackStoppedAsync(PlaybackStopInfo info); /// <summary> /// Instructs antoher client to browse to a library item. diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index c2765754e..1f304112f 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -147,7 +147,7 @@ namespace MediaBrowser.Model.Configuration /// different directories and files. /// </summary> /// <value>The file watcher delay.</value> - public int RealtimeWatcherDelay { get; set; } + public int RealtimeMonitorDelay { get; set; } /// <summary> /// Gets or sets a value indicating whether [enable dashboard response caching]. @@ -239,7 +239,7 @@ namespace MediaBrowser.Model.Configuration MaxResumePct = 90; MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds); - RealtimeWatcherDelay = 20; + RealtimeMonitorDelay = 30; RecentItemDays = 10; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 379e757bb..780f936be 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the media versions. /// </summary> /// <value>The media versions.</value> - public List<MediaVersionInfo> MediaVersions { get; set; } + public List<MediaSourceInfo> MediaSources { get; set; } /// <summary> /// Gets or sets the critic rating. @@ -500,7 +500,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The part count.</value> public int? PartCount { get; set; } - public int? MediaVersionCount { get; set; } + public int? MediaSourceCount { get; set; } /// <summary> /// Determines whether the specified type is type. diff --git a/MediaBrowser.Model/Dto/MediaVersionInfo.cs b/MediaBrowser.Model/Dto/MediaVersionInfo.cs index 3da5e2b8d..ddb340787 100644 --- a/MediaBrowser.Model/Dto/MediaVersionInfo.cs +++ b/MediaBrowser.Model/Dto/MediaVersionInfo.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; namespace MediaBrowser.Model.Dto { - public class MediaVersionInfo + public class MediaSourceInfo { - public string ItemId { get; set; } + public string Id { get; set; } public string Path { get; set; } @@ -22,9 +22,5 @@ namespace MediaBrowser.Model.Dto public Video3DFormat? Video3DFormat { get; set; } public List<MediaStream> MediaStreams { get; set; } - - public List<ChapterInfoDto> Chapters { get; set; } - - public bool IsPrimaryVersion { get; set; } } } diff --git a/MediaBrowser.Model/Entities/BaseItemInfo.cs b/MediaBrowser.Model/Entities/BaseItemInfo.cs index b704bdb57..16a08d693 100644 --- a/MediaBrowser.Model/Entities/BaseItemInfo.cs +++ b/MediaBrowser.Model/Entities/BaseItemInfo.cs @@ -69,6 +69,12 @@ namespace MediaBrowser.Model.Entities /// </summary> /// <value>The thumb item identifier.</value> public string BackdropItemId { get; set; } + + /// <summary> + /// Gets or sets the media version identifier. + /// </summary> + /// <value>The media version identifier.</value> + public string MediaSourceId { get; set; } /// <summary> /// Gets a value indicating whether this instance has primary image. diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 6a7e723a7..5e9b97939 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -132,6 +132,7 @@ <Compile Include="Search\SearchQuery.cs" /> <Compile Include="Session\BrowseRequest.cs" /> <Compile Include="Session\MessageCommand.cs" /> + <Compile Include="Session\PlaybackReports.cs" /> <Compile Include="Session\PlayRequest.cs" /> <Compile Include="Session\PlaystateCommand.cs" /> <Compile Include="Logging\ILogManager.cs" /> diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 65509fd6d..398be3c7b 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.Model.Querying /// <summary> /// The media versions /// </summary> - MediaVersions, + MediaSources, /// <summary> /// The metadata settings diff --git a/MediaBrowser.Model/Session/PlaybackReports.cs b/MediaBrowser.Model/Session/PlaybackReports.cs new file mode 100644 index 000000000..75dd3346c --- /dev/null +++ b/MediaBrowser.Model/Session/PlaybackReports.cs @@ -0,0 +1,56 @@ + +namespace MediaBrowser.Model.Session +{ + /// <summary> + /// Class PlaybackStartInfo. + /// </summary> + public class PlaybackStartInfo + { + public string UserId { get; set; } + + public string ItemId { get; set; } + + public string MediaSourceId { get; set; } + + public bool IsSeekable { get; set; } + + public string[] QueueableMediaTypes { get; set; } + + public PlaybackStartInfo() + { + QueueableMediaTypes = new string[] { }; + } + } + + /// <summary> + /// Class PlaybackProgressInfo. + /// </summary> + public class PlaybackProgressInfo + { + public string UserId { get; set; } + + public string ItemId { get; set; } + + public string MediaSourceId { get; set; } + + public long? PositionTicks { get; set; } + + public bool IsPaused { get; set; } + + public bool IsMuted { get; set; } + } + + /// <summary> + /// Class PlaybackStopInfo. + /// </summary> + public class PlaybackStopInfo + { + public string UserId { get; set; } + + public string ItemId { get; set; } + + public string MediaSourceId { get; set; } + + public long? PositionTicks { get; set; } + } +} diff --git a/MediaBrowser.Model/Session/SessionCapabilities.cs b/MediaBrowser.Model/Session/SessionCapabilities.cs index 731ebaccc..7b3b04ce9 100644 --- a/MediaBrowser.Model/Session/SessionCapabilities.cs +++ b/MediaBrowser.Model/Session/SessionCapabilities.cs @@ -7,6 +7,10 @@ namespace MediaBrowser.Model.Session public bool SupportsFullscreenToggle { get; set; } + public bool SupportsOsdToggle { get; set; } + + public bool SupportsNavigationControl { get; set; } + public SessionCapabilities() { PlayableMediaTypes = new string[] {}; diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs index d44bbeaeb..09a8375d8 100644 --- a/MediaBrowser.Model/Session/SessionInfoDto.cs +++ b/MediaBrowser.Model/Session/SessionInfoDto.cs @@ -1,8 +1,8 @@ -using System.Diagnostics; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; namespace MediaBrowser.Model.Session { @@ -147,6 +147,18 @@ namespace MediaBrowser.Model.Session /// <value><c>true</c> if [supports remote control]; otherwise, <c>false</c>.</value> public bool SupportsRemoteControl { get; set; } + /// <summary> + /// Gets or sets a value indicating whether [supports osd toggle]. + /// </summary> + /// <value><c>true</c> if [supports osd toggle]; otherwise, <c>false</c>.</value> + public bool SupportsOsdToggle { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether [supports navigation commands]. + /// </summary> + /// <value><c>true</c> if [supports navigation commands]; otherwise, <c>false</c>.</value> + public bool SupportsNavigationControl { get; set; } + public event PropertyChangedEventHandler PropertyChanged; public SessionInfoDto() diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 03fe5c802..391ab7cfd 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -213,7 +213,7 @@ namespace MediaBrowser.Providers.Savers builder.Append("<Added>" + SecurityElement.Escape(item.DateCreated.ToLocalTime().ToString("G")) + "</Added>"); - builder.Append("<LockData>" + item.DontFetchMeta.ToString().ToLower() + "</LockData>"); + builder.Append("<LockData>" + item.IsLocked.ToString().ToLower() + "</LockData>"); if (item.LockedFields.Count > 0) { diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index c94cdda84..66a9284d7 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -127,7 +127,7 @@ namespace MediaBrowser.Server.Implementations.Dto public BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, User user = null) where T : BaseItem, IItemByName { - var libraryItems = user != null ? user.RootFolder.GetRecursiveChildren(user) : + var libraryItems = user != null ? user.RootFolder.GetRecursiveChildren(user) : _libraryManager.RootFolder.RecursiveChildren; return GetItemByNameDto(item, fields, item.GetTaggedItems(libraryItems).ToList(), user); @@ -267,12 +267,14 @@ namespace MediaBrowser.Server.Implementations.Dto PlayableMediaTypes = session.PlayableMediaTypes, RemoteEndPoint = session.RemoteEndPoint, AdditionalUsers = session.AdditionalUsers, - SupportsFullscreenToggle = session.SupportsFullscreenToggle + SupportsFullscreenToggle = session.SupportsFullscreenToggle, + SupportsNavigationControl = session.SupportsNavigationControl, + SupportsOsdToggle = session.SupportsOsdToggle }; if (session.NowPlayingItem != null) { - dto.NowPlayingItem = GetBaseItemInfo(session.NowPlayingItem); + dto.NowPlayingItem = GetNowPlayingInfo(session.NowPlayingItem, session.NowPlayingMediaSourceId, session.NowPlayingRunTimeTicks); } if (session.UserId.HasValue) @@ -288,9 +290,11 @@ namespace MediaBrowser.Server.Implementations.Dto /// Converts a BaseItem to a BaseItemInfo /// </summary> /// <param name="item">The item.</param> + /// <param name="mediaSourceId">The media version identifier.</param> + /// <param name="nowPlayingRuntimeTicks">The now playing runtime ticks.</param> /// <returns>BaseItemInfo.</returns> /// <exception cref="System.ArgumentNullException">item</exception> - public BaseItemInfo GetBaseItemInfo(BaseItem item) + private BaseItemInfo GetNowPlayingInfo(BaseItem item, string mediaSourceId, long? nowPlayingRuntimeTicks) { if (item == null) { @@ -303,7 +307,8 @@ namespace MediaBrowser.Server.Implementations.Dto Name = item.Name, MediaType = item.MediaType, Type = item.GetClientTypeName(), - RunTimeTicks = item.RunTimeTicks + RunTimeTicks = nowPlayingRuntimeTicks, + MediaSourceId = mediaSourceId }; info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary); @@ -735,7 +740,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (fields.Contains(ItemFields.Settings)) { dto.LockedFields = item.LockedFields; - dto.LockData = item.DontFetchMeta; + dto.LockData = item.IsLocked; } var hasBudget = item as IHasBudget; @@ -1041,7 +1046,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; } - + // Add audio info var audio = item as Audio; if (audio != null) @@ -1058,8 +1063,8 @@ namespace MediaBrowser.Server.Implementations.Dto dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); } - dto.MediaVersions = GetMediaVersions(audio); - dto.MediaVersionCount = 1; + dto.MediaSources = GetMediaSources(audio); + dto.MediaSourceCount = 1; } var album = item as MusicAlbum; @@ -1090,22 +1095,20 @@ namespace MediaBrowser.Server.Implementations.Dto dto.IsHD = video.IsHD; dto.PartCount = video.AdditionalPartIds.Count + 1; - dto.MediaVersionCount = video.AlternateVersionCount + 1; + dto.MediaSourceCount = video.MediaSourceCount; - if (fields.Contains(ItemFields.MediaVersions)) + if (fields.Contains(ItemFields.MediaSources)) { - dto.MediaVersions = GetMediaVersions(video); + dto.MediaSources = GetMediaSources(video); } if (fields.Contains(ItemFields.Chapters)) { List<ChapterInfoDto> chapters; - if (dto.MediaVersions != null && dto.MediaVersions.Count > 0) + if (dto.MediaSources != null && dto.MediaSources.Count > 0) { - chapters = dto.MediaVersions.Where(i => i.IsPrimaryVersion) - .SelectMany(i => i.Chapters) - .ToList(); + chapters = _itemRepo.GetChapters(item.Id).Select(c => GetChapterInfoDto(c, item)).ToList(); } else { @@ -1127,9 +1130,9 @@ namespace MediaBrowser.Server.Implementations.Dto { List<MediaStream> mediaStreams; - if (dto.MediaVersions != null && dto.MediaVersions.Count > 0) + if (dto.MediaSources != null && dto.MediaSources.Count > 0) { - mediaStreams = dto.MediaVersions.Where(i => i.IsPrimaryVersion) + mediaStreams = dto.MediaSources.Where(i => new Guid(i.Id) == item.Id) .SelectMany(i => i.MediaStreams) .ToList(); } @@ -1264,11 +1267,11 @@ namespace MediaBrowser.Server.Implementations.Dto } } - private List<MediaVersionInfo> GetMediaVersions(Video item) + private List<MediaSourceInfo> GetMediaSources(Video item) { - var result = item.GetAlternateVersions().Select(i => GetVersionInfo(i, false)).ToList(); + var result = item.GetAlternateVersions().Select(GetVersionInfo).ToList(); - result.Add(GetVersionInfo(item, true)); + result.Add(GetVersionInfo(item)); return result.OrderBy(i => { @@ -1286,49 +1289,47 @@ namespace MediaBrowser.Server.Implementations.Dto return stream == null || stream.Width == null ? 0 : stream.Width.Value; }) - .ThenBy(i => i.IsPrimaryVersion ? 0 : 1) .ToList(); } - private List<MediaVersionInfo> GetMediaVersions(Audio item) + private List<MediaSourceInfo> GetMediaSources(Audio item) { - var result = new List<MediaVersionInfo>(); - - result.Add(GetVersionInfo(item, true)); + var result = new List<MediaSourceInfo> + { + GetVersionInfo(item, true) + }; return result; } - private MediaVersionInfo GetVersionInfo(Video i, bool isPrimary) + private MediaSourceInfo GetVersionInfo(Video i) { - return new MediaVersionInfo - { - Chapters = _itemRepo.GetChapters(i.Id).Select(c => GetChapterInfoDto(c, i)).ToList(), + var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(); - ItemId = i.Id.ToString("N"), + return new MediaSourceInfo + { + Id = i.Id.ToString("N"), IsoType = i.IsoType, LocationType = i.LocationType, - MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(), - Name = GetAlternateVersionName(i), + MediaStreams = mediaStreams, + Name = GetMediaSourceName(i, mediaStreams), Path = GetMappedPath(i), RunTimeTicks = i.RunTimeTicks, Video3DFormat = i.Video3DFormat, - VideoType = i.VideoType, - IsPrimaryVersion = isPrimary + VideoType = i.VideoType }; } - private MediaVersionInfo GetVersionInfo(Audio i, bool isPrimary) + private MediaSourceInfo GetVersionInfo(Audio i, bool isPrimary) { - return new MediaVersionInfo + return new MediaSourceInfo { - ItemId = i.Id.ToString("N"), + Id = i.Id.ToString("N"), LocationType = i.LocationType, MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(), Name = i.Name, Path = GetMappedPath(i), - RunTimeTicks = i.RunTimeTicks, - IsPrimaryVersion = isPrimary + RunTimeTicks = i.RunTimeTicks }; } @@ -1351,32 +1352,29 @@ namespace MediaBrowser.Server.Implementations.Dto return path; } - private string GetAlternateVersionName(Video video) + private string GetMediaSourceName(Video video, List<MediaStream> mediaStreams) { - var name = ""; + var terms = new List<string>(); - var videoStream = video.GetDefaultVideoStream(); + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); if (video.Video3DFormat.HasValue) { - name = "3D " + name; - name = name.Trim(); + terms.Add("3D"); } if (video.VideoType == VideoType.BluRay) { - name = name + " " + "Bluray"; - name = name.Trim(); + terms.Add("Bluray"); } else if (video.VideoType == VideoType.Dvd) { - name = name + " " + "DVD"; - name = name.Trim(); + terms.Add("DVD"); } else if (video.VideoType == VideoType.HdDvd) { - name = name + " " + "HD-DVD"; - name = name.Trim(); + terms.Add("HD-DVD"); } else if (video.VideoType == VideoType.Iso) { @@ -1384,18 +1382,17 @@ namespace MediaBrowser.Server.Implementations.Dto { if (video.IsoType.Value == IsoType.BluRay) { - name = name + " " + "Bluray"; + terms.Add("Bluray"); } else if (video.IsoType.Value == IsoType.Dvd) { - name = name + " " + "DVD"; + terms.Add("DVD"); } } else { - name = name + " " + "ISO"; + terms.Add("ISO"); } - name = name.Trim(); } if (videoStream != null) @@ -1404,44 +1401,45 @@ namespace MediaBrowser.Server.Implementations.Dto { if (videoStream.Width.Value >= 3800) { - name = name + " " + "4K"; - name = name.Trim(); + terms.Add("4K"); } else if (videoStream.Width.Value >= 1900) { - name = name + " " + "1080P"; - name = name.Trim(); + terms.Add("1080P"); } else if (videoStream.Width.Value >= 1270) { - name = name + " " + "720P"; - name = name.Trim(); + terms.Add("720P"); } else if (videoStream.Width.Value >= 700) { - name = name + " " + "480p"; - name = name.Trim(); + terms.Add("480P"); } else { - name = name + " " + "SD"; - name = name.Trim(); + terms.Add("SD"); } } } if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec)) { - name = name + " " + videoStream.Codec.ToUpper(); - name = name.Trim(); + terms.Add(videoStream.Codec.ToUpper()); } - if (string.IsNullOrWhiteSpace(name)) + if (audioStream != null) { - return video.Name; + var audioCodec = string.Equals(audioStream.Codec, "dca", StringComparison.OrdinalIgnoreCase) + ? audioStream.Profile + : audioStream.Codec; + + if (!string.IsNullOrEmpty(audioCodec)) + { + terms.Add(audioCodec.ToUpper()); + } } - return name; + return string.Join("/", terms.ToArray()); } private string GetMappedPath(string path) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 5d326f1ca..d44811886 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -180,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization result.StatusMessage = string.Empty; return; } - + if (fileExists || otherDuplicatePaths.Count > 0) { result.Status = FileSortingStatus.SkippedExisting; @@ -453,24 +453,22 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private bool IsSameEpisode(string sourcePath, string newPath) { + var sourceFileInfo = new FileInfo(sourcePath); + var destinationFileInfo = new FileInfo(newPath); - FileInfo sourceFileInfo = new FileInfo(sourcePath); - FileInfo destinationFileInfo = new FileInfo(newPath); - - try - { - if (sourceFileInfo.Length == destinationFileInfo.Length) - { - return true; - } - } - catch (FileNotFoundException) + try + { + if (sourceFileInfo.Length == destinationFileInfo.Length) { - return false; + return true; } - + } + catch (FileNotFoundException) + { return false; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs b/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs index 23d0363cb..7f936791a 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs @@ -67,6 +67,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("!", " ") .Replace("(", " ") .Replace(")", " ") + .Replace(":", " ") .Replace(",", " ") .Replace("-", " ") .Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase) diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 65bbb35ba..0a0b3f4bc 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -427,11 +427,11 @@ namespace MediaBrowser.Server.Implementations.IO { if (_updateTimer == null) { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeMonitorDelay), TimeSpan.FromMilliseconds(-1)); } else { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeMonitorDelay), TimeSpan.FromMilliseconds(-1)); } } } diff --git a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs index ad2db5abb..3097495e3 100644 --- a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs +++ b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Library EnsureName(item, args); item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || - item.Parents.Any(i => i.DontFetchMeta); + item.Parents.Any(i => i.IsLocked); // Make sure DateCreated and DateModified have values EntityResolutionHelper.EnsureDates(fileSystem, item, args, true); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index d7b2b1321..13a1e8f16 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -190,7 +190,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="directoryService">The directory service.</param> /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param> /// <returns>Movie.</returns> - private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems, bool supportsAlternateVersions) + private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems, bool supportsMultipleSources) where T : Video, new() { var movies = new List<T>(); @@ -262,9 +262,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return result; } } - if (supportsAlternateVersions) + if (supportsMultipleSources) { - var result = GetMovieWithAlternateVersions(movies); + var result = GetMovieWithMultipleSources(movies); if (result != null) { @@ -393,7 +393,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return null; } - private T GetMovieWithAlternateVersions<T>(IEnumerable<T> movies) + private T GetMovieWithMultipleSources<T>(IEnumerable<T> movies) where T : Video, new() { var sortedMovies = movies.OrderBy(i => i.Path).ToList(); diff --git a/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs b/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs index 71f70421a..53f28e7ae 100644 --- a/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs +++ b/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs @@ -1,8 +1,10 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using System; +using System.Collections.Generic; namespace MediaBrowser.Server.Implementations.Roku { @@ -23,6 +25,9 @@ namespace MediaBrowser.Server.Implementations.Roku { if (string.Equals(session.Client, "roku", StringComparison.OrdinalIgnoreCase)) { + session.PlayableMediaTypes = new List<string> { MediaType.Video, MediaType.Audio }; + session.SupportsFullscreenToggle = false; + return new RokuSessionController(_httpClient, _json, _appHost, session); } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 1fb5af127..9d405a175 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -218,15 +218,29 @@ namespace MediaBrowser.Server.Implementations.Session /// </summary> /// <param name="session">The session.</param> /// <param name="item">The item.</param> + /// <param name="mediaSourceId">The media version identifier.</param> /// <param name="isPaused">if set to <c>true</c> [is paused].</param> + /// <param name="isMuted">if set to <c>true</c> [is muted].</param> /// <param name="currentPositionTicks">The current position ticks.</param> - private void UpdateNowPlayingItem(SessionInfo session, BaseItem item, bool isPaused, bool isMuted, long? currentPositionTicks = null) + private void UpdateNowPlayingItem(SessionInfo session, BaseItem item, string mediaSourceId, bool isPaused, bool isMuted, long? currentPositionTicks = null) { session.IsMuted = isMuted; session.IsPaused = isPaused; session.NowPlayingPositionTicks = currentPositionTicks; session.NowPlayingItem = item; session.LastActivityDate = DateTime.UtcNow; + session.NowPlayingMediaSourceId = mediaSourceId; + + if (string.IsNullOrWhiteSpace(mediaSourceId)) + { + session.NowPlayingRunTimeTicks = item.RunTimeTicks; + } + else + { + var version = _libraryManager.GetItemById(new Guid(mediaSourceId)); + + session.NowPlayingRunTimeTicks = version.RunTimeTicks; + } } /// <summary> @@ -246,6 +260,8 @@ namespace MediaBrowser.Server.Implementations.Session session.NowPlayingItem = null; session.NowPlayingPositionTicks = null; session.IsPaused = false; + session.NowPlayingRunTimeTicks = null; + session.NowPlayingMediaSourceId = null; } } @@ -352,7 +368,9 @@ namespace MediaBrowser.Server.Implementations.Session var item = info.Item; - UpdateNowPlayingItem(session, item, false, false); + var mediaSourceId = GetMediaSourceId(item, info.MediaSourceId); + + UpdateNowPlayingItem(session, item, mediaSourceId, false, false); session.CanSeek = info.CanSeek; session.QueueableMediaTypes = info.QueueableMediaTypes; @@ -371,7 +389,8 @@ namespace MediaBrowser.Server.Implementations.Session EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs { Item = item, - Users = users + Users = users, + MediaSourceId = info.MediaSourceId }, _logger); } @@ -405,7 +424,7 @@ namespace MediaBrowser.Server.Implementations.Session /// <returns>Task.</returns> /// <exception cref="System.ArgumentNullException"></exception> /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception> - public async Task OnPlaybackProgress(PlaybackProgressInfo info) + public async Task OnPlaybackProgress(Controller.Session.PlaybackProgressInfo info) { if (info == null) { @@ -419,7 +438,9 @@ namespace MediaBrowser.Server.Implementations.Session var session = Sessions.First(i => i.Id.Equals(info.SessionId)); - UpdateNowPlayingItem(session, info.Item, info.IsPaused, info.IsMuted, info.PositionTicks); + var mediaSourceId = GetMediaSourceId(info.Item, info.MediaSourceId); + + UpdateNowPlayingItem(session, info.Item, mediaSourceId, info.IsPaused, info.IsMuted, info.PositionTicks); var key = info.Item.GetUserDataKey(); @@ -434,7 +455,8 @@ namespace MediaBrowser.Server.Implementations.Session { Item = info.Item, Users = users, - PlaybackPositionTicks = info.PositionTicks + PlaybackPositionTicks = info.PositionTicks, + MediaSourceId = mediaSourceId }, _logger); } @@ -458,7 +480,7 @@ namespace MediaBrowser.Server.Implementations.Session /// <returns>Task.</returns> /// <exception cref="System.ArgumentNullException">info</exception> /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception> - public async Task OnPlaybackStopped(PlaybackStopInfo info) + public async Task OnPlaybackStopped(Controller.Session.PlaybackStopInfo info) { if (info == null) { @@ -494,16 +516,32 @@ namespace MediaBrowser.Server.Implementations.Session playedToCompletion = await OnPlaybackStopped(user.Id, key, info.Item, info.PositionTicks).ConfigureAwait(false); } + var mediaSourceId = GetMediaSourceId(info.Item, info.MediaSourceId); + EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs { Item = info.Item, Users = users, PlaybackPositionTicks = info.PositionTicks, - PlayedToCompletion = playedToCompletion + PlayedToCompletion = playedToCompletion, + MediaSourceId = mediaSourceId }, _logger); } + private string GetMediaSourceId(BaseItem item, string reportedMediaSourceId) + { + if (string.IsNullOrWhiteSpace(reportedMediaSourceId)) + { + if (item is Video || item is Audio) + { + reportedMediaSourceId = item.Id.ToString("N"); + } + } + + return reportedMediaSourceId; + } + private async Task<bool> OnPlaybackStopped(Guid userId, string userDataKey, BaseItem item, long? positionTicks) { var data = _userDataRepository.GetUserData(userId, userDataKey); @@ -899,6 +937,8 @@ namespace MediaBrowser.Server.Implementations.Session session.PlayableMediaTypes = capabilities.PlayableMediaTypes.ToList(); session.SupportsFullscreenToggle = capabilities.SupportsFullscreenToggle; + session.SupportsOsdToggle = capabilities.SupportsOsdToggle; + session.SupportsNavigationControl = capabilities.SupportsNavigationControl; } } }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index e5fbc7d9f..fe32e2328 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -223,6 +223,11 @@ namespace MediaBrowser.Server.Implementations.Session QueueableMediaTypes = queueableMediaTypes.Split(',').ToList() }; + if (vals.Length > 3) + { + info.MediaSourceId = vals[3]; + } + _sessionManager.OnPlaybackStart(info); } } @@ -265,6 +270,11 @@ namespace MediaBrowser.Server.Implementations.Session SessionId = session.Id }; + if (vals.Length > 4) + { + info.MediaSourceId = vals[4]; + } + _sessionManager.OnPlaybackProgress(info); } } @@ -304,6 +314,11 @@ namespace MediaBrowser.Server.Implementations.Session SessionId = session.Id }; + if (vals.Length > 2) + { + info.MediaSourceId = vals[2]; + } + _sessionManager.OnPlaybackStopped(info); } } diff --git a/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs b/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs index 39a68b3f6..b76bf0a9c 100644 --- a/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs +++ b/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; namespace MediaBrowser.Server.Implementations.Sorting diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 97c26ec78..765cae612 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -2192,20 +2192,6 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Gets a list of all available conrete BaseItem types from the server - */ - self.getItemTypes = function (options) { - - var url = self.getUrl("Library/ItemTypes", options); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - /** * Constructs a url for a user image * @param {String} userId * @param {Object} options @@ -3805,7 +3791,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi * @param {String} userId * @param {String} itemId */ - self.reportPlaybackStart = function (userId, itemId, canSeek, queueableMediaTypes) { + self.reportPlaybackStart = function (userId, itemId, mediaSourceId, canSeek, queueableMediaTypes) { if (!userId) { throw new Error("null userId"); @@ -3823,6 +3809,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi var deferred = $.Deferred(); var msg = [itemId, canSeek, queueableMediaTypes]; + + if (mediaSourceId) { + msg.push(mediaSourceId); + } self.sendWebSocketMessage("PlaybackStart", msg.join('|')); @@ -3830,10 +3820,16 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi return deferred.promise(); } - var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, { + var params = { CanSeek: canSeek, QueueableMediaTypes: queueableMediaTypes - }); + }; + + if (mediaSourceId) { + params.mediaSourceId = mediaSourceId; + } + + var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, params); return self.ajax({ type: "POST", @@ -3846,7 +3842,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi * @param {String} userId * @param {String} itemId */ - self.reportPlaybackProgress = function (userId, itemId, positionTicks, isPaused, isMuted) { + self.reportPlaybackProgress = function (userId, itemId, mediaSourceId, positionTicks, isPaused, isMuted) { if (!userId) { throw new Error("null userId"); @@ -3860,7 +3856,12 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi var deferred = $.Deferred(); - var msgData = itemId + "|" + (positionTicks == null ? "" : positionTicks) + "|" + (isPaused == null ? "" : isPaused) + "|" + (isMuted == null ? "" : isMuted); + var msgData = itemId; + + msgData += "|" + (positionTicks == null ? "" : positionTicks); + msgData += "|" + (isPaused == null ? "" : isPaused); + msgData += "|" + (isMuted == null ? "" : isMuted); + msgData += "|" + (mediaSourceId == null ? "" : mediaSourceId); self.sendWebSocketMessage("PlaybackProgress", msgData); @@ -3877,6 +3878,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi params.positionTicks = positionTicks; } + if (mediaSourceId) { + params.mediaSourceId = mediaSourceId; + } + var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId + "/Progress", params); return self.ajax({ @@ -3890,7 +3895,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi * @param {String} userId * @param {String} itemId */ - self.reportPlaybackStopped = function (userId, itemId, positionTicks) { + self.reportPlaybackStopped = function (userId, itemId, mediaSourceId, positionTicks) { if (!userId) { throw new Error("null userId"); @@ -3904,20 +3909,26 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi var deferred = $.Deferred(); - self.sendWebSocketMessage("PlaybackStopped", itemId + "|" + (positionTicks == null ? "" : positionTicks)); + var msg = itemId; + msg += "|" + (positionTicks == null ? "" : positionTicks); + msg += "|" + (mediaSourceId == null ? "" : mediaSourceId); + + self.sendWebSocketMessage("PlaybackStopped", msg); deferred.resolveWith(null, []); return deferred.promise(); } - var params = { - - }; + var params = {}; if (positionTicks) { params.positionTicks = positionTicks; } + if (mediaSourceId) { + params.mediaSourceId = mediaSourceId; + } + var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, params); return self.ajax({ diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 16eec2640..a0dbae3d4 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -84,9 +84,7 @@ </ProjectReference>
</ItemGroup>
<ItemGroup>
- <EmbeddedResource Include="ApiClient.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </EmbeddedResource>
+ <EmbeddedResource Include="ApiClient.js" />
<Content Include="dashboard-ui\advancedserversettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 0bdea523e..7396527bb 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.245" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.247" targetFramework="net45" /> </packages>
\ No newline at end of file |
