Browse Source

fixes #912 - Add special views for Dlna

Luke Pulverenti 10 years ago
parent
commit
6f45ea0823
44 changed files with 1329 additions and 485 deletions
  1. 3 3
      MediaBrowser.Api/Images/ImageService.cs
  2. 1 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  3. 2 0
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  4. 23 4
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  5. 1 0
      MediaBrowser.Api/Playback/StreamState.cs
  6. 18 118
      MediaBrowser.Api/TvShowsService.cs
  7. 1 1
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  8. 1 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  9. 30 0
      MediaBrowser.Controller/Channels/Channel.cs
  10. 30 0
      MediaBrowser.Controller/Channels/ChannelFolderItem.cs
  11. 0 2
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  12. 2 2
      MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
  13. 2 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  14. 25 5
      MediaBrowser.Controller/Entities/Folder.cs
  15. 35 0
      MediaBrowser.Controller/Entities/UserItemsQuery.cs
  16. 13 0
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  17. 24 56
      MediaBrowser.Controller/Entities/UserView.cs
  18. 614 0
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  19. 9 0
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  20. 4 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  21. 24 0
      MediaBrowser.Controller/TV/ITVSeriesManager.cs
  22. 2 9
      MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
  23. 59 190
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  24. 35 17
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  25. 6 3
      MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
  26. 4 2
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  27. 5 2
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  28. 13 1
      MediaBrowser.Dlna/Ssdp/Datagram.cs
  29. 9 7
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  30. 1 1
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  31. 21 0
      MediaBrowser.Model/Entities/CollectionType.cs
  32. 1 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  33. 1 1
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  34. 1 1
      MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
  35. 7 7
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  36. 20 3
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  37. 18 0
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  38. 3 2
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  39. 202 0
      MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
  40. 11 4
      MediaBrowser.ServerApplication/ApplicationHost.cs
  41. 42 34
      MediaBrowser.ServerApplication/LibraryViewer.cs
  42. 1 1
      MediaBrowser.ServerApplication/MainStartup.cs
  43. 4 4
      MediaBrowser.ServerApplication/ServerNotifyIcon.cs
  44. 1 1
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 3 - 3
MediaBrowser.Api/Images/ImageService.cs

@@ -39,8 +39,8 @@ namespace MediaBrowser.Api.Images
 
     [Route("/Items/{Id}/Images/{Type}", "GET")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
-    [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "GET")]
-    [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "HEAD")]
+    [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "GET")]
+    [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "HEAD")]
     [Api(Description = "Gets an item image")]
     public class GetItemImage : ImageRequest
     {
@@ -583,7 +583,7 @@ namespace MediaBrowser.Api.Images
                 Width = request.Width,
                 OutputFormat = request.Format,
                 AddPlayedIndicator = request.AddPlayedIndicator,
-                PercentPlayed = request.PercentPlayed,
+                PercentPlayed = request.PercentPlayed ?? 0,
                 UnplayedCount = request.UnplayedCount,
                 BackgroundColor = request.BackgroundColor
             };

+ 1 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -169,7 +169,7 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 2 - 0
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1516,6 +1516,7 @@ namespace MediaBrowser.Api.Playback
                 state.RunTimeTicks = item.RunTimeTicks;
                 state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
                 state.InputBitrate = mediaSource.Bitrate;
+                state.InputFileSize = mediaSource.Size;
                 mediaStreams = mediaSource.MediaStreams;
             }
             else
@@ -1530,6 +1531,7 @@ namespace MediaBrowser.Api.Playback
                 state.MediaPath = mediaSource.Path;
                 state.InputProtocol = mediaSource.Protocol;
                 state.InputContainer = mediaSource.Container;
+                state.InputFileSize = mediaSource.Size;
                 state.InputBitrate = mediaSource.Bitrate;
 
                 if (item is Video)

+ 23 - 4
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Linq;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
@@ -153,7 +154,25 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 using (state)
                 {
-                    var throttleLimit = state.InputBitrate.HasValue ? (state.InputBitrate.Value / 8) : 0;
+                    var limits = new List<long>();
+                    if (state.InputBitrate.HasValue)
+                    {
+                        // Bytes per second
+                        limits.Add((state.InputBitrate.Value / 8));
+                    }
+                    if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue)
+                    {
+                        var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds;
+
+                        if (totalSeconds > 1)
+                        {
+                            var timeBasedLimit = state.InputFileSize.Value / totalSeconds;
+                            limits.Add(Convert.ToInt64(timeBasedLimit));
+                        }
+                    }
+
+                    // Take the greater of the above to methods, just to be safe
+                    var throttleLimit = limits.Count > 0 ? limits.Max() : 0;
 
                     return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
@@ -166,8 +185,8 @@ namespace MediaBrowser.Api.Playback.Progressive
                         // Pad by 20% to play it safe
                         ThrottleLimit = Convert.ToInt64(1.2 * throttleLimit),
 
-                        // Three minutes
-                        MinThrottlePosition = throttleLimit * 180
+                        // 3.5 minutes
+                        MinThrottlePosition = throttleLimit * 210
                     });
                 }
             }

+ 1 - 0
MediaBrowser.Api/Playback/StreamState.cs

@@ -69,6 +69,7 @@ namespace MediaBrowser.Api.Playback
         public long? RunTimeTicks;
 
         public long? InputBitrate { get; set; }
+        public long? InputFileSize { get; set; }
 
         public string OutputAudioSync = "1";
         public string OutputVideoSync = "vfr";

+ 18 - 118
MediaBrowser.Api/TvShowsService.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
@@ -26,7 +27,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <value>The user id.</value>
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
@@ -195,6 +196,7 @@ namespace MediaBrowser.Api
 
         private readonly IItemRepository _itemRepo;
         private readonly IDtoService _dtoService;
+        private readonly ITVSeriesManager _tvSeriesManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TvShowsService" /> class.
@@ -202,13 +204,14 @@ namespace MediaBrowser.Api
         /// <param name="userManager">The user manager.</param>
         /// <param name="userDataManager">The user data repository.</param>
         /// <param name="libraryManager">The library manager.</param>
-        public TvShowsService(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService)
+        public TvShowsService(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, ITVSeriesManager tvSeriesManager)
         {
             _userManager = userManager;
             _userDataManager = userDataManager;
             _libraryManager = libraryManager;
             _itemRepo = itemRepo;
             _dtoService = dtoService;
+            _tvSeriesManager = tvSeriesManager;
         }
 
         /// <summary>
@@ -270,129 +273,26 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetNextUpEpisodes request)
         {
-            var user = _userManager.GetUserById(request.UserId);
-
-            var itemsList = GetNextUpEpisodes(request)
-                .ToList();
+            var result = _tvSeriesManager.GetNextUp(new NextUpQuery
+            {
+                Limit = request.Limit,
+                ParentId = request.ParentId,
+                SeriesId = request.SeriesId,
+                StartIndex = request.StartIndex,
+                UserId = request.UserId
+            });
 
-            var pagedItems = ApplyPaging(itemsList, request.StartIndex, request.Limit);
+            var user = _userManager.GetUserById(new Guid(request.UserId));
 
             var fields = request.GetItemFields().ToList();
 
-            var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray();
+            var returnItems = result.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray();
 
-            var result = new ItemsResult
+            return ToOptimizedSerializedResultUsingCache(new ItemsResult
             {
-                TotalRecordCount = itemsList.Count,
+                TotalRecordCount = result.TotalRecordCount,
                 Items = returnItems
-            };
-
-            return ToOptimizedSerializedResultUsingCache(result);
-        }
-
-        public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request)
-        {
-            var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId)
-                .OfType<Series>();
-
-            // Avoid implicitly captured closure
-            return GetNextUpEpisodes(request, items);
-        }
-
-        public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request, IEnumerable<Series> series)
-        {
-            var user = _userManager.GetUserById(request.UserId);
-
-            // Avoid implicitly captured closure
-            var currentUser = user;
-
-            return FilterSeries(request, series)
-                .AsParallel()
-                .Select(i => GetNextUp(i, currentUser))
-                .Where(i => i.Item1 != null)
-                .OrderByDescending(i =>
-                {
-                    var episode = i.Item1;
-
-                    var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey());
-
-                    if (seriesUserData.IsFavorite)
-                    {
-                        return 2;
-                    }
-
-                    if (seriesUserData.Likes.HasValue)
-                    {
-                        return seriesUserData.Likes.Value ? 1 : -1;
-                    }
-
-                    return 0;
-                })
-                .ThenByDescending(i => i.Item2)
-                .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
-                .Select(i => i.Item1);
-        }
-
-        /// <summary>
-        /// Gets the next up.
-        /// </summary>
-        /// <param name="series">The series.</param>
-        /// <param name="user">The user.</param>
-        /// <returns>Task{Episode}.</returns>
-        private Tuple<Episode, DateTime> GetNextUp(Series series, User user)
-        {
-            // Get them in display order, then reverse
-            var allEpisodes = series.GetSeasons(user, true, true)
-                .SelectMany(i => i.GetEpisodes(user, true, true))
-                .Reverse()
-                .ToList();
-
-            Episode lastWatched = null;
-            var lastWatchedDate = DateTime.MinValue;
-            Episode nextUp = null;
-
-            // Go back starting with the most recent episodes
-            foreach (var episode in allEpisodes)
-            {
-                var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey());
-
-                if (userData.Played)
-                {
-                    if (lastWatched != null || nextUp == null)
-                    {
-                        break;
-                    }
-
-                    lastWatched = episode;
-                    lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue;
-                }
-                else
-                {
-                    if (episode.LocationType != LocationType.Virtual)
-                    {
-                        nextUp = episode;
-                    }
-                }
-            }
-
-            if (lastWatched != null)
-            {
-                return new Tuple<Episode, DateTime>(nextUp, lastWatchedDate);
-            }
-
-            return new Tuple<Episode, DateTime>(null, lastWatchedDate);
-        }
-
-        private IEnumerable<Series> FilterSeries(GetNextUpEpisodes request, IEnumerable<Series> items)
-        {
-            if (!string.IsNullOrWhiteSpace(request.SeriesId))
-            {
-                var id = new Guid(request.SeriesId);
-
-                items = items.Where(i => i.Id == id);
-            }
-
-            return items;
+            });
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -122,7 +122,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <PropertyGroup>
     <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
 xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i

+ 1 - 1
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -116,7 +116,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <PropertyGroup>
     <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
 xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i

+ 30 - 0
MediaBrowser.Controller/Channels/Channel.cs

@@ -1,6 +1,10 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Querying;
 using System;
 using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -17,5 +21,31 @@ namespace MediaBrowser.Controller.Channels
             
             return base.IsVisible(user);
         }
+
+        public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
+        {
+            try
+            {
+                // Don't blow up here because it could cause parent screens with other content to fail
+                return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery
+                {
+                    ChannelId = Id.ToString("N"),
+                    Limit = query.Limit,
+                    StartIndex = query.StartIndex,
+                    UserId = query.User.Id.ToString("N"),
+                    SortBy = query.SortBy,
+                    SortOrder = query.SortOrder
+
+                }, CancellationToken.None);
+            }
+            catch
+            {
+                // Already logged at lower levels
+                return new QueryResult<BaseItem>
+                {
+
+                };
+            }
+        }
     }
 }

+ 30 - 0
MediaBrowser.Controller/Channels/ChannelFolderItem.cs

@@ -1,6 +1,9 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Querying;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -33,5 +36,32 @@ namespace MediaBrowser.Controller.Channels
         {
             return ExternalId;
         }
+
+        public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
+        {
+            try
+            {
+                // Don't blow up here because it could cause parent screens with other content to fail
+                return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery
+                {
+                    ChannelId = ChannelId,
+                    FolderId = Id.ToString("N"),
+                    Limit = query.Limit,
+                    StartIndex = query.StartIndex,
+                    UserId = query.User.Id.ToString("N"),
+                    SortBy = query.SortBy,
+                    SortOrder = query.SortOrder
+
+                }, CancellationToken.None);
+            }
+            catch
+            {
+                // Already logged at lower levels
+                return new QueryResult<BaseItem>
+                {
+
+                };
+            }
+        }
     }
 }

+ 0 - 2
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -11,8 +11,6 @@ namespace MediaBrowser.Controller.Channels
 {
     public class ChannelVideoItem : Video, IChannelMediaItem
     {
-        public static IChannelManager ChannelManager { get; set; }
-
         public string ExternalId { get; set; }
 
         public string ChannelId { get; set; }

+ 2 - 2
MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs

@@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Drawing
 
         public int? UnplayedCount { get; set; }
 
-        public double? PercentPlayed { get; set; }
+        public double PercentPlayed { get; set; }
 
         public string BackgroundColor { get; set; }
 
@@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Drawing
             return (!Quality.HasValue || Quality.Value == 100) &&
                 IsOutputFormatDefault(originalImagePath) &&
                 !AddPlayedIndicator &&
-                !PercentPlayed.HasValue &&
+                PercentPlayed.Equals(0) &&
                 !UnplayedCount.HasValue &&
                 string.IsNullOrEmpty(BackgroundColor);
         }

+ 2 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -240,6 +241,7 @@ namespace MediaBrowser.Controller.Entities
         public static IFileSystem FileSystem { get; set; }
         public static IUserDataManager UserDataManager { get; set; }
         public static ILiveTvManager LiveTvManager { get; set; }
+        public static IChannelManager ChannelManager { get; set; }
 
         /// <summary>
         /// Returns a <see cref="System.String" /> that represents this instance.

+ 25 - 5
MediaBrowser.Controller/Entities/Folder.cs

@@ -6,6 +6,8 @@ using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
+using MoreLinq;
 using System;
 using System.Collections;
 using System.Collections.Generic;
@@ -14,7 +16,6 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
-using MoreLinq;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -24,6 +25,7 @@ namespace MediaBrowser.Controller.Entities
     public class Folder : BaseItem, IHasThemeMedia, IHasTags
     {
         public static IUserManager UserManager { get; set; }
+        public static IUserViewManager UserViewManager { get; set; }
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
@@ -770,6 +772,24 @@ namespace MediaBrowser.Controller.Entities
             return item;
         }
 
+        public virtual Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
+        {
+            var user = query.User;
+
+            var items = query.Recursive
+                ? GetRecursiveChildren(user)
+                : GetChildren(user, true);
+
+            var result = SortAndFilter(items, query);
+
+            return Task.FromResult(result);
+        }
+
+        protected QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items, UserItemsQuery query)
+        {
+            return UserViewBuilder.SortAndFilter(items, null, query, LibraryManager, UserDataManager);
+        }
+
         /// <summary>
         /// Gets allowed children of an item
         /// </summary>
@@ -944,7 +964,7 @@ namespace MediaBrowser.Controller.Entities
                 .OfType<CollectionFolder>()
                 .SelectMany(i => i.PhysicalLocations)
                 .ToList();
-            
+
             return LinkedChildren
                 .Select(i =>
                 {
@@ -985,10 +1005,10 @@ namespace MediaBrowser.Controller.Entities
         /// Gets the linked children.
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        public IEnumerable<Tuple<LinkedChild,BaseItem>> GetLinkedChildrenInfos()
+        public IEnumerable<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos()
         {
             return LinkedChildren
-                .Select(i => new Tuple<LinkedChild,BaseItem>(i, GetLinkedChild(i)))
+                .Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i)))
                 .Where(i => i.Item2 != null);
         }
 
@@ -1183,7 +1203,7 @@ namespace MediaBrowser.Controller.Entities
                 var isUnplayed = true;
 
                 var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey());
-                
+
                 // Incrememt totalPercentPlayed
                 if (itemUserData != null)
                 {

+ 35 - 0
MediaBrowser.Controller/Entities/UserItemsQuery.cs

@@ -0,0 +1,35 @@
+using MediaBrowser.Model.Entities;
+using System;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public class UserItemsQuery
+    {
+        public bool Recursive { get; set; }
+
+        public int? StartIndex { get; set; }
+
+        public int? Limit { get; set; }
+
+        public string[] SortBy { get; set; }
+
+        public SortOrder SortOrder { get; set; }
+
+        public User User { get; set; }
+
+        public Func<BaseItem, User, bool> Filter { get; set; }
+
+        public bool? IsFolder { get; set; }
+        public bool? IsFavorite { get; set; }
+        public bool? IsPlayed { get; set; }
+        public bool? IsResumable { get; set; }
+
+        public string[] MediaTypes { get; set; }
+        
+        public UserItemsQuery()
+        {
+            SortBy = new string[] { };
+            MediaTypes = new string[] { };
+        }
+    }
+}

+ 13 - 0
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Library;
+using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -14,6 +16,17 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class UserRootFolder : Folder
     {
+        public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
+        {
+            var result = await UserViewManager.GetUserViews(new UserViewQuery
+            {
+                UserId = query.User.Id.ToString("N")
+
+            }, CancellationToken.None).ConfigureAwait(false);
+
+            return SortAndFilter(result, query);
+        }
+
         /// <summary>
         /// Get the children of this folder from the actual file system
         /// </summary>

+ 24 - 56
MediaBrowser.Controller/Entities/UserView.cs

@@ -1,12 +1,9 @@
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities
@@ -14,56 +11,37 @@ namespace MediaBrowser.Controller.Entities
     public class UserView : Folder
     {
         public string ViewType { get; set; }
-        public static IUserViewManager UserViewManager { get; set; }
+        public Guid ParentId { get; set; }
 
-        public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
+        public static ITVSeriesManager TVSeriesManager;
+
+        public override Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query)
         {
-            var mediaFolders = GetMediaFolders(user);
+            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager)
+                .GetUserItems(this, ViewType, query);
+        }
 
-            switch (ViewType)
+        public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
+        {
+            var result = GetUserItems(new UserItemsQuery
             {
-                case CollectionType.LiveTvChannels:
-                    return LiveTvManager.GetInternalChannels(new LiveTvChannelQuery
-                    {
-                        UserId = user.Id.ToString("N")
-
-                    }, CancellationToken.None).Result.Items;
-                case CollectionType.LiveTvRecordingGroups:
-                    return LiveTvManager.GetInternalRecordings(new RecordingQuery
-                    {
-                        UserId = user.Id.ToString("N"),
-                        Status = RecordingStatus.Completed
-
-                    }, CancellationToken.None).Result.Items;
-                case CollectionType.LiveTv:
-                    return GetLiveTvFolders(user).Result;
-                case CollectionType.Folders:
-                    return user.RootFolder.GetChildren(user, includeLinkedChildren);
-                case CollectionType.Games:
-                    return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
-                        .OfType<GameSystem>();
-                case CollectionType.BoxSets:
-                    return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
-                        .OfType<BoxSet>();
-                case CollectionType.TvShows:
-                    return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
-                        .OfType<Series>();
-                case CollectionType.Trailers:
-                    return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
-                        .OfType<Trailer>();
-                default:
-                    return mediaFolders.SelectMany(i => i.GetChildren(user, includeLinkedChildren));
-            }
+                User = user
+
+            }).Result;
+
+            return result.Items;
         }
 
-        private async Task<IEnumerable<BaseItem>> GetLiveTvFolders(User user)
+        public override IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
         {
-            var list = new List<BaseItem>();
+            var result = GetUserItems(new UserItemsQuery
+            {
+                User = user,
+                Recursive = true
 
-            list.Add(await UserViewManager.GetUserView(CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
-            list.Add(await UserViewManager.GetUserView(CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
+            }).Result;
 
-            return list;
+            return result.Items;
         }
 
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
@@ -71,16 +49,6 @@ namespace MediaBrowser.Controller.Entities
             return GetChildren(user, false);
         }
 
-        private IEnumerable<Folder> GetMediaFolders(User user)
-        {
-            var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
-
-            return user.RootFolder
-                .GetChildren(user, true, true)
-                .OfType<Folder>()
-                .Where(i => !excludeFolderIds.Contains(i.Id) && !IsExcludedFromGrouping(i));
-        }
-
         public static bool IsExcludedFromGrouping(Folder folder)
         {
             var standaloneTypes = new List<string>

+ 614 - 0
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -0,0 +1,614 @@
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.TV;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Querying;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public class UserViewBuilder
+    {
+        private readonly IChannelManager _channelManager;
+        private readonly ILiveTvManager _liveTvManager;
+        private readonly IUserViewManager _userViewManager;
+        private readonly ILibraryManager _libraryManager;
+        private readonly ILogger _logger;
+        private readonly IUserDataManager _userDataManager;
+        private readonly ITVSeriesManager _tvSeriesManager;
+
+        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager)
+        {
+            _userViewManager = userViewManager;
+            _liveTvManager = liveTvManager;
+            _channelManager = channelManager;
+            _libraryManager = libraryManager;
+            _logger = logger;
+            _userDataManager = userDataManager;
+            _tvSeriesManager = tvSeriesManager;
+        }
+
+        public async Task<QueryResult<BaseItem>> GetUserItems(Folder parent, string viewType, UserItemsQuery query)
+        {
+            var user = query.User;
+
+            switch (viewType)
+            {
+                case CollectionType.Channels:
+                    {
+                        var result = await _channelManager.GetChannelsInternal(new ChannelQuery
+                        {
+                            UserId = user.Id.ToString("N"),
+                            Limit = query.Limit,
+                            StartIndex = query.StartIndex
+
+                        }, CancellationToken.None).ConfigureAwait(false);
+
+                        return GetResult(result);
+                    }
+
+                case CollectionType.LiveTvChannels:
+                    {
+                        var result = await _liveTvManager.GetInternalChannels(new LiveTvChannelQuery
+                        {
+                            UserId = query.User.Id.ToString("N"),
+                            Limit = query.Limit,
+                            StartIndex = query.StartIndex
+
+                        }, CancellationToken.None).ConfigureAwait(false);
+
+                        return GetResult(result);
+                    }
+
+                case CollectionType.LiveTvNowPlaying:
+                    {
+                        var result = await _liveTvManager.GetRecommendedProgramsInternal(new RecommendedProgramQuery
+                        {
+                            UserId = query.User.Id.ToString("N"),
+                            Limit = query.Limit,
+                            IsAiring = true
+
+                        }, CancellationToken.None).ConfigureAwait(false);
+
+                        return GetResult(result);
+                    }
+
+                case CollectionType.LiveTvRecordingGroups:
+                    {
+                        var result = await _liveTvManager.GetInternalRecordings(new RecordingQuery
+                        {
+                            UserId = query.User.Id.ToString("N"),
+                            Status = RecordingStatus.Completed,
+                            Limit = query.Limit,
+                            StartIndex = query.StartIndex
+
+                        }, CancellationToken.None).ConfigureAwait(false);
+
+                        return GetResult(result);
+                    }
+
+                case CollectionType.LiveTv:
+                    {
+                        var result = await GetLiveTvFolders(user).ConfigureAwait(false);
+
+                        return GetResult(result, query);
+                    }
+
+                case CollectionType.Folders:
+                    return GetResult(user.RootFolder.GetChildren(user, true), query);
+
+                case CollectionType.Games:
+                    return await GetGameView(user, parent, query).ConfigureAwait(false);
+
+                case CollectionType.BoxSets:
+                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetRecursiveChildren(user)).OfType<BoxSet>(), query);
+
+                case CollectionType.TvShows:
+                    return await GetTvView(parent, user, query).ConfigureAwait(false);
+
+                case CollectionType.Music:
+                    return await GetMusicFolders(parent, user, query).ConfigureAwait(false);
+
+                case CollectionType.Movies:
+                    return await GetMovieFolders(parent, user, query).ConfigureAwait(false);
+
+                case CollectionType.GameGenres:
+                    return GetGameGenres(parent, user, query);
+
+                case CollectionType.GameSystems:
+                    return GetGameSystems(parent, user, query);
+
+                case CollectionType.LatestGames:
+                    return GetLatestGames(parent, user, query);
+
+                case CollectionType.RecentlyPlayedGames:
+                    return GetRecentlyPlayedGames(parent, user, query);
+
+                case CollectionType.GameFavorites:
+                    return GetFavoriteGames(parent, user, query);
+
+                case CollectionType.TvSeries:
+                    return GetTvSeries(parent, user, query);
+
+                case CollectionType.TvGenres:
+                    return GetTvGenres(parent, user, query);
+
+                case CollectionType.TvResume:
+                    return GetTvResume(parent, user, query);
+
+                case CollectionType.TvNextUp:
+                    return GetTvNextUp(parent, query);
+
+                case CollectionType.TvLatest:
+                    return GetTvLatest(parent, user, query);
+
+                case CollectionType.MovieFavorites:
+                    return GetFavoriteMovies(parent, user, query);
+
+                case CollectionType.MovieLatest:
+                    return GetMovieLatest(parent, user, query);
+
+                case CollectionType.MovieGenres:
+                    return GetMovieGenres(parent, user, query);
+
+                case CollectionType.MovieResume:
+                    return GetMovieResume(parent, user, query);
+
+                case CollectionType.MovieMovies:
+                    return GetMovieMovies(parent, user, query);
+
+                case CollectionType.MovieCollections:
+                    return GetMovieCollections(parent, user, query);
+
+                default:
+                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), query);
+            }
+        }
+
+        private int GetSpecialItemsLimit()
+        {
+            return 50;
+        }
+
+        private async Task<QueryResult<BaseItem>> GetMusicFolders(Folder parent, User user, UserItemsQuery query)
+        {
+            if (query.Recursive)
+            {
+                return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }), query);
+            }
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).OfType<MusicArtist>(), query);
+        }
+
+        private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, UserItemsQuery query)
+        {
+            if (query.Recursive)
+            {
+                return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie || i is BoxSet), query);
+            }
+
+            var list = new List<BaseItem>();
+
+            list.Add(await GetUserView(CollectionType.MovieResume, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.MovieLatest, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
+
+            return GetResult(list, query);
+        }
+
+        private QueryResult<BaseItem> GetFavoriteMovies(Folder parent, User user, UserItemsQuery query)
+        {
+            query.IsFavorite = true;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query);
+        }
+
+        private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, UserItemsQuery query)
+        {
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query);
+        }
+
+        private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, UserItemsQuery query)
+        {
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is BoxSet), query);
+        }
+
+        private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, UserItemsQuery query)
+        {
+            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
+            query.SortOrder = SortOrder.Descending;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), GetSpecialItemsLimit(), query);
+        }
+
+        private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, UserItemsQuery query)
+        {
+            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
+            query.SortOrder = SortOrder.Descending;
+            query.IsResumable = true;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), GetSpecialItemsLimit(), query);
+        }
+        
+        private QueryResult<BaseItem> GetMovieGenres(Folder parent, User user, UserItemsQuery query)
+        {
+            var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
+                .Where(i => i is Movie)
+                .SelectMany(i => i.Genres)
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .Select(i =>
+                {
+                    try
+                    {
+                        return _libraryManager.GetGenre(i);
+                    }
+                    catch
+                    {
+                        // Full exception logged at lower levels
+                        _logger.Error("Error getting genre");
+                        return null;
+                    }
+
+                })
+                .Where(i => i != null);
+
+            return GetResult(genres, query);
+        }
+
+        private async Task<QueryResult<BaseItem>> GetTvView(Folder parent, User user, UserItemsQuery query)
+        {
+            if (query.Recursive)
+            {
+                return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Series || i is Season || i is Episode), query);
+            }
+
+            var list = new List<BaseItem>();
+
+            list.Add(await GetUserView(CollectionType.TvResume, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.TvNextUp, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.TvSeries, user, "3", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(CollectionType.TvFavorites, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
+
+            return GetResult(list, query);
+        }
+
+        private async Task<QueryResult<BaseItem>> GetGameView(User user, Folder parent, UserItemsQuery query)
+        {
+            if (query.Recursive)
+            {
+                return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }), query);
+            }
+
+            var list = new List<BaseItem>();
+
+            list.Add(await GetUserView(CollectionType.LatestGames, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
+
+            return GetResult(list, query);
+        }
+
+        private QueryResult<BaseItem> GetLatestGames(Folder parent, User user, UserItemsQuery query)
+        {
+            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
+            query.SortOrder = SortOrder.Descending;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), GetSpecialItemsLimit(), query);
+        }
+
+        private QueryResult<BaseItem> GetRecentlyPlayedGames(Folder parent, User user, UserItemsQuery query)
+        {
+            query.IsPlayed = true;
+            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
+            query.SortOrder = SortOrder.Descending;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), GetSpecialItemsLimit(), query);
+        }
+
+        private QueryResult<BaseItem> GetFavoriteGames(Folder parent, User user, UserItemsQuery query)
+        {
+            query.IsFavorite = true;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), query);
+        }
+
+        private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, UserItemsQuery query)
+        {
+            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
+            query.SortOrder = SortOrder.Descending;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Episode>(), GetSpecialItemsLimit(), query);
+        }
+
+        private QueryResult<BaseItem> GetTvNextUp(Folder parent, UserItemsQuery query)
+        {
+            var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows, string.Empty });
+            
+            var result = _tvSeriesManager.GetNextUp(new NextUpQuery
+            {
+                Limit = query.Limit,
+                StartIndex = query.StartIndex,
+                UserId = query.User.Id.ToString("N")
+
+            }, parentFolders);
+
+            return result;
+        }
+
+        private QueryResult<BaseItem> GetTvResume(Folder parent, User user, UserItemsQuery query)
+        {
+            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
+            query.SortOrder = SortOrder.Descending;
+            query.IsResumable = true;
+
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Episode>(), GetSpecialItemsLimit(), query);
+        }
+
+        private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, UserItemsQuery query)
+        {
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Series>(), query);
+        }
+
+        private QueryResult<BaseItem> GetTvGenres(Folder parent, User user, UserItemsQuery query)
+        {
+            var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty })
+                .OfType<Series>()
+                .SelectMany(i => i.Genres)
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .Select(i =>
+                {
+                    try
+                    {
+                        return _libraryManager.GetGenre(i);
+                    }
+                    catch
+                    {
+                        // Full exception logged at lower levels
+                        _logger.Error("Error getting genre");
+                        return null;
+                    }
+
+                })
+                .Where(i => i != null);
+
+            return GetResult(genres, query);
+        }
+
+        private QueryResult<BaseItem> GetGameSystems(Folder parent, User user, UserItemsQuery query)
+        {
+            return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<GameSystem>(), query);
+        }
+
+        private QueryResult<BaseItem> GetGameGenres(Folder parent, User user, UserItemsQuery query)
+        {
+            var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Games })
+                .OfType<Game>()
+                .SelectMany(i => i.Genres)
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .Select(i =>
+                {
+                    try
+                    {
+                        return _libraryManager.GetGameGenre(i);
+                    }
+                    catch
+                    {
+                        // Full exception logged at lower levels
+                        _logger.Error("Error getting game genre");
+                        return null;
+                    }
+
+                })
+                .Where(i => i != null);
+
+            return GetResult(genres, query);
+        }
+
+        private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result)
+            where T : BaseItem
+        {
+            return new QueryResult<BaseItem>
+            {
+                Items = result.Items,
+                TotalRecordCount = result.TotalRecordCount
+            };
+        }
+
+        private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
+            UserItemsQuery query)
+            where T : BaseItem
+        {
+            return GetResult(items, null, query);
+        }
+
+        private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
+            int? totalRecordLimit,
+            UserItemsQuery query)
+            where T : BaseItem
+        {
+            return SortAndFilter(items, totalRecordLimit, query, _libraryManager, _userDataManager);
+        }
+
+        public static QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items,
+            int? totalRecordLimit,
+            UserItemsQuery query,
+            ILibraryManager libraryManager,
+            IUserDataManager userDataManager)
+        {
+            var user = query.User;
+
+            items = items.Where(i => Filter(i, user, query, userDataManager));
+
+            return Sort(items, totalRecordLimit, query, libraryManager);
+        }
+
+        public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items,
+            int? totalRecordLimit,
+            UserItemsQuery query,
+            ILibraryManager libraryManager)
+        {
+            var user = query.User;
+
+            items = libraryManager.ReplaceVideosWithPrimaryVersions(items);
+
+            if (query.SortBy.Length > 0)
+            {
+                items = libraryManager.Sort(items, user, query.SortBy, query.SortOrder);
+            }
+
+            var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
+            var totalCount = itemsArray.Length;
+
+            if (query.Limit.HasValue)
+            {
+                itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray();
+            }
+            else if (query.StartIndex.HasValue)
+            {
+                itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
+            }
+
+            return new QueryResult<BaseItem>
+            {
+                TotalRecordCount = totalCount,
+                Items = itemsArray
+            };
+        }
+
+        private static bool Filter(BaseItem item, User user, UserItemsQuery query, IUserDataManager userDataManager)
+        {
+            if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (query.IsFolder.HasValue && query.IsFolder.Value != item.IsFolder)
+            {
+                return false;
+            }
+
+            if (query.Filter != null && !query.Filter(item, user))
+            {
+                return false;
+            }
+
+            UserItemData userData = null;
+
+            if (query.IsFavorite.HasValue)
+            {
+                userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+
+                if (userData.IsFavorite != query.IsFavorite.Value)
+                {
+                    return false;
+                }
+            }
+
+            if (query.IsResumable.HasValue)
+            {
+                userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+                var isResumable = userData.PlaybackPositionTicks > 0;
+
+                if (isResumable != query.IsResumable.Value)
+                {
+                    return false;
+                }
+            }
+
+            if (query.IsPlayed.HasValue)
+            {
+                if (item.IsPlayed(user) != query.IsPlayed.Value)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private IEnumerable<Folder> GetMediaFolders(User user)
+        {
+            var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
+
+            return user.RootFolder
+                .GetChildren(user, true, true)
+                .OfType<Folder>()
+                .Where(i => !excludeFolderIds.Contains(i.Id) && !UserView.IsExcludedFromGrouping(i));
+        }
+
+        private IEnumerable<Folder> GetMediaFolders(User user, string[] viewTypes)
+        {
+            return GetMediaFolders(user)
+                .Where(i =>
+                {
+                    var folder = i as ICollectionFolder;
+
+                    return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+                });
+        }
+
+        private IEnumerable<Folder> GetMediaFolders(Folder parent, User user, string[] viewTypes)
+        {
+            if (parent == null || parent is UserView)
+            {
+                return GetMediaFolders(user, viewTypes);
+            }
+
+            return new[] { parent };
+        }
+
+        private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, string[] viewTypes)
+        {
+            if (parent == null || parent is UserView)
+            {
+                return GetMediaFolders(user, viewTypes).SelectMany(i => i.GetRecursiveChildren(user));
+            }
+
+            return parent.GetRecursiveChildren(user);
+        }
+
+        private async Task<IEnumerable<BaseItem>> GetLiveTvFolders(User user)
+        {
+            var list = new List<BaseItem>();
+
+            list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvNowPlaying, user, "0", CancellationToken.None).ConfigureAwait(false));
+            list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
+            list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
+
+            return list;
+        }
+
+        private async Task<UserView> GetUserView(string type, User user, string sortName, Folder parent)
+        {
+            var view = await _userViewManager.GetUserView(type, user, sortName, CancellationToken.None)
+                        .ConfigureAwait(false);
+
+            if (parent.Id != view.ParentId)
+            {
+                view.ParentId = parent.Id;
+                await view.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None)
+                        .ConfigureAwait(false);
+            }
+
+            return view;
+        }
+    }
+}

+ 9 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -244,6 +244,15 @@ namespace MediaBrowser.Controller.LiveTv
         Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query,
             CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Gets the recommended programs internal.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;QueryResult&lt;LiveTvProgram&gt;&gt;.</returns>
+        Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query,
+            CancellationToken cancellationToken);
+
         /// <summary>
         /// Gets the live tv information.
         /// </summary>

+ 4 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -157,7 +157,9 @@
     <Compile Include="Entities\IHasAwards.cs" />
     <Compile Include="Entities\Photo.cs" />
     <Compile Include="Entities\PhotoAlbum.cs" />
+    <Compile Include="Entities\UserItemsQuery.cs" />
     <Compile Include="Entities\UserView.cs" />
+    <Compile Include="Entities\UserViewBuilder.cs" />
     <Compile Include="FileOrganization\IFileOrganizationService.cs" />
     <Compile Include="Library\DeleteOptions.cs" />
     <Compile Include="Library\ILibraryPostScanTask.cs" />
@@ -336,6 +338,7 @@
     <Compile Include="Sync\ISyncRepository.cs" />
     <Compile Include="Themes\IAppThemeManager.cs" />
     <Compile Include="Themes\InternalThemeImage.cs" />
+    <Compile Include="TV\ITVSeriesManager.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
@@ -360,7 +363,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
     <PreBuildEvent>
     </PreBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 24 - 0
MediaBrowser.Controller/TV/ITVSeriesManager.cs

@@ -0,0 +1,24 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Querying;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.TV
+{
+    public interface ITVSeriesManager
+    {
+        /// <summary>
+        /// Gets the next up.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> GetNextUp(NextUpQuery query);
+
+        /// <summary>
+        /// Gets the next up.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="parentsFolders">The parents folders.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> GetNextUp(NextUpQuery request, IEnumerable<Folder> parentsFolders);
+    }
+}

+ 2 - 9
MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
@@ -22,8 +21,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly IDlnaManager _dlna;
         private readonly IServerConfigurationManager _config;
         private readonly IUserManager _userManager;
-        private readonly IUserViewManager _userViewManager;
-        private readonly IChannelManager _channelManager;
 
         public ContentDirectory(IDlnaManager dlna,
             IUserDataManager userDataManager,
@@ -32,7 +29,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             IServerConfigurationManager config,
             IUserManager userManager,
             ILogger logger,
-            IHttpClient httpClient, IUserViewManager userViewManager, IChannelManager channelManager)
+            IHttpClient httpClient)
             : base(logger, httpClient)
         {
             _dlna = dlna;
@@ -41,8 +38,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _libraryManager = libraryManager;
             _config = config;
             _userManager = userManager;
-            _userViewManager = userViewManager;
-            _channelManager = channelManager;
         }
 
         private int SystemUpdateId
@@ -78,9 +73,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 _userDataManager,
                 user,
                 SystemUpdateId,
-                _config,
-                _userViewManager,
-                _channelManager)
+                _config)
                 .ProcessControlRequest(request);
         }
 

+ 59 - 190
MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

@@ -1,26 +1,18 @@
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Dlna.Didl;
 using MediaBrowser.Dlna.Server;
 using MediaBrowser.Dlna.Service;
-using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
@@ -45,21 +37,17 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly DidlBuilder _didlBuilder;
 
         private readonly DeviceProfile _profile;
-        private readonly IUserViewManager _userViewManager;
-        private readonly IChannelManager _channelManager;
 
-        public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, IUserViewManager userViewManager, IChannelManager channelManager)
+        public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config)
             : base(config, logger)
         {
             _libraryManager = libraryManager;
             _userDataManager = userDataManager;
             _user = user;
             _systemUpdateId = systemUpdateId;
-            _userViewManager = userViewManager;
-            _channelManager = channelManager;
             _profile = profile;
 
-            _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress);
+            _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, userDataManager);
         }
 
         protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
@@ -202,7 +190,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 {
 
 
-                    var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
+                    var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
                     totalCount = childrenResult.TotalRecordCount;
 
                     result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter, id));
@@ -213,7 +201,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             {
                 var folder = (Folder)item;
 
-                var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
+                var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
                 totalCount = childrenResult.TotalRecordCount;
 
                 provided = childrenResult.Items.Length;
@@ -223,7 +211,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
                     if (i.IsFolder)
                     {
                         var f = (Folder)i;
-                        var childCount = (await GetChildrenSorted(f, user, sortCriteria, null, 0).ConfigureAwait(false))
+                        var childCount = (await GetUserItems(f, user, sortCriteria, null, 0).ConfigureAwait(false))
                             .TotalRecordCount;
 
                         result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter));
@@ -321,216 +309,97 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
         private async Task<QueryResult<BaseItem>> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
         {
-            // TODO: Make a recursive version of GetChildrenSorted (although sorting isn't needed)
-            var result = folder.GetRecursiveChildren(user, true);
+            var sortOrders = new List<string>();
+            if (!folder.IsPreSorted)
+            {
+                sortOrders.Add(ItemSortBy.SortName);
+            }
 
-            var items = FilterUnsupportedContent(result);
+            var mediaTypes = new List<string>();
+            bool? isFolder = null;
 
             if (search.SearchType == SearchType.Audio)
             {
-                items = items.OfType<Audio>();
+                mediaTypes.Add(MediaType.Audio);
+                isFolder = false;
             }
             else if (search.SearchType == SearchType.Video)
             {
-                items = items.OfType<Video>();
+                mediaTypes.Add(MediaType.Video);
+                isFolder = false;
             }
             else if (search.SearchType == SearchType.Image)
             {
-                items = items.OfType<Photo>();
+                mediaTypes.Add(MediaType.Photo);
+                isFolder = false;
             }
             else if (search.SearchType == SearchType.Playlist)
             {
-                items = items.OfType<Playlist>();
+                //items = items.OfType<Playlist>();
+                isFolder = true;
             }
             else if (search.SearchType == SearchType.MusicAlbum)
             {
-                items = items.OfType<MusicAlbum>();
+                //items = items.OfType<MusicAlbum>();
+                isFolder = true;
             }
-
-            items = SortItems(items, user, sort);
-
-            return ToResult(items, startIndex, limit);
+            
+            return await folder.GetUserItems(new UserItemsQuery
+            {
+                Limit = limit,
+                StartIndex = startIndex,
+                SortBy = sortOrders.ToArray(),
+                SortOrder = sort.SortOrder,
+                User = user,
+                Recursive = true,
+                Filter = FilterUnsupportedContent,
+                IsFolder = isFolder,
+                MediaTypes = mediaTypes.ToArray()
+
+            }).ConfigureAwait(false);
         }
 
-        private async Task<QueryResult<BaseItem>> GetChildrenSorted(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit)
+        private async Task<QueryResult<BaseItem>> GetUserItems(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit)
         {
-            if (folder is UserRootFolder)
-            {
-                var result = await _userViewManager.GetUserViews(new UserViewQuery
-                {
-                    UserId = user.Id.ToString("N")
-
-                }, CancellationToken.None).ConfigureAwait(false);
-
-                return ToResult(result, startIndex, limit);
-            }
-
-            var view = folder as UserView;
-
-            if (view != null)
+            var sortOrders = new List<string>();
+            if (!folder.IsPreSorted)
             {
-                var result = await GetUserViewChildren(view, user, sort).ConfigureAwait(false);
-
-                return ToResult(result, startIndex, limit);
+                sortOrders.Add(ItemSortBy.SortName);
             }
 
-            var channel = folder as Channel;
-
-            if (channel != null)
+            return await folder.GetUserItems(new UserItemsQuery
             {
-                try
-                {
-                    // Don't blow up here because it could cause parent screens with other content to fail
-                    return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
-                    {
-                        ChannelId = channel.Id.ToString("N"),
-                        Limit = limit,
-                        StartIndex = startIndex,
-                        UserId = user.Id.ToString("N")
-
-                    }, CancellationToken.None);
-                }
-                catch
-                {
-                    // Already logged at lower levels
-                }
-            }
+                Limit = limit,
+                StartIndex = startIndex,
+                SortBy = sortOrders.ToArray(),
+                SortOrder = sort.SortOrder,
+                User = user,
+                Filter = FilterUnsupportedContent
 
-            var channelFolderItem = folder as ChannelFolderItem;
-
-            if (channelFolderItem != null)
-            {
-                try
-                {
-                    // Don't blow up here because it could cause parent screens with other content to fail
-                    return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
-                    {
-                        ChannelId = channelFolderItem.ChannelId,
-                        FolderId = channelFolderItem.Id.ToString("N"),
-                        Limit = limit,
-                        StartIndex = startIndex,
-                        UserId = user.Id.ToString("N")
-
-                    }, CancellationToken.None);
-                }
-                catch
-                {
-                    // Already logged at lower levels
-                }
-            }
-
-            return ToResult(GetPlainFolderChildrenSorted(folder, user, sort), startIndex, limit);
+            }).ConfigureAwait(false);
         }
 
-        private QueryResult<BaseItem> ToResult(IEnumerable<BaseItem> items, int? startIndex, int? limit)
+        private bool FilterUnsupportedContent(BaseItem i, User user)
         {
-            var list = items.ToArray();
-            var totalCount = list.Length;
-
-            if (startIndex.HasValue)
+            // Unplayable
+            if (i.LocationType == LocationType.Virtual && !i.IsFolder)
             {
-                list = list.Skip(startIndex.Value).ToArray();
+                return false;
             }
 
-            if (limit.HasValue)
+            // Unplayable
+            var supportsPlaceHolder = i as ISupportsPlaceHolders;
+            if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
             {
-                list = list.Take(limit.Value).ToArray();
+                return false;
             }
 
-            return new QueryResult<BaseItem>
+            if (i is Game || i is Book)
             {
-                Items = list,
-                TotalRecordCount = totalCount
-            };
-        }
-
-        private async Task<IEnumerable<BaseItem>> GetUserViewChildren(UserView folder, User user, SortCriteria sort)
-        {
-            if (string.Equals(folder.ViewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase))
-            {
-                var result = await _channelManager.GetChannelsInternal(new ChannelQuery()
-                {
-                    UserId = user.Id.ToString("N")
-
-                }, CancellationToken.None).ConfigureAwait(false);
-
-                return result.Items;
+                //return false;
             }
-            if (string.Equals(folder.ViewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetChildren(user, true).OfType<Series>(), user, sort);
-            }
-            if (string.Equals(folder.ViewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetRecursiveChildren(user, true).OfType<Movie>(), user, sort);
-            }
-            if (string.Equals(folder.ViewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetChildren(user, true).OfType<MusicArtist>(), user, sort);
-            }
-            if (string.Equals(folder.ViewType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetChildren(user, true), user, sort);
-            }
-            if (string.Equals(folder.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetChildren(user, true), user, sort);
-            }
-            if (string.Equals(folder.ViewType, CollectionType.LiveTvRecordingGroups, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetChildren(user, true), user, sort);
-            }
-            if (string.Equals(folder.ViewType, CollectionType.LiveTvChannels, StringComparison.OrdinalIgnoreCase))
-            {
-                return SortItems(folder.GetChildren(user, true), user, sort);
-            }
-
-            return GetPlainFolderChildrenSorted(folder, user, sort);
-        }
-
-        private IEnumerable<BaseItem> GetPlainFolderChildrenSorted(Folder folder, User user, SortCriteria sort)
-        {
-            var items = folder.GetChildren(user, true);
-
-            items = FilterUnsupportedContent(items);
-
-            if (folder.IsPreSorted)
-            {
-                return items;
-            }
-
-            return SortItems(items, user, sort);
-        }
-
-        private IEnumerable<BaseItem> SortItems(IEnumerable<BaseItem> items, User user, SortCriteria sort)
-        {
-            return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, sort.SortOrder);
-        }
-
-        private IEnumerable<BaseItem> FilterUnsupportedContent(IEnumerable<BaseItem> items)
-        {
-            return items.Where(i =>
-            {
-                // Unplayable
-                if (i.LocationType == LocationType.Virtual && !i.IsFolder)
-                {
-                    return false;
-                }
-
-                // Unplayable
-                var supportsPlaceHolder = i as ISupportsPlaceHolders;
-                if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
-                {
-                    return false;
-                }
-
-                if (i is Game || i is Book)
-                {
-                    return false;
-                }
 
-                return true;
-            });
+            return true;
         }
 
         private BaseItem GetItemFromObjectId(string id, User user)

+ 35 - 17
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -1,4 +1,4 @@
-using System.IO;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Drawing;
@@ -6,16 +6,16 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
 using System;
-using System.Collections.Generic;
 using System.Globalization;
+using System.IO;
 using System.Linq;
 using System.Xml;
-using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Dlna.Didl
 {
@@ -32,12 +32,14 @@ namespace MediaBrowser.Dlna.Didl
         private readonly IImageProcessor _imageProcessor;
         private readonly string _serverAddress;
         private readonly User _user;
+        private readonly IUserDataManager _userDataManager;
 
-        public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress)
+        public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, IUserDataManager userDataManager)
         {
             _profile = profile;
             _imageProcessor = imageProcessor;
             _serverAddress = serverAddress;
+            _userDataManager = userDataManager;
             _user = user;
         }
 
@@ -677,7 +679,20 @@ namespace MediaBrowser.Dlna.Didl
 
             var result = element.OwnerDocument;
 
-            var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
+            var playbackPercentage = 0;
+
+            if (item is Video)
+            {
+                var userData = _userDataManager.GetUserDataDto(item, _user);
+
+                playbackPercentage = Convert.ToInt32(userData.PlayedPercentage ?? 0);
+                if (playbackPercentage >= 100)
+                {
+                    playbackPercentage = 0;
+                }
+            }
+
+            var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, "jpg");
 
             var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
             var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
@@ -687,7 +702,7 @@ namespace MediaBrowser.Dlna.Didl
             element.AppendChild(icon);
 
             // TOOD: Remove these default values
-            var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
+            var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, "jpg");
             icon = result.CreateElement("upnp", "icon", NS_UPNP);
             icon.InnerText = iconUrlInfo.Url;
             element.AppendChild(icon);
@@ -703,18 +718,19 @@ namespace MediaBrowser.Dlna.Didl
                 }
             }
 
-            AddImageResElement(item, element, 4096, 4096, "jpg", "JPEG_LRG");
-            AddImageResElement(item, element, 4096, 4096, "png", "PNG_LRG");
-            AddImageResElement(item, element, 1024, 768, "jpg", "JPEG_MED");
-            AddImageResElement(item, element, 640, 480, "jpg", "JPEG_SM");
-            AddImageResElement(item, element, 160, 160, "jpg", "JPEG_TN");
-            AddImageResElement(item, element, 160, 160, "png", "PNG_TN");
+            AddImageResElement(item, element, 4096, 4096, playbackPercentage, "jpg", "JPEG_LRG");
+            AddImageResElement(item, element, 4096, 4096, playbackPercentage, "png", "PNG_LRG");
+            AddImageResElement(item, element, 1024, 768, playbackPercentage, "jpg", "JPEG_MED");
+            AddImageResElement(item, element, 640, 480, playbackPercentage, "jpg", "JPEG_SM");
+            AddImageResElement(item, element, 160, 160, playbackPercentage, "jpg", "JPEG_TN");
+            AddImageResElement(item, element, 160, 160, playbackPercentage, "png", "PNG_TN");
         }
 
         private void AddImageResElement(BaseItem item, 
             XmlElement element, 
             int maxWidth, 
             int maxHeight, 
+            int playbackPercentage,
             string format, 
             string org_Pn)
         {
@@ -727,7 +743,7 @@ namespace MediaBrowser.Dlna.Didl
 
             var result = element.OwnerDocument;
 
-            var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
+            var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, format);
 
             var res = result.CreateElement(string.Empty, "res", NS_DIDL);
 
@@ -849,16 +865,18 @@ namespace MediaBrowser.Dlna.Didl
             internal int? Height;
         }
 
-        private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
+        private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, int playbackPercentage, string format)
         {
-            var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}",
+            var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/{7}",
                 _serverAddress,
                 info.ItemId,
                 info.Type,
                 info.ImageTag,
                 format,
-                maxWidth,
-                maxHeight);
+                maxWidth.ToString(CultureInfo.InvariantCulture),
+                maxHeight.ToString(CultureInfo.InvariantCulture),
+                playbackPercentage.ToString(CultureInfo.InvariantCulture)
+                );
 
             var width = info.Width;
             var height = info.Height;

+ 6 - 3
MediaBrowser.Dlna/Main/DlnaEntryPoint.cs

@@ -34,14 +34,15 @@ namespace MediaBrowser.Dlna.Main
         private readonly IUserManager _userManager;
         private readonly IDlnaManager _dlnaManager;
         private readonly IImageProcessor _imageProcessor;
-        
+        private readonly IUserDataManager _userDataManager;
+
         private SsdpHandler _ssdpHandler;
         private DeviceDiscovery _deviceDiscovery;
 
         private readonly List<string> _registeredServerIds = new List<string>();
         private bool _dlnaServerStarted;
 
-        public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor)
+        public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor, IUserDataManager userDataManager)
         {
             _config = config;
             _appHost = appHost;
@@ -53,6 +54,7 @@ namespace MediaBrowser.Dlna.Main
             _userManager = userManager;
             _dlnaManager = dlnaManager;
             _imageProcessor = imageProcessor;
+            _userDataManager = userDataManager;
             _logger = logManager.GetLogger("Dlna");
         }
 
@@ -218,7 +220,8 @@ namespace MediaBrowser.Dlna.Main
                         _imageProcessor,
                         _deviceDiscovery,
                         _httpClient,
-                        _config);
+                        _config,
+                        _userDataManager);
 
                     _manager.Start();
                 }

+ 4 - 2
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -32,6 +32,7 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IDlnaManager _dlnaManager;
         private readonly IUserManager _userManager;
         private readonly IImageProcessor _imageProcessor;
+        private readonly IUserDataManager _userDataManager;
 
         private readonly DeviceDiscovery _deviceDiscovery;
         private readonly string _serverAddress;
@@ -51,7 +52,7 @@ namespace MediaBrowser.Dlna.PlayTo
 
         private Timer _updateTimer;
 
-        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery)
+        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery, IUserDataManager userDataManager)
         {
             _session = session;
             _itemRepository = itemRepository;
@@ -62,6 +63,7 @@ namespace MediaBrowser.Dlna.PlayTo
             _imageProcessor = imageProcessor;
             _serverAddress = serverAddress;
             _deviceDiscovery = deviceDiscovery;
+            _userDataManager = userDataManager;
             _logger = logger;
         }
 
@@ -474,7 +476,7 @@ namespace MediaBrowser.Dlna.PlayTo
 
             playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
 
-            var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
+            var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress, _userDataManager).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
 
             playlistItem.Didl = itemXml;
 

+ 5 - 2
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -29,10 +29,11 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IImageProcessor _imageProcessor;
         private readonly IHttpClient _httpClient;
         private readonly IServerConfigurationManager _config;
+        private readonly IUserDataManager _userDataManager;
 
         private readonly DeviceDiscovery _deviceDiscovery;
         
-        public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config)
+        public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager)
         {
             _logger = logger;
             _sessionManager = sessionManager;
@@ -45,6 +46,7 @@ namespace MediaBrowser.Dlna.PlayTo
             _deviceDiscovery = deviceDiscovery;
             _httpClient = httpClient;
             _config = config;
+            _userDataManager = userDataManager;
         }
 
         public void Start()
@@ -103,7 +105,8 @@ namespace MediaBrowser.Dlna.PlayTo
                             _userManager,
                             _imageProcessor,
                             serverAddress,
-                            _deviceDiscovery);
+                            _deviceDiscovery,
+                            _userDataManager);
 
                         controller.Init(device);
 

+ 13 - 1
MediaBrowser.Dlna/Ssdp/Datagram.cs

@@ -22,6 +22,8 @@ namespace MediaBrowser.Dlna.Ssdp
         /// </summary>
         public int SendCount { get; private set; }
 
+        public bool HandleBindError { get; set; }
+
         private readonly ILogger _logger;
 
         public Datagram(IPEndPoint toEndPoint, IPEndPoint fromEndPoint, ILogger logger, string message, int totalSendCount)
@@ -42,7 +44,17 @@ namespace MediaBrowser.Dlna.Ssdp
 
                 if (FromEndPoint != null)
                 {
-                    client.Bind(FromEndPoint);
+                    try
+                    {
+                        client.Bind(FromEndPoint);
+                    }
+                    catch
+                    {
+                        if (!HandleBindError)
+                        {
+                            throw;
+                        }
+                    }
                 }
 
                 client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, ToEndPoint, result =>

+ 9 - 7
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

@@ -65,7 +65,7 @@ namespace MediaBrowser.Dlna.Ssdp
                 string mx = null;
                 args.Headers.TryGetValue("mx", out mx);
                 int delaySeconds;
-                if (!string.IsNullOrWhiteSpace(mx) && 
+                if (!string.IsNullOrWhiteSpace(mx) &&
                     int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds)
                     && delaySeconds > 0)
                 {
@@ -124,18 +124,23 @@ namespace MediaBrowser.Dlna.Ssdp
             IPEndPoint localAddress,
             int sendCount = 1)
         {
-            SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
+            SendDatagram(header, values, _ssdpEndp, localAddress, false, sendCount);
         }
 
         public void SendDatagram(string header,
             Dictionary<string, string> values,
             IPEndPoint endpoint,
             IPEndPoint localAddress,
+            bool handleBindError,
             int sendCount = 1)
         {
             var msg = new SsdpMessageBuilder().BuildMessage(header, values);
 
-            var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
+            var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount)
+            {
+                HandleBindError = handleBindError
+            };
+
             if (_messageQueue.Count == 0)
             {
                 dgram.Send();
@@ -170,10 +175,7 @@ namespace MediaBrowser.Dlna.Ssdp
                     values["ST"] = d.Type;
                     values["USN"] = d.USN;
 
-                    // Commenting this out because binding to the local ipendpoint often throws an error
-                    //SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0));
-
-                    SendDatagram(header, values, endpoint, null);
+                    SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0), true);
 
                     if (_config.GetDlnaConfiguration().EnableDebugLogging)
                     {

+ 1 - 1
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -91,7 +91,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 21 - 0
MediaBrowser.Model/Entities/CollectionType.cs

@@ -26,7 +26,28 @@
         public const string Playlists = "playlists";
         public const string Folders = "folders";
 
+        public const string LiveTvNowPlaying = "LiveTvNowPlaying";
         public const string LiveTvChannels = "LiveTvChannels";
         public const string LiveTvRecordingGroups = "LiveTvRecordingGroups";
+
+        public const string TvSeries = "TvSeries";
+        public const string TvGenres = "TvGenres";
+        public const string TvLatest = "TvLatest";
+        public const string TvNextUp = "TvNextUp";
+        public const string TvResume = "TvResume";
+        public const string TvFavorites = "TvFavorites";
+
+        public const string MovieLatest = "MovieLatest";
+        public const string MovieResume = "MovieResume";
+        public const string MovieMovies = "MovieMovies";
+        public const string MovieCollections = "MovieCollections";
+        public const string MovieFavorites = "MovieFavorites";
+        public const string MovieGenres = "MovieGenres";
+        
+        public const string LatestGames = "LatestGames";
+        public const string RecentlyPlayedGames = "RecentlyPlayedGames";
+        public const string GameSystems = "GameSystems";
+        public const string GameGenres = "GameGenres";
+        public const string GameFavorites = "GameFavorites";
     }
 }

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

@@ -372,7 +372,7 @@
 xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
 )</PostBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <Import Project="Fody.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 1 - 1
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -211,7 +211,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 1 - 1
MediaBrowser.Server.Implementations/Connect/ConnectManager.cs

@@ -198,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Connect
 
         private string GetConnectUrl(string handler)
         {
-            return "http://mb3admin.com/admin/connect/" + handler;
+            return "http://mediabrowser.tv:8095/" + handler;
         }
     }
 }

+ 7 - 7
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -196,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
             try
             {
-                var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue;
+                var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0;
 
                 using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
@@ -308,7 +308,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// <param name="options">The options.</param>
         private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options)
         {
-            if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && !options.PercentPlayed.HasValue)
+            if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0))
             {
                 return;
             }
@@ -328,11 +328,11 @@ namespace MediaBrowser.Server.Implementations.Drawing
                     new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value);
                 }
 
-                if (options.PercentPlayed.HasValue)
+                if (options.PercentPlayed >= 0)
                 {
                     var currentImageSize = new Size(imageWidth, imageHeight);
 
-                    new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed.Value);
+                    new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed);
                 }
             }
             catch (Exception ex)
@@ -437,7 +437,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// <summary>
         /// Gets the cache file path based on a set of parameters
         /// </summary>
-        private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double? percentPlayed, int? unwatchedCount, string backgroundColor)
+        private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor)
         {
             var filename = originalPath;
 
@@ -462,9 +462,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
                 hasIndicator = true;
             }
 
-            if (percentPlayed.HasValue)
+            if (percentPlayed > 0)
             {
-                filename += "p=" + percentPlayed.Value;
+                filename += "p=" + percentPlayed;
                 hasIndicator = true;
             }
 

+ 20 - 3
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -726,7 +726,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return result;
         }
 
-        public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
+        public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
         {
             IEnumerable<LiveTvProgram> programs = _programs.Values;
 
@@ -771,7 +771,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false);
 
-            var returnArray = programList
+            var returnArray = programList.ToArray();
+
+            var result = new QueryResult<LiveTvProgram>
+            {
+                Items = returnArray,
+                TotalRecordCount = returnArray.Length
+            };
+
+            return result;
+        }
+
+        public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
+        {
+            var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false);
+
+            var user = _userManager.GetUserById(new Guid(query.UserId));
+
+            var returnArray = internalResult.Items
                 .Select(i =>
                 {
                     var channel = GetChannel(i);
@@ -785,7 +802,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var result = new QueryResult<ProgramInfoDto>
             {
                 Items = returnArray,
-                TotalRecordCount = returnArray.Length
+                TotalRecordCount = internalResult.TotalRecordCount
             };
 
             return result;

+ 18 - 0
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -841,6 +841,24 @@
     "ViewTypeBoxSets": "Collections",
     "ViewTypeChannels": "Channels",
     "ViewTypeLiveTV": "Live TV",
+    "ViewTypeLiveTvNowPlaying": "Now Airing",
+    "ViewTypeLatestGames":  "Latest Games",
+    "ViewTypeRecentlyPlayedGames":  "Recently Played",
+    "ViewTypeGameFavorites":  "Favorites",
+    "ViewTypeGameSystems":  "Game Systems",
+    "ViewTypeGameGenres":  "Genres",
+    "ViewTypeTvResume":  "Resume",
+    "ViewTypeTvNextUp":  "Next Up",
+    "ViewTypeTvLatest":  "Latest",
+    "ViewTypeTvSeries":  "Series",
+    "ViewTypeTvGenres":  "Genres",
+    "ViewTypeTvFavorites":  "Favorites",
+    "ViewTypeMovieResume":  "Resume",
+    "ViewTypeMovieLatest":  "Latest",
+    "ViewTypeMovieMovies":  "Movies",
+    "ViewTypeMovieCollections":  "Collections",
+    "ViewTypeMovieFavorites":  "Favorites",
+    "ViewTypeMovieGenres":  "Genres",
     "HeaderOtherDisplaySettings": "Display Settings",
     "HeaderMyViews": "My Views",
     "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:",

+ 3 - 2
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -293,6 +293,7 @@
     <Compile Include="Sync\SyncManager.cs" />
     <Compile Include="Sync\SyncRepository.cs" />
     <Compile Include="Themes\AppThemeManager.cs" />
+    <Compile Include="TV\TVSeriesManager.cs" />
     <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" />
     <Compile Include="Udp\UdpServer.cs" />
   </ItemGroup>
@@ -500,7 +501,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">
@@ -508,4 +509,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
+</Project>

+ 202 - 0
MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs

@@ -0,0 +1,202 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.TV;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.TV
+{
+    public class TVSeriesManager : ITVSeriesManager
+    {
+        private readonly IUserManager _userManager;
+        private readonly IUserDataManager _userDataManager;
+        private readonly ILibraryManager _libraryManager;
+
+        public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager)
+        {
+            _userManager = userManager;
+            _userDataManager = userDataManager;
+            _libraryManager = libraryManager;
+        }
+
+        public QueryResult<BaseItem> GetNextUp(NextUpQuery request)
+        {
+            var user = _userManager.GetUserById(new Guid(request.UserId));
+
+            if (user == null)
+            {
+                throw new ArgumentException("User not found");
+            }
+
+            var parentIds = string.IsNullOrEmpty(request.ParentId)
+                ? new string[] { }
+                : new[] { request.ParentId };
+
+            var items = GetAllLibraryItems(user, parentIds)
+                .OfType<Series>();
+
+            // Avoid implicitly captured closure
+            var episodes = GetNextUpEpisodes(request, user, items);
+
+            return GetResult(episodes, null, request);
+        }
+
+        public QueryResult<BaseItem> GetNextUp(NextUpQuery request, IEnumerable<Folder> parentsFolders)
+        {
+            var user = _userManager.GetUserById(new Guid(request.UserId));
+
+            if (user == null)
+            {
+                throw new ArgumentException("User not found");
+            }
+
+            var items = parentsFolders.SelectMany(i => i.GetRecursiveChildren(user))
+                .OfType<Series>();
+
+            // Avoid implicitly captured closure
+            var episodes = GetNextUpEpisodes(request, user, items);
+
+            return GetResult(episodes, null, request);
+        }
+
+        private IEnumerable<BaseItem> GetAllLibraryItems(User user, string[] parentIds)
+        {
+            if (parentIds.Length > 0)
+            {
+                return parentIds.SelectMany(i =>
+                {
+                    var folder = (Folder)_libraryManager.GetItemById(new Guid(i));
+
+                    return folder.GetRecursiveChildren(user);
+
+                });
+            }
+
+            if (user == null)
+            {
+                throw new ArgumentException("User not found");
+            }
+
+            return user.RootFolder.GetRecursiveChildren(user);
+        }
+
+        public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<Series> series)
+        {
+            // Avoid implicitly captured closure
+            var currentUser = user;
+
+            return FilterSeries(request, series)
+                .AsParallel()
+                .Select(i => GetNextUp(i, currentUser))
+                .Where(i => i.Item1 != null)
+                .OrderByDescending(i =>
+                {
+                    var episode = i.Item1;
+
+                    var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey());
+
+                    if (seriesUserData.IsFavorite)
+                    {
+                        return 2;
+                    }
+
+                    if (seriesUserData.Likes.HasValue)
+                    {
+                        return seriesUserData.Likes.Value ? 1 : -1;
+                    }
+
+                    return 0;
+                })
+                .ThenByDescending(i => i.Item2)
+                .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
+                .Select(i => i.Item1);
+        }
+
+        /// <summary>
+        /// Gets the next up.
+        /// </summary>
+        /// <param name="series">The series.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>Task{Episode}.</returns>
+        private Tuple<Episode, DateTime> GetNextUp(Series series, User user)
+        {
+            // Get them in display order, then reverse
+            var allEpisodes = series.GetSeasons(user, true, true)
+                .SelectMany(i => i.GetEpisodes(user, true, true))
+                .Reverse()
+                .ToList();
+
+            Episode lastWatched = null;
+            var lastWatchedDate = DateTime.MinValue;
+            Episode nextUp = null;
+
+            // Go back starting with the most recent episodes
+            foreach (var episode in allEpisodes)
+            {
+                var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey());
+
+                if (userData.Played)
+                {
+                    if (lastWatched != null || nextUp == null)
+                    {
+                        break;
+                    }
+
+                    lastWatched = episode;
+                    lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue;
+                }
+                else
+                {
+                    if (episode.LocationType != LocationType.Virtual)
+                    {
+                        nextUp = episode;
+                    }
+                }
+            }
+
+            if (lastWatched != null)
+            {
+                return new Tuple<Episode, DateTime>(nextUp, lastWatchedDate);
+            }
+
+            return new Tuple<Episode, DateTime>(null, lastWatchedDate);
+        }
+
+        private IEnumerable<Series> FilterSeries(NextUpQuery request, IEnumerable<Series> items)
+        {
+            if (!string.IsNullOrWhiteSpace(request.SeriesId))
+            {
+                var id = new Guid(request.SeriesId);
+
+                items = items.Where(i => i.Id == id);
+            }
+
+            return items;
+        }
+
+        private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, int? totalRecordLimit, NextUpQuery query)
+        {
+            var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
+            var totalCount = itemsArray.Length;
+
+            if (query.Limit.HasValue)
+            {
+                itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray();
+            }
+            else if (query.StartIndex.HasValue)
+            {
+                itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
+            }
+
+            return new QueryResult<BaseItem>
+            {
+                TotalRecordCount = totalCount,
+                Items = itemsArray
+            };
+        }
+    }
+}

+ 11 - 4
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -38,6 +38,7 @@ using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Controller.Sync;
 using MediaBrowser.Controller.Themes;
+using MediaBrowser.Controller.TV;
 using MediaBrowser.Dlna;
 using MediaBrowser.Dlna.ConnectionManager;
 using MediaBrowser.Dlna.ContentDirectory;
@@ -79,6 +80,7 @@ using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Session;
 using MediaBrowser.Server.Implementations.Sync;
 using MediaBrowser.Server.Implementations.Themes;
+using MediaBrowser.Server.Implementations.TV;
 using MediaBrowser.ServerApplication.FFMpeg;
 using MediaBrowser.ServerApplication.IO;
 using MediaBrowser.ServerApplication.Native;
@@ -213,10 +215,11 @@ namespace MediaBrowser.ServerApplication
         private ISubtitleManager SubtitleManager { get; set; }
         private IChapterManager ChapterManager { get; set; }
 
-        private IUserViewManager UserViewManager { get; set; }
+        internal IUserViewManager UserViewManager { get; set; }
 
         private IAuthenticationRepository AuthenticationRepository { get; set; }
         private ISyncRepository SyncRepository { get; set; }
+        private ITVSeriesManager TVSeriesManager { get; set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ApplicationHost" /> class.
@@ -466,6 +469,9 @@ namespace MediaBrowser.ServerApplication
             ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, Logger, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager);
             RegisterSingleInstance(ChannelManager);
 
+            TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager);
+            RegisterSingleInstance(TVSeriesManager);
+
             var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
             RegisterSingleInstance<IAppThemeManager>(appThemeManager);
 
@@ -487,7 +493,7 @@ namespace MediaBrowser.ServerApplication
             UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager, ApplicationPaths, playlistManager);
             RegisterSingleInstance(UserViewManager);
 
-            var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, UserViewManager, ChannelManager);
+            var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient);
             RegisterSingleInstance<IContentDirectory>(contentDirectory);
 
             NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
@@ -682,9 +688,10 @@ namespace MediaBrowser.ServerApplication
             Folder.UserManager = UserManager;
             BaseItem.FileSystem = FileSystemManager;
             BaseItem.UserDataManager = UserDataManager;
-            ChannelVideoItem.ChannelManager = ChannelManager;
+            BaseItem.ChannelManager = ChannelManager;
             BaseItem.LiveTvManager = LiveTvManager;
-            UserView.UserViewManager = UserViewManager;
+            Folder.UserViewManager = UserViewManager;
+            UserView.TVSeriesManager = TVSeriesManager;
         }
 
         /// <summary>

+ 42 - 34
MediaBrowser.ServerApplication/LibraryViewer.cs

@@ -1,6 +1,9 @@
-using MediaBrowser.Controller.Entities;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Serialization;
 using System;
@@ -16,17 +19,17 @@ namespace MediaBrowser.ServerApplication
     {
         private readonly IJsonSerializer _jsonSerializer;
         private readonly ILibraryManager _libraryManager;
-        private readonly IItemRepository _itemRepository;
+        private readonly IUserViewManager _userViewManager;
 
         private User _currentUser;
 
-        public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IItemRepository itemRepo)
+        public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IUserViewManager userViewManager)
         {
             InitializeComponent();
 
             _jsonSerializer = jsonSerializer;
             _libraryManager = libraryManager;
-            _itemRepository = itemRepo;
+            _userViewManager = userViewManager;
 
             foreach (var user in userManager.Users)
                 selectUser.Items.Add(user);
@@ -72,23 +75,50 @@ namespace MediaBrowser.ServerApplication
                 LoadTree();
         }
 
+        private IEnumerable<BaseItem> GetItems(Folder parent, User user)
+        {
+            if (parent == null)
+            {
+                var task = _userViewManager.GetUserViews(new UserViewQuery
+                {
+                    UserId = user.Id.ToString("N")
+
+                }, CancellationToken.None);
+
+                task.RunSynchronously();
+
+                return task.Result;
+            }
+            else
+            {
+                var task = parent.GetUserItems(new UserItemsQuery
+                {
+                    User = user,
+                    SortBy = new[] { ItemSortBy.SortName }
+
+                });
+
+                task.RunSynchronously();
+
+                return task.Result.Items;
+            }
+        }
+
         private void LoadTree()
         {
             treeView1.Nodes.Clear();
 
             var isPhysical = _currentUser.Name == "Physical";
-            IEnumerable<BaseItem> children = isPhysical ? new[] { _libraryManager.RootFolder } : _libraryManager.RootFolder.GetChildren(_currentUser, true);
-            children = OrderByName(children, _currentUser);
+            IEnumerable<BaseItem> children = isPhysical ? new[] { _libraryManager.RootFolder } : GetItems(null, _currentUser);
 
-            foreach (Folder folder in children)
+            foreach (var folder in children.OfType<Folder>())
             {
-
                 var currentFolder = folder;
 
                 var node = new TreeNode { Tag = currentFolder };
 
-                var subChildren = isPhysical ? currentFolder.Children : currentFolder.GetChildren(_currentUser, true);
-                subChildren = OrderByName(subChildren, _currentUser);
+                var subChildren = isPhysical ? currentFolder.Children.OrderBy(i => i.SortName) : GetItems(currentFolder, _currentUser);
+
                 AddChildren(node, subChildren, _currentUser, isPhysical);
                 node.Text = currentFolder.Name + " (" +
                             node.Nodes.Count + ")";
@@ -111,9 +141,9 @@ namespace MediaBrowser.ServerApplication
                 var subFolder = item as Folder;
                 if (subFolder != null)
                 {
-                    var subChildren = isPhysical ? subFolder.Children : subFolder.GetChildren(_currentUser, true);
+                    var subChildren = isPhysical ? subFolder.Children.OrderBy(i => i.SortName) : GetItems(subFolder, _currentUser);
 
-                    AddChildren(node, OrderBy(subChildren, user, ItemSortBy.SortName), user, isPhysical);
+                    AddChildren(node, subChildren, user, isPhysical);
                     node.Text = item.Name + " (" + node.Nodes.Count + ")";
                 }
                 else
@@ -124,28 +154,6 @@ namespace MediaBrowser.ServerApplication
             }
         }
 
-        /// <summary>
-        /// Orders the name of the by.
-        /// </summary>
-        /// <param name="items">The items.</param>
-        /// <param name="user">The user.</param>
-        /// <returns>IEnumerable{BaseItem}.</returns>
-        private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user)
-        {
-            return OrderBy(items, user, ItemSortBy.SortName);
-        }
-
-        /// <summary>
-        /// Orders the name of the by.
-        /// </summary>
-        /// <param name="items">The items.</param>
-        /// <param name="user">The user.</param>
-        /// <returns>IEnumerable{BaseItem}.</returns>
-        private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order)
-        {
-            return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending);
-        }
-
         /// <summary>
         /// The INDEN t_ STRING
         /// </summary>

+ 1 - 1
MediaBrowser.ServerApplication/MainStartup.cs

@@ -249,7 +249,7 @@ namespace MediaBrowser.ServerApplication
         {
             //Application.EnableVisualStyles();
             //Application.SetCompatibleTextRenderingDefault(false);
-            _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.ItemRepository, _appHost.LocalizationManager);
+            _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.LocalizationManager, _appHost.UserViewManager);
             Application.Run();
         }
 

+ 4 - 4
MediaBrowser.ServerApplication/ServerNotifyIcon.cs

@@ -39,7 +39,7 @@ namespace MediaBrowser.ServerApplication
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
         private readonly IJsonSerializer _jsonSerializer;
-        private readonly IItemRepository _itemRepository;
+        private readonly IUserViewManager _userViewManager;
         private readonly ILocalizationManager _localization;
         private LogForm _logForm;
 
@@ -61,11 +61,11 @@ namespace MediaBrowser.ServerApplication
             IServerConfigurationManager configurationManager, 
             IUserManager userManager, ILibraryManager libraryManager, 
             IJsonSerializer jsonSerializer, 
-            IItemRepository itemRepo, ILocalizationManager localization)
+            ILocalizationManager localization, IUserViewManager userViewManager)
         {
             _logger = logManager.GetLogger("MainWindow");
-            _itemRepository = itemRepo;
             _localization = localization;
+            _userViewManager = userViewManager;
             _appHost = appHost;
             _logManager = logManager;
             _configurationManager = configurationManager;
@@ -318,7 +318,7 @@ namespace MediaBrowser.ServerApplication
 
         void cmdLibraryExplorer_Click(object sender, EventArgs e)
         {
-            new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _itemRepository).Show();
+            new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _userViewManager).Show();
         }
 
         void cmdRestart_Click(object sender, EventArgs e)

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

@@ -2136,7 +2136,7 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">