Просмотр исходного кода

Merge remote-tracking branch 'upstream/master'

Tim Hobbs 11 лет назад
Родитель
Сommit
543ce24c10
52 измененных файлов с 1254 добавлено и 402 удалено
  1. 1 1
      MediaBrowser.Api/ItemUpdateService.cs
  2. 3 1
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 3 0
      MediaBrowser.Api/Playback/StreamRequest.cs
  4. 15 3
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  5. 2 4
      MediaBrowser.Api/UserLibrary/YearsService.cs
  6. 23 13
      MediaBrowser.Api/VideosService.cs
  7. 56 0
      MediaBrowser.Controller/Dlna/CodecProfile.cs
  8. 7 0
      MediaBrowser.Controller/Dlna/DeviceIdentification.cs
  9. 7 0
      MediaBrowser.Controller/Dlna/DeviceProfile.cs
  10. 16 74
      MediaBrowser.Controller/Dlna/DirectPlayProfile.cs
  11. 26 0
      MediaBrowser.Controller/Dlna/MediaProfile.cs
  12. 0 5
      MediaBrowser.Controller/Dlna/TranscodingProfile.cs
  13. 0 7
      MediaBrowser.Controller/Dto/IDtoService.cs
  14. 2 2
      MediaBrowser.Controller/Entities/Video.cs
  15. 1 0
      MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs
  16. 2 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  17. 6 0
      MediaBrowser.Controller/Session/PlaybackInfo.cs
  18. 6 0
      MediaBrowser.Controller/Session/PlaybackProgressInfo.cs
  19. 6 0
      MediaBrowser.Controller/Session/PlaybackStopInfo.cs
  20. 25 1
      MediaBrowser.Controller/Session/SessionInfo.cs
  21. 322 36
      MediaBrowser.Dlna/DlnaManager.cs
  22. 1 0
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  23. 37 8
      MediaBrowser.Dlna/PlayTo/DlnaController.cs
  24. 4 76
      MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
  25. 375 0
      MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs
  26. 13 13
      MediaBrowser.Dlna/PlayTo/StreamHelper.cs
  27. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  28. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  29. 6 15
      MediaBrowser.Model/ApiClient/IApiClient.cs
  30. 2 2
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  31. 2 2
      MediaBrowser.Model/Dto/BaseItemDto.cs
  32. 2 6
      MediaBrowser.Model/Dto/MediaVersionInfo.cs
  33. 6 0
      MediaBrowser.Model/Entities/BaseItemInfo.cs
  34. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  35. 1 1
      MediaBrowser.Model/Querying/ItemFields.cs
  36. 56 0
      MediaBrowser.Model/Session/PlaybackReports.cs
  37. 4 0
      MediaBrowser.Model/Session/SessionCapabilities.cs
  38. 14 2
      MediaBrowser.Model/Session/SessionInfoDto.cs
  39. 1 1
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  40. 68 70
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  41. 12 14
      MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  42. 1 0
      MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs
  43. 2 2
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  44. 1 1
      MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
  45. 4 4
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  46. 5 0
      MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs
  47. 48 8
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  48. 15 0
      MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
  49. 1 2
      MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs
  50. 35 24
      MediaBrowser.WebDashboard/ApiClient.js
  51. 1 3
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  52. 1 1
      MediaBrowser.WebDashboard/packages.config

+ 1 - 1
MediaBrowser.Api/ItemUpdateService.cs

@@ -94,7 +94,7 @@ namespace MediaBrowser.Api
             var item = _dtoService.GetItemByDtoId(request.ItemId);
             var item = _dtoService.GetItemByDtoId(request.ItemId);
 
 
             var newLockData = request.LockData ?? false;
             var newLockData = request.LockData ?? false;
-            var dontFetchMetaChanged = item.DontFetchMeta != newLockData;
+            var dontFetchMetaChanged = item.IsLocked != newLockData;
 
 
             UpdateItem(request, item);
             UpdateItem(request, item);
 
 

+ 3 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1308,7 +1308,9 @@ namespace MediaBrowser.Api.Playback
                 RequestedUrl = url
                 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)
             if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
             {
             {

+ 3 - 0
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")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
         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")]
         [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; }
         public string DeviceId { get; set; }
 
 

+ 15 - 3
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")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
         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>
         /// <summary>
         /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
         /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
         /// </summary>
         /// </summary>
@@ -277,6 +280,9 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
         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>
         /// <summary>
         /// Gets or sets the position ticks.
         /// Gets or sets the position ticks.
         /// </summary>
         /// </summary>
@@ -312,6 +318,9 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
         public string Id { get; set; }
         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>
         /// <summary>
         /// Gets or sets the position ticks.
         /// Gets or sets the position ticks.
         /// </summary>
         /// </summary>
@@ -736,7 +745,8 @@ namespace MediaBrowser.Api.UserLibrary
                 CanSeek = request.CanSeek,
                 CanSeek = request.CanSeek,
                 Item = item,
                 Item = item,
                 SessionId = GetSession().Id,
                 SessionId = GetSession().Id,
-                QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
+                QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
+                MediaSourceId = request.MediaSourceId
             };
             };
 
 
             _sessionManager.OnPlaybackStart(info);
             _sessionManager.OnPlaybackStart(info);
@@ -758,7 +768,8 @@ namespace MediaBrowser.Api.UserLibrary
                 PositionTicks = request.PositionTicks,
                 PositionTicks = request.PositionTicks,
                 IsMuted = request.IsMuted,
                 IsMuted = request.IsMuted,
                 IsPaused = request.IsPaused,
                 IsPaused = request.IsPaused,
-                SessionId = GetSession().Id
+                SessionId = GetSession().Id,
+                MediaSourceId = request.MediaSourceId
             };
             };
 
 
             var task = _sessionManager.OnPlaybackProgress(info);
             var task = _sessionManager.OnPlaybackProgress(info);
@@ -782,7 +793,8 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 Item = item,
                 Item = item,
                 PositionTicks = request.PositionTicks,
                 PositionTicks = request.PositionTicks,
-                SessionId = session.Id
+                SessionId = session.Id,
+                MediaSourceId = request.MediaSourceId
             };
             };
 
 
             var task = _sessionManager.OnPlaybackStopped(info);
             var task = _sessionManager.OnPlaybackStopped(info);

+ 2 - 4
MediaBrowser.Api/UserLibrary/YearsService.cs

@@ -14,8 +14,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetYears
     /// Class GetYears
     /// </summary>
     /// </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
     public class GetYears : GetItemsByName
     {
     {
     }
     }
@@ -23,8 +22,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetYear
     /// Class GetYear
     /// </summary>
     /// </summary>
-    [Route("/Years/{Year}", "GET")]
-    [Api(Description = "Gets a year")]
+    [Route("/Years/{Year}", "GET", Summary = "Gets a year")]
     public class GetYear : IReturn<BaseItemDto>
     public class GetYear : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>

+ 23 - 13
MediaBrowser.Api/VideosService.cs

@@ -13,8 +13,7 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api
 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>
     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")]
         [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; }
         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")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
         public string Id { get; set; }
         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
     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)]
         [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);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
-        public void Delete(DeleteAlternateVersions request)
+        public void Delete(DeleteAlternateSources request)
         {
         {
             var task = RemoveAlternateVersions(request);
             var task = RemoveAlternateVersions(request);
 
 
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
 
 
-        private async Task RemoveAlternateVersions(DeleteAlternateVersions request)
+        private async Task RemoveAlternateVersions(DeleteAlternateSources request)
         {
         {
             var video = (Video)_dtoService.GetItemByDtoId(request.Id);
             var video = (Video)_dtoService.GetItemByDtoId(request.Id);
 
 
@@ -146,7 +143,7 @@ namespace MediaBrowser.Api
 
 
             var videos = items.Cast<Video>().ToList();
             var videos = items.Cast<Video>().ToList();
 
 
-            var videosWithVersions = videos.Where(i => i.AlternateVersionCount > 0)
+            var videosWithVersions = videos.Where(i => i.MediaSourceCount > 1)
                 .ToList();
                 .ToList();
 
 
             if (videosWithVersions.Count > 1)
             if (videosWithVersions.Count > 1)
@@ -158,14 +155,27 @@ namespace MediaBrowser.Api
 
 
             if (primaryVersion == null)
             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();
                     var stream = i.GetDefaultVideoStream();
 
 
                     return stream == null || stream.Width == null ? 0 : stream.Width.Value;
                     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))
             foreach (var item in videos.Where(i => i.Id != primaryVersion.Id))

+ 56 - 0
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
+    }
+}

+ 7 - 0
MediaBrowser.Controller/Dlna/DeviceIdentification.cs

@@ -67,5 +67,12 @@ namespace MediaBrowser.Controller.Dlna
     {
     {
         public string Name { get; set; }
         public string Name { get; set; }
         public string Value { get; set; }
         public string Value { get; set; }
+        public HeaderMatchType Match { get; set; }
+    }
+
+    public enum HeaderMatchType
+    {
+        Equals = 0,
+        Substring = 1
     }
     }
 }
 }

+ 7 - 0
MediaBrowser.Controller/Dlna/DeviceProfile.cs

@@ -55,10 +55,17 @@ namespace MediaBrowser.Controller.Dlna
 
 
         public string ProtocolInfo { get; set; }
         public string ProtocolInfo { get; set; }
 
 
+        public MediaProfile[] MediaProfiles { get; set; }
+        public CodecProfile[] CodecProfiles { get; set; }
+
+        public int TimelineOffsetSeconds { get; set; }
+
         public DeviceProfile()
         public DeviceProfile()
         {
         {
             DirectPlayProfiles = new DirectPlayProfile[] { };
             DirectPlayProfiles = new DirectPlayProfile[] { };
             TranscodingProfiles = new TranscodingProfile[] { };
             TranscodingProfiles = new TranscodingProfile[] { };
+            MediaProfiles = new MediaProfile[] { };
+            CodecProfiles = new CodecProfile[] { };
         }
         }
     }
     }
 }
 }

+ 16 - 74
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
 namespace MediaBrowser.Controller.Dlna
 {
 {
     public class DirectPlayProfile
     public class DirectPlayProfile
     {
     {
-        public string Container { get; set; }
+        public string[] Containers { get; set; }
         public string AudioCodec { get; set; }
         public string AudioCodec { get; set; }
         public string VideoCodec { 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 DlnaProfileType Type { get; set; }
 
 
         public List<ProfileCondition> Conditions { get; set; }
         public List<ProfileCondition> Conditions { get; set; }
@@ -62,37 +16,25 @@ namespace MediaBrowser.Controller.Dlna
         public DirectPlayProfile()
         public DirectPlayProfile()
         {
         {
             Conditions = new List<ProfileCondition>();
             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
     public enum DlnaProfileType
     {
     {
         Audio = 0,
         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
     }
     }
 }
 }

+ 26 - 0
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();
+        }
+    }
+}

+ 0 - 5
MediaBrowser.Controller/Dlna/TranscodingProfile.cs

@@ -8,12 +8,7 @@ namespace MediaBrowser.Controller.Dlna
 
 
         public DlnaProfileType Type { get; set; }
         public DlnaProfileType Type { get; set; }
 
 
-        public string MimeType { get; set; }
-
-        public string OrgPn { get; set; }
-
         public string VideoCodec { get; set; }
         public string VideoCodec { get; set; }
-
         public string AudioCodec { get; set; }
         public string AudioCodec { get; set; }
 
 
         public List<TranscodingSetting> Settings { get; set; }
         public List<TranscodingSetting> Settings { get; set; }

+ 0 - 7
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -28,13 +28,6 @@ namespace MediaBrowser.Controller.Dto
         /// <returns>SessionInfoDto.</returns>
         /// <returns>SessionInfoDto.</returns>
         SessionInfoDto GetSessionInfoDto(SessionInfo session);
         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>
         /// <summary>
         /// Gets the dto id.
         /// Gets the dto id.
         /// </summary>
         /// </summary>

+ 2 - 2
MediaBrowser.Controller/Entities/Video.cs

@@ -37,11 +37,11 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
-        public int AlternateVersionCount
+        public int MediaSourceCount
         {
         {
             get
             get
             {
             {
-                return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count;
+                return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count + 1;
             }
             }
         }
         }
 
 

+ 1 - 0
MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs

@@ -12,6 +12,7 @@ namespace MediaBrowser.Controller.Library
         public List<User> Users { get; set; }
         public List<User> Users { get; set; }
         public long? PlaybackPositionTicks { get; set; }
         public long? PlaybackPositionTicks { get; set; }
         public BaseItem Item { get; set; }
         public BaseItem Item { get; set; }
+        public string MediaSourceId { get; set; }
 
 
         public PlaybackProgressEventArgs()
         public PlaybackProgressEventArgs()
         {
         {

+ 2 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -78,10 +78,12 @@
     <Compile Include="Channels\Channel.cs" />
     <Compile Include="Channels\Channel.cs" />
     <Compile Include="Collections\CollectionCreationOptions.cs" />
     <Compile Include="Collections\CollectionCreationOptions.cs" />
     <Compile Include="Collections\ICollectionManager.cs" />
     <Compile Include="Collections\ICollectionManager.cs" />
+    <Compile Include="Dlna\CodecProfile.cs" />
     <Compile Include="Dlna\DeviceIdentification.cs" />
     <Compile Include="Dlna\DeviceIdentification.cs" />
     <Compile Include="Dlna\DirectPlayProfile.cs" />
     <Compile Include="Dlna\DirectPlayProfile.cs" />
     <Compile Include="Dlna\IDlnaManager.cs" />
     <Compile Include="Dlna\IDlnaManager.cs" />
     <Compile Include="Dlna\DeviceProfile.cs" />
     <Compile Include="Dlna\DeviceProfile.cs" />
+    <Compile Include="Dlna\MediaProfile.cs" />
     <Compile Include="Dlna\TranscodingProfile.cs" />
     <Compile Include="Dlna\TranscodingProfile.cs" />
     <Compile Include="Drawing\IImageProcessor.cs" />
     <Compile Include="Drawing\IImageProcessor.cs" />
     <Compile Include="Drawing\ImageFormat.cs" />
     <Compile Include="Drawing\ImageFormat.cs" />

+ 6 - 0
MediaBrowser.Controller/Session/PlaybackInfo.cs

@@ -34,5 +34,11 @@ namespace MediaBrowser.Controller.Session
         /// </summary>
         /// </summary>
         /// <value>The session id.</value>
         /// <value>The session id.</value>
         public Guid SessionId { get; set; }
         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; }
     }
     }
 }
 }

+ 6 - 0
MediaBrowser.Controller/Session/PlaybackProgressInfo.cs

@@ -34,5 +34,11 @@ namespace MediaBrowser.Controller.Session
         /// </summary>
         /// </summary>
         /// <value>The position ticks.</value>
         /// <value>The position ticks.</value>
         public long? PositionTicks { get; set; }
         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; }
     }
     }
 }
 }

+ 6 - 0
MediaBrowser.Controller/Session/PlaybackStopInfo.cs

@@ -22,5 +22,11 @@ namespace MediaBrowser.Controller.Session
         /// </summary>
         /// </summary>
         /// <value>The position ticks.</value>
         /// <value>The position ticks.</value>
         public long? PositionTicks { get; set; }
         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; }
     }
     }
 }
 }

+ 25 - 1
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -119,12 +119,24 @@ namespace MediaBrowser.Controller.Session
         /// <value>The now playing item.</value>
         /// <value>The now playing item.</value>
         public BaseItem NowPlayingItem { get; set; }
         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>
         /// <summary>
         /// Gets or sets the now playing position ticks.
         /// Gets or sets the now playing position ticks.
         /// </summary>
         /// </summary>
         /// <value>The now playing position ticks.</value>
         /// <value>The now playing position ticks.</value>
         public long? NowPlayingPositionTicks { get; set; }
         public long? NowPlayingPositionTicks { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether this instance is paused.
         /// Gets or sets a value indicating whether this instance is paused.
         /// </summary>
         /// </summary>
@@ -161,6 +173,18 @@ namespace MediaBrowser.Controller.Session
         /// <value><c>true</c> if [supports fullscreen toggle]; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if [supports fullscreen toggle]; otherwise, <c>false</c>.</value>
         public bool SupportsFullscreenToggle { get; set; }
         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>
         /// <summary>
         /// Gets a value indicating whether this instance is active.
         /// Gets a value indicating whether this instance is active.
         /// </summary>
         /// </summary>

+ 322 - 36
MediaBrowser.Dlna/DlnaManager.cs

@@ -62,13 +62,11 @@ namespace MediaBrowser.Dlna
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"mkv"}, 
                         Containers = new[]{"mkv"}, 
-                        MimeType = "x-mkv", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        MimeType = "x-msvideo", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
@@ -76,6 +74,23 @@ namespace MediaBrowser.Dlna
                         Containers = new[]{"mp4"},                       
                         Containers = new[]{"mp4"},                       
                         Type = DlnaProfileType.Video
                         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
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"mkv"}, 
                         Containers = new[]{"mkv"}, 
-                        MimeType = "x-mkv", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        MimeType = "x-msvideo", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
@@ -128,6 +141,23 @@ namespace MediaBrowser.Dlna
                         Containers = new[]{"mp4"},                       
                         Containers = new[]{"mp4"},                       
                         Type = DlnaProfileType.Video
                         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
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"mkv"}, 
                         Containers = new[]{"mkv"}, 
-                        MimeType = "x-mkv", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        MimeType = "x-msvideo", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
@@ -180,6 +208,23 @@ namespace MediaBrowser.Dlna
                         Containers = new[]{"mp4"},                       
                         Containers = new[]{"mp4"},                       
                         Type = DlnaProfileType.Video
                         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
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        MimeType = "avi", 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
@@ -225,6 +269,16 @@ namespace MediaBrowser.Dlna
                         Containers = new[]{"mp4"}, 
                         Containers = new[]{"mp4"}, 
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     }
                     }
+                },
+
+                MediaProfiles = new[]
+                {
+                    new MediaProfile
+                    {
+                        Container ="avi",
+                        MimeType = "video/avi",
+                        Type = DlnaProfileType.Video
+                    }
                 }
                 }
             });
             });
 
 
@@ -263,7 +317,16 @@ namespace MediaBrowser.Dlna
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        MimeType = "x-msvideo", 
+                        Type = DlnaProfileType.Video
+                    }
+                },
+
+                MediaProfiles = new[]
+                {
+                    new MediaProfile
+                    {
+                        Container ="avi",
+                        MimeType = "video/x-msvideo",
                         Type = DlnaProfileType.Video
                         Type = DlnaProfileType.Video
                     }
                     }
                 }
                 }
@@ -303,14 +366,29 @@ namespace MediaBrowser.Dlna
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        Type = DlnaProfileType.Video,
-                        MimeType = "avi"
+                        Type = DlnaProfileType.Video
                     },
                     },
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"asf"}, 
                         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
                     new TranscodingProfile
                     {
                     {
                         Container = "ts", 
                         Container = "ts", 
-                        Type = DlnaProfileType.Video,
-                        MimeType = "mpeg"
+                        Type = DlnaProfileType.Video
                     }
                     }
                 },
                 },
 
 
@@ -350,20 +427,48 @@ namespace MediaBrowser.Dlna
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"wma"}, 
                         Containers = new[]{"wma"}, 
-                        Type = DlnaProfileType.Audio,
-                        MimeType = "x-ms-wma"
+                        Type = DlnaProfileType.Audio
                     },                    
                     },                    
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        Type = DlnaProfileType.Video,
-                        MimeType = "avi"
+                        Type = DlnaProfileType.Video
                     },
                     },
                      new DirectPlayProfile
                      new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"mp4"}, 
                         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
                      new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         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
             list.Add(new DeviceProfile
             {
             {
                 Name = "Philips (2010-)",
                 Name = "Philips (2010-)",
@@ -479,49 +592,222 @@ namespace MediaBrowser.Dlna
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"avi"}, 
                         Containers = new[]{"avi"}, 
-                        Type = DlnaProfileType.Video,
-                        MimeType = "avi"
+                        Type = DlnaProfileType.Video
                     },
                     },
 
 
                     new DirectPlayProfile
                     new DirectPlayProfile
                     {
                     {
                         Containers = new[]{"mkv"}, 
                         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
             list.Add(new DeviceProfile
             {
             {
                 Name = "WDTV Live",
                 Name = "WDTV Live",
                 ClientType = "DLNA",
                 ClientType = "DLNA",
 
 
+                TimelineOffsetSeconds = 5,
+
                 Identification = new DeviceIdentification
                 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[]
                 DirectPlayProfiles = new[]
                 {
                 {
                     new DirectPlayProfile
                     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
                         Type = DlnaProfileType.Audio
                     },
                     },
 
 
                     new DirectPlayProfile
                     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
                         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
             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",
                 Name = "Linksys DMA2100",
                 ClientType = "DLNA",
                 ClientType = "DLNA",
 
 
@@ -547,10 +833,10 @@ namespace MediaBrowser.Dlna
             });
             });
 
 
             list.Add(new DeviceProfile
             list.Add(new DeviceProfile
-            {                
+            {
                 Name = "Denon AVR",
                 Name = "Denon AVR",
-                ClientType = "DLNA",       
-         
+                ClientType = "DLNA",
+
                 Identification = new DeviceIdentification
                 Identification = new DeviceIdentification
                 {
                 {
                     FriendlyName = @"Denon:\[AVR:.*",
                     FriendlyName = @"Denon:\[AVR:.*",
@@ -612,7 +898,7 @@ namespace MediaBrowser.Dlna
 
 
         public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
         public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
         {
         {
-            return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ?? 
+            return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ??
                 GetDefaultProfile();
                 GetDefaultProfile();
         }
         }
 
 

+ 1 - 0
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

@@ -66,6 +66,7 @@
     <Compile Include="PlayTo\PlaylistItem.cs">
     <Compile Include="PlayTo\PlaylistItem.cs">
       <SubType>Code</SubType>
       <SubType>Code</SubType>
     </Compile>
     </Compile>
+    <Compile Include="PlayTo\PlaylistItemFactory.cs" />
     <Compile Include="PlayTo\PlayToManager.cs" />
     <Compile Include="PlayTo\PlayToManager.cs" />
     <Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
     <Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
     <Compile Include="PlayTo\ServiceAction.cs" />
     <Compile Include="PlayTo\ServiceAction.cs" />

+ 37 - 8
MediaBrowser.Dlna/PlayTo/DlnaController.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
@@ -69,12 +70,12 @@ namespace MediaBrowser.Dlna.PlayTo
             _device.CurrentIdChanged += Device_CurrentIdChanged;
             _device.CurrentIdChanged += Device_CurrentIdChanged;
             _device.Start();
             _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
         #region Device EventHandlers & Update Timer
 
 
-        System.Threading.Timer _updateTimer;
+        Timer _updateTimer;
 
 
         async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
         async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
         {
         {
@@ -88,7 +89,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
             {
                 _playbackStarted = false;
                 _playbackStarted = false;
 
 
-                await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
+                await _sessionManager.OnPlaybackStopped(new Controller.Session.PlaybackStopInfo
                 {
                 {
                     Item = _currentItem,
                     Item = _currentItem,
                     SessionId = _session.Id,
                     SessionId = _session.Id,
@@ -164,7 +165,7 @@ namespace MediaBrowser.Dlna.PlayTo
                 var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
                 var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
                 if (playlistItem != null && playlistItem.Transcode)
                 if (playlistItem != null && playlistItem.Transcode)
                 {
                 {
-                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+                    await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
                     {
                     {
                         Item = _currentItem,
                         Item = _currentItem,
                         SessionId = _session.Id,
                         SessionId = _session.Id,
@@ -176,7 +177,7 @@ namespace MediaBrowser.Dlna.PlayTo
                 }
                 }
                 else if (_currentItem != null)
                 else if (_currentItem != null)
                 {
                 {
-                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+                    await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
                     {
                     {
                         Item = _currentItem,
                         Item = _currentItem,
                         SessionId = _session.Id,
                         SessionId = _session.Id,
@@ -263,7 +264,7 @@ namespace MediaBrowser.Dlna.PlayTo
 
 
                 case PlaystateCommand.Seek:
                 case PlaystateCommand.Seek:
                     var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
                     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());
                         var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
                         playlistItem.StartPositionTicks = newItem.StartPositionTicks;
                         playlistItem.StartPositionTicks = newItem.StartPositionTicks;
@@ -394,11 +395,13 @@ namespace MediaBrowser.Dlna.PlayTo
 
 
             var deviceInfo = _device.Properties;
             var deviceInfo = _device.Properties;
 
 
-            var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()));
+            var playlistItem = GetPlaylistItem(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()));
             playlistItem.StartPositionTicks = startPostionTicks;
             playlistItem.StartPositionTicks = startPostionTicks;
 
 
-            if (playlistItem.IsAudio)
+            if (playlistItem.MediaType == DlnaProfileType.Audio)
+            {
                 playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
                 playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
+            }
             else
             else
             {
             {
                 playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
                 playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
@@ -412,6 +415,32 @@ namespace MediaBrowser.Dlna.PlayTo
             return playlistItem;
             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>
         /// <summary>
         /// Plays the items.
         /// Plays the items.
         /// </summary>
         /// </summary>

+ 4 - 76
MediaBrowser.Dlna/PlayTo/PlaylistItem.cs

@@ -1,9 +1,4 @@
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Entities;
-using System;
-using System.IO;
-using System.Linq;
 
 
 namespace MediaBrowser.Dlna.PlayTo
 namespace MediaBrowser.Dlna.PlayTo
 {
 {
@@ -11,13 +6,13 @@ namespace MediaBrowser.Dlna.PlayTo
     {
     {
         public string ItemId { get; set; }
         public string ItemId { get; set; }
 
 
+        public string MediaSourceId { get; set; }
+        
         public bool Transcode { 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; }
         public string MimeType { get; set; }
 
 
@@ -30,72 +25,5 @@ namespace MediaBrowser.Dlna.PlayTo
         public string Didl { get; set; }
         public string Didl { get; set; }
 
 
         public long StartPositionTicks { 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;
-        }
     }
     }
 }
 }

+ 375 - 0
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;
+        }
+    }
+}

+ 13 - 13
MediaBrowser.Dlna/PlayTo/StreamHelper.cs

@@ -24,43 +24,43 @@ namespace MediaBrowser.Dlna.PlayTo
 
 
             var contentFeatures = string.Empty;
             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";
                 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";
                 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";
                 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";
                 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";
                 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";
                 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";
                 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";
                 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";
                 contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
             }
             }
-            else if (item.IsVideo)
+            else if (item.MediaType == Controller.Dlna.DlnaProfileType.Video)
             {
             {
                 //Default to AVI for video
                 //Default to AVI for video
                 contentFeatures = "DLNA.ORG_PN=AVI";
                 contentFeatures = "DLNA.ORG_PN=AVI";
@@ -85,7 +85,7 @@ namespace MediaBrowser.Dlna.PlayTo
         internal static string GetAudioUrl(PlaylistItem item, string serverAddress)
         internal static string GetAudioUrl(PlaylistItem item, string serverAddress)
         {
         {
             if (!item.Transcode)
             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);
             return string.Format("{0}/audio/{1}/stream.mp3?AudioCodec=Mp3", serverAddress, item.ItemId);
         }
         }
@@ -108,7 +108,7 @@ namespace MediaBrowser.Dlna.PlayTo
             if (!item.Transcode)
             if (!item.Transcode)
             {
             {
                 dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, !item.Transcode, null, null, null, null, null, null, null, null, null, null, item.MimeType);
                 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 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();
             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);
             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>
         /// <summary>

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -428,6 +428,9 @@
     <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
     <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
       <Link>Session\MessageCommand.cs</Link>
       <Link>Session\MessageCommand.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Session\PlaybackReports.cs">
+      <Link>Session\PlaybackReports.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
     <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
       <Link>Session\PlayRequest.cs</Link>
       <Link>Session\PlayRequest.cs</Link>
     </Compile>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -415,6 +415,9 @@
     <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
     <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
       <Link>Session\MessageCommand.cs</Link>
       <Link>Session\MessageCommand.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Session\PlaybackReports.cs">
+      <Link>Session\PlaybackReports.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
     <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
       <Link>Session\PlayRequest.cs</Link>
       <Link>Session\PlayRequest.cs</Link>
     </Compile>
     </Compile>

+ 6 - 15
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -538,35 +538,26 @@ namespace MediaBrowser.Model.ApiClient
         /// <summary>
         /// <summary>
         /// Reports to the server that the user has begun playing an item
         /// Reports to the server that the user has begun playing an item
         /// </summary>
         /// </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>
         /// <returns>Task{UserItemDataDto}.</returns>
         /// <exception cref="ArgumentNullException">itemId</exception>
         /// <exception cref="ArgumentNullException">itemId</exception>
-        Task ReportPlaybackStartAsync(string itemId, string userId, bool isSeekable, List<string> queueableMediaTypes);
+        Task ReportPlaybackStartAsync(PlaybackStartInfo info);
 
 
         /// <summary>
         /// <summary>
         /// Reports playback progress to the server
         /// Reports playback progress to the server
         /// </summary>
         /// </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>
         /// <returns>Task{UserItemDataDto}.</returns>
         /// <exception cref="ArgumentNullException">itemId</exception>
         /// <exception cref="ArgumentNullException">itemId</exception>
-        Task ReportPlaybackProgressAsync(string itemId, string userId, long? positionTicks, bool isPaused, bool isMuted);
+        Task ReportPlaybackProgressAsync(PlaybackProgressInfo info);
 
 
         /// <summary>
         /// <summary>
         /// Reports to the server that the user has stopped playing an item
         /// Reports to the server that the user has stopped playing an item
         /// </summary>
         /// </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>
         /// <returns>Task{UserItemDataDto}.</returns>
         /// <exception cref="ArgumentNullException">itemId</exception>
         /// <exception cref="ArgumentNullException">itemId</exception>
-        Task ReportPlaybackStoppedAsync(string itemId, string userId, long? positionTicks);
+        Task ReportPlaybackStoppedAsync(PlaybackStopInfo info);
 
 
         /// <summary>
         /// <summary>
         /// Instructs antoher client to browse to a library item.
         /// Instructs antoher client to browse to a library item.

+ 2 - 2
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -147,7 +147,7 @@ namespace MediaBrowser.Model.Configuration
         /// different directories and files.
         /// different directories and files.
         /// </summary>
         /// </summary>
         /// <value>The file watcher delay.</value>
         /// <value>The file watcher delay.</value>
-        public int RealtimeWatcherDelay { get; set; }
+        public int RealtimeMonitorDelay { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether [enable dashboard response caching].
         /// Gets or sets a value indicating whether [enable dashboard response caching].
@@ -239,7 +239,7 @@ namespace MediaBrowser.Model.Configuration
             MaxResumePct = 90;
             MaxResumePct = 90;
             MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds);
             MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds);
 
 
-            RealtimeWatcherDelay = 20;
+            RealtimeMonitorDelay = 30;
 
 
             RecentItemDays = 10;
             RecentItemDays = 10;
 
 

+ 2 - 2
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -92,7 +92,7 @@ namespace MediaBrowser.Model.Dto
         /// Gets or sets the media versions.
         /// Gets or sets the media versions.
         /// </summary>
         /// </summary>
         /// <value>The media versions.</value>
         /// <value>The media versions.</value>
-        public List<MediaVersionInfo> MediaVersions { get; set; }
+        public List<MediaSourceInfo> MediaSources { get; set; }
         
         
         /// <summary>
         /// <summary>
         /// Gets or sets the critic rating.
         /// Gets or sets the critic rating.
@@ -500,7 +500,7 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// </summary>
         /// <value>The part count.</value>
         /// <value>The part count.</value>
         public int? PartCount { get; set; }
         public int? PartCount { get; set; }
-        public int? MediaVersionCount { get; set; }
+        public int? MediaSourceCount { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Determines whether the specified type is type.
         /// Determines whether the specified type is type.

+ 2 - 6
MediaBrowser.Model/Dto/MediaVersionInfo.cs

@@ -3,9 +3,9 @@ using System.Collections.Generic;
 
 
 namespace MediaBrowser.Model.Dto
 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; }
         public string Path { get; set; }
 
 
@@ -22,9 +22,5 @@ namespace MediaBrowser.Model.Dto
         public Video3DFormat? Video3DFormat { get; set; }
         public Video3DFormat? Video3DFormat { get; set; }
         
         
         public List<MediaStream> MediaStreams { get; set; }
         public List<MediaStream> MediaStreams { get; set; }
-
-        public List<ChapterInfoDto> Chapters { get; set; }
-
-        public bool IsPrimaryVersion { get; set; }
     }
     }
 }
 }

+ 6 - 0
MediaBrowser.Model/Entities/BaseItemInfo.cs

@@ -69,6 +69,12 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         /// </summary>
         /// <value>The thumb item identifier.</value>
         /// <value>The thumb item identifier.</value>
         public string BackdropItemId { get; set; }
         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>
         /// <summary>
         /// Gets a value indicating whether this instance has primary image.
         /// Gets a value indicating whether this instance has primary image.

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -132,6 +132,7 @@
     <Compile Include="Search\SearchQuery.cs" />
     <Compile Include="Search\SearchQuery.cs" />
     <Compile Include="Session\BrowseRequest.cs" />
     <Compile Include="Session\BrowseRequest.cs" />
     <Compile Include="Session\MessageCommand.cs" />
     <Compile Include="Session\MessageCommand.cs" />
+    <Compile Include="Session\PlaybackReports.cs" />
     <Compile Include="Session\PlayRequest.cs" />
     <Compile Include="Session\PlayRequest.cs" />
     <Compile Include="Session\PlaystateCommand.cs" />
     <Compile Include="Session\PlaystateCommand.cs" />
     <Compile Include="Logging\ILogManager.cs" />
     <Compile Include="Logging\ILogManager.cs" />

+ 1 - 1
MediaBrowser.Model/Querying/ItemFields.cs

@@ -84,7 +84,7 @@ namespace MediaBrowser.Model.Querying
         /// <summary>
         /// <summary>
         /// The media versions
         /// The media versions
         /// </summary>
         /// </summary>
-        MediaVersions,
+        MediaSources,
 
 
         /// <summary>
         /// <summary>
         /// The metadata settings
         /// The metadata settings

+ 56 - 0
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; }
+    }
+}

+ 4 - 0
MediaBrowser.Model/Session/SessionCapabilities.cs

@@ -7,6 +7,10 @@ namespace MediaBrowser.Model.Session
 
 
         public bool SupportsFullscreenToggle { get; set; }
         public bool SupportsFullscreenToggle { get; set; }
 
 
+        public bool SupportsOsdToggle { get; set; }
+
+        public bool SupportsNavigationControl { get; set; }
+        
         public SessionCapabilities()
         public SessionCapabilities()
         {
         {
             PlayableMediaTypes = new string[] {};
             PlayableMediaTypes = new string[] {};

+ 14 - 2
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -1,8 +1,8 @@
-using System.Diagnostics;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
+using System.Diagnostics;
 
 
 namespace MediaBrowser.Model.Session
 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>
         /// <value><c>true</c> if [supports remote control]; otherwise, <c>false</c>.</value>
         public bool SupportsRemoteControl { get; set; }
         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 event PropertyChangedEventHandler PropertyChanged;
 
 
         public SessionInfoDto()
         public SessionInfoDto()

+ 1 - 1
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("<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)
             if (item.LockedFields.Count > 0)
             {
             {

+ 68 - 70
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)
         public BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, User user = null)
             where T : BaseItem, IItemByName
             where T : BaseItem, IItemByName
         {
         {
-            var libraryItems = user != null ? user.RootFolder.GetRecursiveChildren(user) : 
+            var libraryItems = user != null ? user.RootFolder.GetRecursiveChildren(user) :
                 _libraryManager.RootFolder.RecursiveChildren;
                 _libraryManager.RootFolder.RecursiveChildren;
 
 
             return GetItemByNameDto(item, fields, item.GetTaggedItems(libraryItems).ToList(), user);
             return GetItemByNameDto(item, fields, item.GetTaggedItems(libraryItems).ToList(), user);
@@ -267,12 +267,14 @@ namespace MediaBrowser.Server.Implementations.Dto
                 PlayableMediaTypes = session.PlayableMediaTypes,
                 PlayableMediaTypes = session.PlayableMediaTypes,
                 RemoteEndPoint = session.RemoteEndPoint,
                 RemoteEndPoint = session.RemoteEndPoint,
                 AdditionalUsers = session.AdditionalUsers,
                 AdditionalUsers = session.AdditionalUsers,
-                SupportsFullscreenToggle = session.SupportsFullscreenToggle
+                SupportsFullscreenToggle = session.SupportsFullscreenToggle,
+                SupportsNavigationControl = session.SupportsNavigationControl,
+                SupportsOsdToggle = session.SupportsOsdToggle
             };
             };
 
 
             if (session.NowPlayingItem != null)
             if (session.NowPlayingItem != null)
             {
             {
-                dto.NowPlayingItem = GetBaseItemInfo(session.NowPlayingItem);
+                dto.NowPlayingItem = GetNowPlayingInfo(session.NowPlayingItem, session.NowPlayingMediaSourceId, session.NowPlayingRunTimeTicks);
             }
             }
 
 
             if (session.UserId.HasValue)
             if (session.UserId.HasValue)
@@ -288,9 +290,11 @@ namespace MediaBrowser.Server.Implementations.Dto
         /// Converts a BaseItem to a BaseItemInfo
         /// Converts a BaseItem to a BaseItemInfo
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <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>
         /// <returns>BaseItemInfo.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public BaseItemInfo GetBaseItemInfo(BaseItem item)
+        private BaseItemInfo GetNowPlayingInfo(BaseItem item, string mediaSourceId, long? nowPlayingRuntimeTicks)
         {
         {
             if (item == null)
             if (item == null)
             {
             {
@@ -303,7 +307,8 @@ namespace MediaBrowser.Server.Implementations.Dto
                 Name = item.Name,
                 Name = item.Name,
                 MediaType = item.MediaType,
                 MediaType = item.MediaType,
                 Type = item.GetClientTypeName(),
                 Type = item.GetClientTypeName(),
-                RunTimeTicks = item.RunTimeTicks
+                RunTimeTicks = nowPlayingRuntimeTicks,
+                MediaSourceId = mediaSourceId
             };
             };
 
 
             info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
             info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
@@ -735,7 +740,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             if (fields.Contains(ItemFields.Settings))
             if (fields.Contains(ItemFields.Settings))
             {
             {
                 dto.LockedFields = item.LockedFields;
                 dto.LockedFields = item.LockedFields;
-                dto.LockData = item.DontFetchMeta;
+                dto.LockData = item.IsLocked;
             }
             }
 
 
             var hasBudget = item as IHasBudget;
             var hasBudget = item as IHasBudget;
@@ -1041,7 +1046,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             {
             {
                 dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
                 dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
             }
             }
-            
+
             // Add audio info
             // Add audio info
             var audio = item as Audio;
             var audio = item as Audio;
             if (audio != null)
             if (audio != null)
@@ -1058,8 +1063,8 @@ namespace MediaBrowser.Server.Implementations.Dto
                     dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
                     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;
             var album = item as MusicAlbum;
@@ -1090,22 +1095,20 @@ namespace MediaBrowser.Server.Implementations.Dto
                 dto.IsHD = video.IsHD;
                 dto.IsHD = video.IsHD;
 
 
                 dto.PartCount = video.AdditionalPartIds.Count + 1;
                 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))
                 if (fields.Contains(ItemFields.Chapters))
                 {
                 {
                     List<ChapterInfoDto> 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
                     else
                     {
                     {
@@ -1127,9 +1130,9 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                 {
                     List<MediaStream> mediaStreams;
                     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)
                             .SelectMany(i => i.MediaStreams)
                             .ToList();
                             .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 =>
             return result.OrderBy(i =>
             {
             {
@@ -1286,49 +1289,47 @@ namespace MediaBrowser.Server.Implementations.Dto
 
 
                 return stream == null || stream.Width == null ? 0 : stream.Width.Value;
                 return stream == null || stream.Width == null ? 0 : stream.Width.Value;
             })
             })
-            .ThenBy(i => i.IsPrimaryVersion ? 0 : 1)
             .ToList();
             .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;
             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,
                 IsoType = i.IsoType,
                 LocationType = i.LocationType,
                 LocationType = i.LocationType,
-                MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
-                Name = GetAlternateVersionName(i),
+                MediaStreams = mediaStreams,
+                Name = GetMediaSourceName(i, mediaStreams),
                 Path = GetMappedPath(i),
                 Path = GetMappedPath(i),
                 RunTimeTicks = i.RunTimeTicks,
                 RunTimeTicks = i.RunTimeTicks,
                 Video3DFormat = i.Video3DFormat,
                 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,
                 LocationType = i.LocationType,
                 MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
                 MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
                 Name = i.Name,
                 Name = i.Name,
                 Path = GetMappedPath(i),
                 Path = GetMappedPath(i),
-                RunTimeTicks = i.RunTimeTicks,
-                IsPrimaryVersion = isPrimary
+                RunTimeTicks = i.RunTimeTicks
             };
             };
         }
         }
 
 
@@ -1351,32 +1352,29 @@ namespace MediaBrowser.Server.Implementations.Dto
             return path;
             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)
             if (video.Video3DFormat.HasValue)
             {
             {
-                name = "3D " + name;
-                name = name.Trim();
+                terms.Add("3D");
             }
             }
 
 
             if (video.VideoType == VideoType.BluRay)
             if (video.VideoType == VideoType.BluRay)
             {
             {
-                name = name + " " + "Bluray";
-                name = name.Trim();
+                terms.Add("Bluray");
             }
             }
             else if (video.VideoType == VideoType.Dvd)
             else if (video.VideoType == VideoType.Dvd)
             {
             {
-                name = name + " " + "DVD";
-                name = name.Trim();
+                terms.Add("DVD");
             }
             }
             else if (video.VideoType == VideoType.HdDvd)
             else if (video.VideoType == VideoType.HdDvd)
             {
             {
-                name = name + " " + "HD-DVD";
-                name = name.Trim();
+                terms.Add("HD-DVD");
             }
             }
             else if (video.VideoType == VideoType.Iso)
             else if (video.VideoType == VideoType.Iso)
             {
             {
@@ -1384,18 +1382,17 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                 {
                     if (video.IsoType.Value == IsoType.BluRay)
                     if (video.IsoType.Value == IsoType.BluRay)
                     {
                     {
-                        name = name + " " + "Bluray";
+                        terms.Add("Bluray");
                     }
                     }
                     else if (video.IsoType.Value == IsoType.Dvd)
                     else if (video.IsoType.Value == IsoType.Dvd)
                     {
                     {
-                        name = name + " " + "DVD";
+                        terms.Add("DVD");
                     }
                     }
                 }
                 }
                 else
                 else
                 {
                 {
-                    name = name + " " + "ISO";
+                    terms.Add("ISO");
                 }
                 }
-                name = name.Trim();
             }
             }
 
 
             if (videoStream != null)
             if (videoStream != null)
@@ -1404,44 +1401,45 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                 {
                     if (videoStream.Width.Value >= 3800)
                     if (videoStream.Width.Value >= 3800)
                     {
                     {
-                        name = name + " " + "4K";
-                        name = name.Trim();
+                        terms.Add("4K");
                     }
                     }
                     else if (videoStream.Width.Value >= 1900)
                     else if (videoStream.Width.Value >= 1900)
                     {
                     {
-                        name = name + " " + "1080P";
-                        name = name.Trim();
+                        terms.Add("1080P");
                     }
                     }
                     else if (videoStream.Width.Value >= 1270)
                     else if (videoStream.Width.Value >= 1270)
                     {
                     {
-                        name = name + " " + "720P";
-                        name = name.Trim();
+                        terms.Add("720P");
                     }
                     }
                     else if (videoStream.Width.Value >= 700)
                     else if (videoStream.Width.Value >= 700)
                     {
                     {
-                        name = name + " " + "480p";
-                        name = name.Trim();
+                        terms.Add("480P");
                     }
                     }
                     else
                     else
                     {
                     {
-                        name = name + " " + "SD";
-                        name = name.Trim();
+                        terms.Add("SD");
                     }
                     }
                 }
                 }
             }
             }
 
 
             if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
             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)
         private string GetMappedPath(string path)

+ 12 - 14
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -180,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                     result.StatusMessage = string.Empty;
                     result.StatusMessage = string.Empty;
                     return;
                     return;
                 }
                 }
-                
+
                 if (fileExists || otherDuplicatePaths.Count > 0)
                 if (fileExists || otherDuplicatePaths.Count > 0)
                 {
                 {
                     result.Status = FileSortingStatus.SkippedExisting;
                     result.Status = FileSortingStatus.SkippedExisting;
@@ -453,24 +453,22 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
 
         private bool IsSameEpisode(string sourcePath, string newPath)
         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;
+            }
 
 
+            return false;
         }
         }
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs

@@ -67,6 +67,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             .Replace("!", " ")
             .Replace("!", " ")
             .Replace("(", " ")
             .Replace("(", " ")
             .Replace(")", " ")
             .Replace(")", " ")
+            .Replace(":", " ")
             .Replace(",", " ")
             .Replace(",", " ")
             .Replace("-", " ")
             .Replace("-", " ")
             .Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase)
             .Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase)

+ 2 - 2
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -427,11 +427,11 @@ namespace MediaBrowser.Server.Implementations.IO
             {
             {
                 if (_updateTimer == null)
                 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
                 else
                 {
                 {
-                    _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1));
+                    _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeMonitorDelay), TimeSpan.FromMilliseconds(-1));
                 }
                 }
             }
             }
         }
         }

+ 1 - 1
MediaBrowser.Server.Implementations/Library/ResolverHelper.cs

@@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Library
             EnsureName(item, args);
             EnsureName(item, args);
 
 
             item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
             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
             // Make sure DateCreated and DateModified have values
             EntityResolutionHelper.EnsureDates(fileSystem, item, args, true);
             EntityResolutionHelper.EnsureDates(fileSystem, item, args, true);

+ 4 - 4
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="directoryService">The directory service.</param>
         /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param>
         /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param>
         /// <returns>Movie.</returns>
         /// <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()
             where T : Video, new()
         {
         {
             var movies = new List<T>();
             var movies = new List<T>();
@@ -262,9 +262,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                         return result;
                         return result;
                     }
                     }
                 }
                 }
-                if (supportsAlternateVersions)
+                if (supportsMultipleSources)
                 {
                 {
-                    var result = GetMovieWithAlternateVersions(movies);
+                    var result = GetMovieWithMultipleSources(movies);
 
 
                     if (result != null)
                     if (result != null)
                     {
                     {
@@ -393,7 +393,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
             return null;
             return null;
         }
         }
 
 
-        private T GetMovieWithAlternateVersions<T>(IEnumerable<T> movies)
+        private T GetMovieWithMultipleSources<T>(IEnumerable<T> movies)
                where T : Video, new()
                where T : Video, new()
         {
         {
             var sortedMovies = movies.OrderBy(i => i.Path).ToList();
             var sortedMovies = movies.OrderBy(i => i.Path).ToList();

+ 5 - 0
MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs

@@ -1,8 +1,10 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using System;
 using System;
+using System.Collections.Generic;
 
 
 namespace MediaBrowser.Server.Implementations.Roku
 namespace MediaBrowser.Server.Implementations.Roku
 {
 {
@@ -23,6 +25,9 @@ namespace MediaBrowser.Server.Implementations.Roku
         {
         {
             if (string.Equals(session.Client, "roku", StringComparison.OrdinalIgnoreCase))
             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);
                 return new RokuSessionController(_httpClient, _json, _appHost, session);
             }
             }
 
 

+ 48 - 8
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -218,15 +218,29 @@ namespace MediaBrowser.Server.Implementations.Session
         /// </summary>
         /// </summary>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="item">The item.</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="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>
         /// <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.IsMuted = isMuted;
             session.IsPaused = isPaused;
             session.IsPaused = isPaused;
             session.NowPlayingPositionTicks = currentPositionTicks;
             session.NowPlayingPositionTicks = currentPositionTicks;
             session.NowPlayingItem = item;
             session.NowPlayingItem = item;
             session.LastActivityDate = DateTime.UtcNow;
             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>
         /// <summary>
@@ -246,6 +260,8 @@ namespace MediaBrowser.Server.Implementations.Session
                 session.NowPlayingItem = null;
                 session.NowPlayingItem = null;
                 session.NowPlayingPositionTicks = null;
                 session.NowPlayingPositionTicks = null;
                 session.IsPaused = false;
                 session.IsPaused = false;
+                session.NowPlayingRunTimeTicks = null;
+                session.NowPlayingMediaSourceId = null;
             }
             }
         }
         }
 
 
@@ -352,7 +368,9 @@ namespace MediaBrowser.Server.Implementations.Session
 
 
             var item = info.Item;
             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.CanSeek = info.CanSeek;
             session.QueueableMediaTypes = info.QueueableMediaTypes;
             session.QueueableMediaTypes = info.QueueableMediaTypes;
@@ -371,7 +389,8 @@ namespace MediaBrowser.Server.Implementations.Session
             EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
             EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
             {
             {
                 Item = item,
                 Item = item,
-                Users = users
+                Users = users,
+                MediaSourceId = info.MediaSourceId
 
 
             }, _logger);
             }, _logger);
         }
         }
@@ -405,7 +424,7 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
         /// <exception cref="System.ArgumentNullException"></exception>
         /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception>
         /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception>
-        public async Task OnPlaybackProgress(PlaybackProgressInfo info)
+        public async Task OnPlaybackProgress(Controller.Session.PlaybackProgressInfo info)
         {
         {
             if (info == null)
             if (info == null)
             {
             {
@@ -419,7 +438,9 @@ namespace MediaBrowser.Server.Implementations.Session
 
 
             var session = Sessions.First(i => i.Id.Equals(info.SessionId));
             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();
             var key = info.Item.GetUserDataKey();
 
 
@@ -434,7 +455,8 @@ namespace MediaBrowser.Server.Implementations.Session
             {
             {
                 Item = info.Item,
                 Item = info.Item,
                 Users = users,
                 Users = users,
-                PlaybackPositionTicks = info.PositionTicks
+                PlaybackPositionTicks = info.PositionTicks,
+                MediaSourceId = mediaSourceId
 
 
             }, _logger);
             }, _logger);
         }
         }
@@ -458,7 +480,7 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">info</exception>
         /// <exception cref="System.ArgumentNullException">info</exception>
         /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception>
         /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception>
-        public async Task OnPlaybackStopped(PlaybackStopInfo info)
+        public async Task OnPlaybackStopped(Controller.Session.PlaybackStopInfo info)
         {
         {
             if (info == null)
             if (info == null)
             {
             {
@@ -494,16 +516,32 @@ namespace MediaBrowser.Server.Implementations.Session
                 playedToCompletion = await OnPlaybackStopped(user.Id, key, info.Item, info.PositionTicks).ConfigureAwait(false);
                 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
             EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs
             {
             {
                 Item = info.Item,
                 Item = info.Item,
                 Users = users,
                 Users = users,
                 PlaybackPositionTicks = info.PositionTicks,
                 PlaybackPositionTicks = info.PositionTicks,
-                PlayedToCompletion = playedToCompletion
+                PlayedToCompletion = playedToCompletion,
+                MediaSourceId = mediaSourceId
 
 
             }, _logger);
             }, _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)
         private async Task<bool> OnPlaybackStopped(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
         {
         {
             var data = _userDataRepository.GetUserData(userId, userDataKey);
             var data = _userDataRepository.GetUserData(userId, userDataKey);
@@ -899,6 +937,8 @@ namespace MediaBrowser.Server.Implementations.Session
 
 
             session.PlayableMediaTypes = capabilities.PlayableMediaTypes.ToList();
             session.PlayableMediaTypes = capabilities.PlayableMediaTypes.ToList();
             session.SupportsFullscreenToggle = capabilities.SupportsFullscreenToggle;
             session.SupportsFullscreenToggle = capabilities.SupportsFullscreenToggle;
+            session.SupportsOsdToggle = capabilities.SupportsOsdToggle;
+            session.SupportsNavigationControl = capabilities.SupportsNavigationControl;
         }
         }
     }
     }
 }
 }

+ 15 - 0
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -223,6 +223,11 @@ namespace MediaBrowser.Server.Implementations.Session
                     QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
                     QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
                 };
                 };
 
 
+                if (vals.Length > 3)
+                {
+                    info.MediaSourceId = vals[3];
+                }
+
                 _sessionManager.OnPlaybackStart(info);
                 _sessionManager.OnPlaybackStart(info);
             }
             }
         }
         }
@@ -265,6 +270,11 @@ namespace MediaBrowser.Server.Implementations.Session
                     SessionId = session.Id
                     SessionId = session.Id
                 };
                 };
 
 
+                if (vals.Length > 4)
+                {
+                    info.MediaSourceId = vals[4];
+                }
+
                 _sessionManager.OnPlaybackProgress(info);
                 _sessionManager.OnPlaybackProgress(info);
             }
             }
         }
         }
@@ -304,6 +314,11 @@ namespace MediaBrowser.Server.Implementations.Session
                     SessionId = session.Id
                     SessionId = session.Id
                 };
                 };
 
 
+                if (vals.Length > 2)
+                {
+                    info.MediaSourceId = vals[2];
+                }
+
                 _sessionManager.OnPlaybackStopped(info);
                 _sessionManager.OnPlaybackStopped(info);
             }
             }
         }
         }

+ 1 - 2
MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs

@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Text;
 using System.Text;
 
 
 namespace MediaBrowser.Server.Implementations.Sorting
 namespace MediaBrowser.Server.Implementations.Sorting

+ 35 - 24
MediaBrowser.WebDashboard/ApiClient.js

@@ -2191,20 +2191,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
          * Constructs a url for a user image
          * @param {String} userId
          * @param {String} userId
@@ -3805,7 +3791,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
          * @param {String} userId
          * @param {String} userId
          * @param {String} itemId
          * @param {String} itemId
          */
          */
-        self.reportPlaybackStart = function (userId, itemId, canSeek, queueableMediaTypes) {
+        self.reportPlaybackStart = function (userId, itemId, mediaSourceId, canSeek, queueableMediaTypes) {
 
 
             if (!userId) {
             if (!userId) {
                 throw new Error("null userId");
                 throw new Error("null userId");
@@ -3823,6 +3809,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
                 var deferred = $.Deferred();
                 var deferred = $.Deferred();
 
 
                 var msg = [itemId, canSeek, queueableMediaTypes];
                 var msg = [itemId, canSeek, queueableMediaTypes];
+                
+                if (mediaSourceId) {
+                    msg.push(mediaSourceId);
+                }
 
 
                 self.sendWebSocketMessage("PlaybackStart", msg.join('|'));
                 self.sendWebSocketMessage("PlaybackStart", msg.join('|'));
 
 
@@ -3830,10 +3820,16 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
                 return deferred.promise();
                 return deferred.promise();
             }
             }
 
 
-            var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, {
+            var params = {
                 CanSeek: canSeek,
                 CanSeek: canSeek,
                 QueueableMediaTypes: queueableMediaTypes
                 QueueableMediaTypes: queueableMediaTypes
-            });
+            };
+
+            if (mediaSourceId) {
+                params.mediaSourceId = mediaSourceId;
+            }
+
+            var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, params);
 
 
             return self.ajax({
             return self.ajax({
                 type: "POST",
                 type: "POST",
@@ -3846,7 +3842,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
          * @param {String} userId
          * @param {String} userId
          * @param {String} itemId
          * @param {String} itemId
          */
          */
-        self.reportPlaybackProgress = function (userId, itemId, positionTicks, isPaused, isMuted) {
+        self.reportPlaybackProgress = function (userId, itemId, mediaSourceId, positionTicks, isPaused, isMuted) {
 
 
             if (!userId) {
             if (!userId) {
                 throw new Error("null userId");
                 throw new Error("null userId");
@@ -3860,7 +3856,12 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
 
 
                 var deferred = $.Deferred();
                 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);
                 self.sendWebSocketMessage("PlaybackProgress", msgData);
 
 
@@ -3877,6 +3878,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
                 params.positionTicks = positionTicks;
                 params.positionTicks = positionTicks;
             }
             }
 
 
+            if (mediaSourceId) {
+                params.mediaSourceId = mediaSourceId;
+            }
+
             var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId + "/Progress", params);
             var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId + "/Progress", params);
 
 
             return self.ajax({
             return self.ajax({
@@ -3890,7 +3895,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
          * @param {String} userId
          * @param {String} userId
          * @param {String} itemId
          * @param {String} itemId
          */
          */
-        self.reportPlaybackStopped = function (userId, itemId, positionTicks) {
+        self.reportPlaybackStopped = function (userId, itemId, mediaSourceId, positionTicks) {
 
 
             if (!userId) {
             if (!userId) {
                 throw new Error("null userId");
                 throw new Error("null userId");
@@ -3904,20 +3909,26 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
 
 
                 var deferred = $.Deferred();
                 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, []);
                 deferred.resolveWith(null, []);
                 return deferred.promise();
                 return deferred.promise();
             }
             }
 
 
-            var params = {
-
-            };
+            var params = {};
 
 
             if (positionTicks) {
             if (positionTicks) {
                 params.positionTicks = positionTicks;
                 params.positionTicks = positionTicks;
             }
             }
 
 
+            if (mediaSourceId) {
+                params.mediaSourceId = mediaSourceId;
+            }
+
             var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, params);
             var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, params);
 
 
             return self.ajax({
             return self.ajax({

+ 1 - 3
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -84,9 +84,7 @@
     </ProjectReference>
     </ProjectReference>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="ApiClient.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </EmbeddedResource>
+    <EmbeddedResource Include="ApiClient.js" />
     <Content Include="dashboard-ui\advancedserversettings.html">
     <Content Include="dashboard-ui\advancedserversettings.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.245" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.247" targetFramework="net45" />
 </packages>
 </packages>