浏览代码

Merge pull request #2140 from MediaBrowser/dev

Dev
Luke 8 年之前
父节点
当前提交
39e7687921

+ 6 - 1
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -155,6 +155,9 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         public bool? EnableUserData { get; set; }
 
+        public bool? IsMovie { get; set; }
+        public bool? IsSeries { get; set; }
+
         public GetRecordings()
         {
             EnableTotalRecordCount = true;
@@ -863,7 +866,9 @@ namespace MediaBrowser.Api.LiveTv
                 Status = request.Status,
                 SeriesTimerId = request.SeriesTimerId,
                 IsInProgress = request.IsInProgress,
-                EnableTotalRecordCount = request.EnableTotalRecordCount
+                EnableTotalRecordCount = request.EnableTotalRecordCount,
+                IsMovie = request.IsMovie,
+                IsSeries = request.IsSeries
 
             }, options, CancellationToken.None).ConfigureAwait(false);
 

+ 36 - 10
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -346,16 +346,32 @@ namespace MediaBrowser.Api.Playback
             var isVc1 = state.VideoStream != null &&
                 string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
 
+            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
+
             if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-preset superfast";
+                if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset))
+                {
+                    param += "-preset " + encodingOptions.H264Preset;
+                }
+                else
+                {
+                    param += "-preset superfast";
+                }
 
-                param += " -crf 23";
+                if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
+                {
+                    param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
+                }
+                else
+                {
+                    param += " -crf 23";
+                }
             }
 
             else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-preset fast";
+                param += "-preset fast";
 
                 param += " -crf 28";
             }
@@ -363,14 +379,14 @@ namespace MediaBrowser.Api.Playback
             // h264 (h264_qsv)
             else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-preset 7 -look_ahead 0";
+                param += "-preset 7 -look_ahead 0";
 
             }
 
             // h264 (h264_nvenc)
             else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-preset default";
+                param += "-preset default";
             }
 
             // webm
@@ -394,7 +410,7 @@ namespace MediaBrowser.Api.Playback
                 profileScore = Math.Min(profileScore, 2);
 
                 // http://www.webmproject.org/docs/encoder-parameters/
-                param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
+                param += string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
                     profileScore.ToString(UsCulture),
                     crf,
                     qmin,
@@ -403,18 +419,18 @@ namespace MediaBrowser.Api.Playback
 
             else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+                param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
             }
 
             // asf/wmv
             else if (string.Equals(videoCodec, "wmv2", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-qmin 2";
+                param += "-qmin 2";
             }
 
             else if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
             {
-                param = "-mbd 2";
+                param += "-mbd 2";
             }
 
             param += GetVideoBitrateParam(state, videoCodec);
@@ -1770,6 +1786,16 @@ namespace MediaBrowser.Api.Playback
             //    state.SegmentLength = 6;
             //}
 
+            if (state.VideoRequest != null)
+            {
+                if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
+                {
+                    state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+                    state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
+                        ?? state.SupportedVideoCodecs.FirstOrDefault();
+                }
+            }
+
             if (!string.IsNullOrWhiteSpace(request.AudioCodec))
             {
                 state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
@@ -2012,7 +2038,7 @@ namespace MediaBrowser.Api.Playback
             }
 
             // Source and target codecs must match
-            if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+            if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase))
             {
                 return false;
             }

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

@@ -112,6 +112,7 @@ namespace MediaBrowser.Api.Playback
         public string OutputVideoSync = "-1";
 
         public List<string> SupportedAudioCodecs { get; set; }
+        public List<string> SupportedVideoCodecs { get; set; }
         public string UserAgent { get; set; }
 
         public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
@@ -119,6 +120,7 @@ namespace MediaBrowser.Api.Playback
             _mediaSourceManager = mediaSourceManager;
             _logger = logger;
             SupportedAudioCodecs = new List<string>();
+            SupportedVideoCodecs = new List<string>();
             PlayableStreamFileNames = new List<string>();
             RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }

+ 1 - 1
MediaBrowser.Api/TvShowsService.cs

@@ -478,7 +478,7 @@ namespace MediaBrowser.Api
                 }
                 else
                 {
-                    episodes = series.GetSeasonEpisodes(user, season);
+                    episodes = series.GetSeasonEpisodes(season, user);
                 }
             }
             else

+ 2 - 29
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -85,9 +85,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
         public override int GetChildCount(User user)
         {
-            Logger.Debug("Season {0} getting child cound", (Path ?? Name));
             var result = GetChildren(user, true).Count();
-            Logger.Debug("Season {0} child cound: ", result);
 
             return result;
         }
@@ -158,13 +156,10 @@ namespace MediaBrowser.Controller.Entities.TV
 
             var id = Guid.NewGuid().ToString("N");
 
-            Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id);
             var items = GetEpisodes(user).Where(filter);
 
-            Logger.Debug("Season.GetItemsInternal entering PostFilterAndSort. Request id: " + id);
             var result = PostFilterAndSort(items, query, false, false);
 
-            Logger.Debug("Season.GetItemsInternal complete. Request id: " + id);
             return Task.FromResult(result);
         }
 
@@ -185,34 +180,12 @@ namespace MediaBrowser.Controller.Entities.TV
 
         public IEnumerable<Episode> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes)
         {
-            return series.GetSeasonEpisodes(user, this, allSeriesEpisodes);
+            return series.GetSeasonEpisodes(this, user, allSeriesEpisodes);
         }
 
         public IEnumerable<Episode> GetEpisodes()
         {
-            var episodes = GetRecursiveChildren().OfType<Episode>();
-            var series = Series;
-
-            if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
-            {
-                var seasonNumber = IndexNumber;
-                var list = episodes.ToList();
-
-                if (seasonNumber.HasValue)
-                {
-                    list.AddRange(series.GetRecursiveChildren().OfType<Episode>()
-                        .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
-                }
-                else
-                {
-                    list.AddRange(series.GetRecursiveChildren().OfType<Episode>()
-                        .Where(i => !i.ParentIndexNumber.HasValue));
-                }
-
-                episodes = list.DistinctBy(i => i.Id);
-            }
-
-            return episodes;
+            return Series.GetSeasonEpisodes(this, null, null);
         }
 
         public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)

+ 18 - 39
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -209,7 +209,6 @@ namespace MediaBrowser.Controller.Entities.TV
 
             var seriesKey = GetUniqueSeriesKey(this);
 
-            Logger.Debug("GetSeasons SeriesKey: {0}", seriesKey);
             var query = new InternalItemsQuery(user)
             {
                 AncestorWithPresentationUniqueKey = seriesKey,
@@ -267,7 +266,6 @@ namespace MediaBrowser.Controller.Entities.TV
         public IEnumerable<Episode> GetEpisodes(User user)
         {
             var seriesKey = GetUniqueSeriesKey(this);
-            Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey);
 
             var query = new InternalItemsQuery(user)
             {
@@ -291,8 +289,6 @@ namespace MediaBrowser.Controller.Entities.TV
 
             var allItems = LibraryManager.GetItemList(query).ToList();
 
-            Logger.Debug("GetEpisodes return {0} items from database", allItems.Count);
-
             var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
 
             var allEpisodes = allItems.OfType<Season>()
@@ -373,27 +369,9 @@ namespace MediaBrowser.Controller.Entities.TV
             progress.Report(100);
         }
 
-        private IEnumerable<Episode> GetAllEpisodes(User user)
-        {
-            Logger.Debug("Series.GetAllEpisodes entering GetItemList");
-
-            var result =  LibraryManager.GetItemList(new InternalItemsQuery(user)
-            {
-                AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
-                IncludeItemTypes = new[] { typeof(Episode).Name },
-                SortBy = new[] { ItemSortBy.SortName }
-
-            }).Cast<Episode>().ToList();
-
-            Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count);
-
-            return result;
-        }
-
-        public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason)
+        public IEnumerable<Episode> GetSeasonEpisodes(Season parentSeason, User user)
         {
             var seriesKey = GetUniqueSeriesKey(this);
-            Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey);
 
             var query = new InternalItemsQuery(user)
             {
@@ -401,34 +379,35 @@ namespace MediaBrowser.Controller.Entities.TV
                 IncludeItemTypes = new[] { typeof(Episode).Name },
                 SortBy = new[] { ItemSortBy.SortName }
             };
-            var config = user.Configuration;
-            if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
+            if (user != null)
             {
-                query.IsVirtualItem = false;
-            }
-            else if (!config.DisplayMissingEpisodes)
-            {
-                query.IsMissing = false;
-            }
-            else if (!config.DisplayUnairedEpisodes)
-            {
-                query.IsVirtualUnaired = false;
+                var config = user.Configuration;
+                if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
+                {
+                    query.IsVirtualItem = false;
+                }
+                else if (!config.DisplayMissingEpisodes)
+                {
+                    query.IsMissing = false;
+                }
+                else if (!config.DisplayUnairedEpisodes)
+                {
+                    query.IsVirtualUnaired = false;
+                }
             }
 
             var allItems = LibraryManager.GetItemList(query).OfType<Episode>();
 
-            return GetSeasonEpisodes(user, parentSeason, allItems);
+            return GetSeasonEpisodes(parentSeason, user, allItems);
         }
 
-        public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes)
+        public IEnumerable<Episode> GetSeasonEpisodes(Season parentSeason, User user, IEnumerable<Episode> allSeriesEpisodes)
         {
             if (allSeriesEpisodes == null)
             {
-                Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null");
-                return GetSeasonEpisodes(user, parentSeason);
+                return GetSeasonEpisodes(parentSeason, user);
             }
 
-            Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason");
             var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
 
             var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;

+ 1 - 1
MediaBrowser.Controller/Entities/Trailer.cs

@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities
 
             info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer);
 
-            if (!IsInMixedFolder)
+            if (!IsInMixedFolder && LocationType == LocationType.FileSystem)
             {
                 info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
             }

+ 2 - 1
MediaBrowser.Controller/Entities/UserView.cs

@@ -113,7 +113,8 @@ namespace MediaBrowser.Controller.Entities
         {
             var standaloneTypes = new List<string>
             {
-                CollectionType.Playlists
+                CollectionType.Playlists,
+                CollectionType.BoxSets
             };
 
             var collectionFolder = folder as ICollectionFolder;

+ 6 - 1
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -928,7 +928,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 {
                     StartProcess(processWrapper);
 
-                    ranToCompletion = process.WaitForExit(10000);
+                    var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
+                    if (timeoutMs <= 0)
+                    {
+                        timeoutMs = 10000;
+                    }
+                    ranToCompletion = process.WaitForExit(timeoutMs);
 
                     if (!ranToCompletion)
                     {

+ 4 - 0
MediaBrowser.Model/Configuration/EncodingOptions.cs

@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration
         public string HardwareAccelerationType { get; set; }
         public string EncoderAppPath { get; set; }
         public string VaapiDevice { get; set; }
+        public int H264Crf { get; set; }
+        public string H264Preset { get; set; }
 
         public EncodingOptions()
         {
@@ -19,6 +21,8 @@ namespace MediaBrowser.Model.Configuration
             ThrottleDelaySeconds = 180;
             EncodingThreadCount = -1;
             VaapiDevice = "/dev/dri/card0";
+            H264Crf = 23;
+            H264Preset = "superfast";
         }
     }
 }

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

@@ -207,6 +207,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableChannelView { get; set; }
         public bool EnableExternalContentInSuggestions { get; set; }
 
+        public int ImageExtractionTimeoutMs { get; set; }
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
@@ -216,6 +217,7 @@ namespace MediaBrowser.Model.Configuration
             Migrations = new string[] { };
             CodecsUsed = new string[] { };
             SqliteCacheSize = 0;
+            ImageExtractionTimeoutMs = 10000;
 
             EnableLocalizedGuids = true;
             DisplaySpecialsWithinSeasons = true;

+ 2 - 0
MediaBrowser.Model/LiveTv/RecordingQuery.cs

@@ -68,6 +68,8 @@ namespace MediaBrowser.Model.LiveTv
         /// <value>The fields.</value>
         public ItemFields[] Fields { get; set; }
         public bool? EnableImages { get; set; }
+        public bool? IsMovie { get; set; }
+        public bool? IsSeries { get; set; }
         public int? ImageTypeLimit { get; set; }
         public ImageType[] EnableImageTypes { get; set; }
 

+ 2 - 0
MediaBrowser.Model/System/SystemInfo.cs

@@ -8,6 +8,8 @@ namespace MediaBrowser.Model.System
     /// </summary>
     public class SystemInfo : PublicSystemInfo
     {
+        public PackageVersionClass SystemUpdateLevel { get; set; }
+
         /// <summary>
         /// Gets or sets the display name of the operating system.
         /// </summary>

+ 6 - 12
MediaBrowser.Providers/TV/DummySeasonProvider.cs

@@ -138,8 +138,6 @@ namespace MediaBrowser.Providers.TV
                 .Where(i => i.LocationType == LocationType.Virtual)
                 .ToList();
 
-            var episodes = series.GetRecursiveChildren().OfType<Episode>().ToList();
-
             var seasonsToRemove = virtualSeasons
                 .Where(i =>
                 {
@@ -152,19 +150,15 @@ namespace MediaBrowser.Providers.TV
                         {
                             return true;
                         }
+                    }
 
-                        // If there are no episodes with this season number, delete it
-                        if (episodes.All(e => !e.ParentIndexNumber.HasValue || e.ParentIndexNumber.Value != seasonNumber))
-                        {
-                            return true;
-                        }
-
-                        return false;
+                    // If there are no episodes with this season number, delete it
+                    if (!i.GetEpisodes().Any())
+                    {
+                        return true;
                     }
 
-                    // Season does not have a number
-                    // Remove if there are no episodes directly in series without a season number
-                    return episodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
+                    return false;
                 })
                 .ToList();
 

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

@@ -31,6 +31,8 @@ using CommonIO;
 using IniParser;
 using IniParser.Model;
 using MediaBrowser.Common.Events;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Server.Implementations.LiveTv
@@ -1423,6 +1425,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 return new QueryResult<BaseItem>();
             }
 
+            var includeItemTypes = new List<string>();
+            var excludeItemTypes = new List<string>();
+
+            if (query.IsMovie.HasValue)
+            {
+                if (query.IsMovie.Value)
+                {
+                    includeItemTypes.Add(typeof (Movie).Name);
+                }
+                else
+                {
+                    excludeItemTypes.Add(typeof(Movie).Name);
+                }
+            }
+            if (query.IsSeries.HasValue)
+            {
+                if (query.IsSeries.Value)
+                {
+                    includeItemTypes.Add(typeof(Episode).Name);
+                }
+                else
+                {
+                    excludeItemTypes.Add(typeof(Episode).Name);
+                }
+            }
+
             return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
                 MediaTypes = new[] { MediaType.Video },
@@ -1433,7 +1461,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Limit = Math.Min(200, query.Limit ?? int.MaxValue),
                 SortBy = new[] { ItemSortBy.DateCreated },
                 SortOrder = SortOrder.Descending,
-                EnableTotalRecordCount = query.EnableTotalRecordCount
+                EnableTotalRecordCount = query.EnableTotalRecordCount,
+                IncludeItemTypes = includeItemTypes.ToArray(),
+                ExcludeItemTypes = excludeItemTypes.ToArray()
             });
         }
 
@@ -1492,6 +1522,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 recordings = recordings.Where(i => i.Status == val);
             }
 
+            if (query.IsMovie.HasValue)
+            {
+                var val = query.IsMovie.Value;
+                recordings = recordings.Where(i => i.IsMovie == val);
+            }
+
+            if (query.IsSeries.HasValue)
+            {
+                var val = query.IsSeries.Value;
+                recordings = recordings.Where(i => i.IsSeries == val);
+            }
+
             if (!string.IsNullOrEmpty(query.SeriesTimerId))
             {
                 var guid = new Guid(query.SeriesTimerId);
@@ -1950,16 +1992,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 dto.Number = channel.Number;
                 dto.ChannelNumber = channel.Number;
                 dto.ChannelType = channel.ChannelType;
-                dto.ServiceName = GetService(channel).Name;
+                dto.ServiceName = channel.ServiceName;
 
                 if (options.Fields.Contains(ItemFields.MediaSources))
                 {
                     dto.MediaSources = channel.GetMediaSources(true).ToList();
                 }
 
-                var channelIdString = channel.Id.ToString("N");
                 if (options.AddCurrentProgram)
                 {
+                    var channelIdString = channel.Id.ToString("N");
                     var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString));
 
                     if (currentProgram != null)

+ 5 - 5
MediaBrowser.Server.Implementations/Udp/UdpServer.cs

@@ -109,11 +109,6 @@ namespace MediaBrowser.Server.Implementations.Udp
         {
             var parts = messageText.Split('|');
 
-            if (parts.Length > 1)
-            {
-                _appHost.EnableLoopback(parts[1]);
-            }
-
             var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
 
             if (!string.IsNullOrEmpty(localUrl))
@@ -126,6 +121,11 @@ namespace MediaBrowser.Server.Implementations.Udp
                 };
 
                 await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false);
+                
+                if (parts.Length > 1)
+                {
+                    _appHost.EnableLoopback(parts[1]);
+                }
             }
             else
             {

+ 14 - 2
MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs

@@ -14,17 +14,20 @@ using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Controller.LiveTv;
 
 namespace MediaBrowser.Server.Implementations.UserViews
 {
     public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
     {
         private readonly IUserManager _userManager;
+        private readonly ILibraryManager _libraryManager;
 
-        public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager)
+        public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager)
             : base(fileSystem, providerManager, applicationPaths, imageProcessor)
         {
             _userManager = userManager;
+            _libraryManager = libraryManager;
         }
 
         public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
@@ -50,7 +53,15 @@ namespace MediaBrowser.Server.Implementations.UserViews
 
             if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
             {
-                return new List<BaseItem>();
+                var programs = _libraryManager.GetItemList(new InternalItemsQuery
+                {
+                    IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
+                    ImageTypes = new[] { ImageType.Primary },
+                    Limit = 30,
+                    IsMovie = true
+                }).ToList();
+
+                return GetFinalItems(programs).ToList();
             }
 
             if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) ||
@@ -147,6 +158,7 @@ namespace MediaBrowser.Server.Implementations.UserViews
                 CollectionType.MusicVideos,
                 CollectionType.HomeVideos,
                 CollectionType.BoxSets,
+                CollectionType.LiveTv,
                 CollectionType.Playlists,
                 CollectionType.Photos,
                 string.Empty

+ 2 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -1096,7 +1096,8 @@ namespace MediaBrowser.Server.Startup.Common
                 LocalAddress = localAddress,
                 SupportsLibraryMonitor = SupportsLibraryMonitor,
                 EncoderLocationType = MediaEncoder.EncoderLocationType,
-                SystemArchitecture = NativeApp.Environment.SystemArchitecture
+                SystemArchitecture = NativeApp.Environment.SystemArchitecture,
+                SystemUpdateLevel = ConfigurationManager.CommonConfiguration.SystemUpdateLevel
             };
         }
 

+ 33 - 11
MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Implementations.Updates;
@@ -55,21 +56,39 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
             }
         }
 
-        private async Task CheckVersion(Version currentVersion, PackageVersionClass updateLevel, CancellationToken cancellationToken)
+        private async Task CheckVersion(Version currentVersion, PackageVersionClass currentUpdateLevel, CancellationToken cancellationToken)
         {
             var releases = await new GithubUpdater(_httpClient, _jsonSerializer, TimeSpan.FromMinutes(3))
                 .GetLatestReleases("MediaBrowser", "Emby", _releaseAssetFilename, cancellationToken).ConfigureAwait(false);
 
-            var newUpdateLevel = updateLevel;
+            var newUpdateLevel = GetNewUpdateLevel(currentVersion, currentUpdateLevel, releases);
+
+            if (newUpdateLevel != currentUpdateLevel)
+            {
+                _config.Configuration.SystemUpdateLevel = newUpdateLevel;
+                _config.SaveConfiguration();
+            }
+        }
+
+        private PackageVersionClass GetNewUpdateLevel(Version currentVersion, PackageVersionClass currentUpdateLevel, List<GithubUpdater.RootObject> releases)
+        {
+            var newUpdateLevel = currentUpdateLevel;
 
             // If the current version is later than current stable, set the update level to beta
             if (releases.Count >= 1)
             {
                 var release = releases[0];
                 var version = ParseVersion(release.tag_name);
-                if (version != null && currentVersion > version)
+                if (version != null)
                 {
-                    newUpdateLevel = PackageVersionClass.Beta;
+                    if (currentVersion > version)
+                    {
+                        newUpdateLevel = PackageVersionClass.Beta;
+                    }
+                    else
+                    {
+                        return PackageVersionClass.Release;
+                    }
                 }
             }
 
@@ -78,17 +97,20 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
             {
                 var release = releases[1];
                 var version = ParseVersion(release.tag_name);
-                if (version != null && currentVersion > version)
+                if (version != null)
                 {
-                    newUpdateLevel = PackageVersionClass.Dev;
+                    if (currentVersion > version)
+                    {
+                        newUpdateLevel = PackageVersionClass.Dev;
+                    }
+                    else
+                    {
+                        return PackageVersionClass.Beta;
+                    }
                 }
             }
 
-            if (newUpdateLevel != updateLevel)
-            {
-                _config.Configuration.SystemUpdateLevel = newUpdateLevel;
-                _config.SaveConfiguration();
-            }
+            return newUpdateLevel;
         }
 
         private Version ParseVersion(string versionString)

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

@@ -191,6 +191,9 @@
     <Content Include="dashboard-ui\scripts\camerauploadsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\livetvschedule.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\userpasswordpage.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>