diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 6d600c646b..55c6f6455e 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -158,22 +158,19 @@ namespace Emby.Drawing } var dateModified = options.Image.DateModified; - var length = options.Image.Length; if (options.CropWhiteSpace) { - var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified, length).ConfigureAwait(false); + var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; - length = tuple.Item3; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(new ItemImageInfo { - Length = length, DateModified = dateModified, Type = options.Image.Type, Path = originalImagePath @@ -182,7 +179,6 @@ namespace Emby.Drawing originalImagePath = tuple.Item1; dateModified = tuple.Item2; - length = tuple.Item3; } var originalImageSize = GetImageSize(originalImagePath, dateModified); @@ -199,7 +195,7 @@ namespace Emby.Drawing var quality = options.Quality ?? 90; var outputFormat = GetOutputFormat(options.OutputFormat); - var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, length, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); + var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); var semaphore = GetLock(cacheFilePath); @@ -240,11 +236,10 @@ namespace Emby.Drawing /// /// Crops whitespace from an image, caches the result, and returns the cached path /// - private async Task> GetWhitespaceCroppedImage(string originalImagePath, DateTime dateModified, long length) + private async Task> GetWhitespaceCroppedImage(string originalImagePath, DateTime dateModified) { var name = originalImagePath; name += "datemodified=" + dateModified.Ticks; - name += "length=" + length; var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath)); @@ -270,7 +265,7 @@ namespace Emby.Drawing // We have to have a catch-all here because some of the .net image methods throw a plain old Exception _logger.ErrorException("Error cropping image {0}", ex, originalImagePath); - return new Tuple(originalImagePath, dateModified, length); + return new Tuple(originalImagePath, dateModified); } finally { @@ -280,11 +275,9 @@ namespace Emby.Drawing return GetResult(croppedImagePath); } - private Tuple GetResult(string path) + private Tuple GetResult(string path) { - var file = new FileInfo(path); - - return new Tuple(path, _fileSystem.GetLastWriteTimeUtc(file), file.Length); + return new Tuple(path, _fileSystem.GetLastWriteTimeUtc(path)); } /// @@ -295,7 +288,7 @@ namespace Emby.Drawing /// /// Gets the cache file path based on a set of parameters /// - private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, long length, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor) + private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor) { var filename = originalPath; @@ -306,7 +299,6 @@ namespace Emby.Drawing filename += "quality=" + quality; filename += "datemodified=" + dateModified.Ticks; - filename += "length=" + length; filename += "f=" + format; @@ -492,17 +484,16 @@ namespace Emby.Drawing var originalImagePath = image.Path; var dateModified = image.DateModified; var imageType = image.Type; - var length = image.Length; // Optimization if (imageEnhancers.Count == 0) { - return (originalImagePath + dateModified.Ticks + string.Empty + length).GetMD5().ToString("N"); + return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N"); } // Cache name is created with supported enhancers combined with the last config change so we pick up new config changes var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList(); - cacheKeys.Add(originalImagePath + dateModified.Ticks + string.Empty + length); + cacheKeys.Add(originalImagePath + dateModified.Ticks); return string.Join("|", cacheKeys.ToArray()).GetMD5().ToString("N"); } @@ -525,7 +516,7 @@ namespace Emby.Drawing return result.Item1; } - private async Task> GetEnhancedImage(ItemImageInfo image, + private async Task> GetEnhancedImage(ItemImageInfo image, IHasImages item, int imageIndex, List enhancers) @@ -533,7 +524,6 @@ namespace Emby.Drawing var originalImagePath = image.Path; var dateModified = image.DateModified; var imageType = image.Type; - var length = image.Length; try { @@ -553,7 +543,7 @@ namespace Emby.Drawing _logger.Error("Error enhancing image", ex); } - return new Tuple(originalImagePath, dateModified, length); + return new Tuple(originalImagePath, dateModified); } /// diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index e91dd1a9b0..55aa778e2f 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -2,6 +2,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Configuration; @@ -40,6 +41,7 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; private readonly IFileSystem _fileSystem; + private readonly IMediaSourceManager _mediaSourceManager; public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1); @@ -49,12 +51,15 @@ namespace MediaBrowser.Api /// The logger. /// The session manager. /// The configuration. - public ApiEntryPoint(ILogger logger, ISessionManager sessionManager, IServerConfigurationManager config, IFileSystem fileSystem) + /// The file system. + /// The media source manager. + public ApiEntryPoint(ILogger logger, ISessionManager sessionManager, IServerConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) { Logger = logger; _sessionManager = sessionManager; _config = config; _fileSystem = fileSystem; + _mediaSourceManager = mediaSourceManager; Instance = this; } @@ -114,7 +119,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, path => true)); + Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, false, path => true)); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -133,6 +138,7 @@ namespace MediaBrowser.Api /// /// The path. /// The play session identifier. + /// The live stream identifier. /// The transcoding job identifier. /// The type. /// The process. @@ -142,6 +148,7 @@ namespace MediaBrowser.Api /// TranscodingJob. public TranscodingJob OnTranscodeBeginning(string path, string playSessionId, + string liveStreamId, string transcodingJobId, TranscodingJobType type, Process process, @@ -160,7 +167,8 @@ namespace MediaBrowser.Api DeviceId = deviceId, CancellationTokenSource = cancellationTokenSource, Id = transcodingJobId, - PlaySessionId = playSessionId + PlaySessionId = playSessionId, + LiveStreamId = liveStreamId }; _activeTranscodingJobs.Add(job); @@ -323,7 +331,7 @@ namespace MediaBrowser.Api } } - private void PingTimer(TranscodingJob job, bool isProgressCheckIn) + private async void PingTimer(TranscodingJob job, bool isProgressCheckIn) { if (job.HasExited) { @@ -331,7 +339,6 @@ namespace MediaBrowser.Api return; } - // TODO: Lower this hls timeout var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : 1800000; @@ -339,17 +346,32 @@ namespace MediaBrowser.Api // We can really reduce the timeout for apps that are using the newer api if (!string.IsNullOrWhiteSpace(job.PlaySessionId) && job.Type != TranscodingJobType.Progressive) { - timerDuration = 20000; + timerDuration = 60000; } + job.PingTimeout = timerDuration; + job.LastPingDate = DateTime.UtcNow; + // Don't start the timer for playback checkins with progressive streaming if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn) { - job.StartKillTimer(timerDuration, OnTranscodeKillTimerStopped); + job.StartKillTimer(OnTranscodeKillTimerStopped); } else { - job.ChangeKillTimerIfStarted(timerDuration); + job.ChangeKillTimerIfStarted(); + } + + if (!string.IsNullOrWhiteSpace(job.LiveStreamId)) + { + try + { + await _mediaSourceManager.PingLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error closing live stream", ex); + } } } @@ -361,9 +383,20 @@ namespace MediaBrowser.Api { var job = (TranscodingJob)state; + if (!job.HasExited && job.Type != TranscodingJobType.Progressive) + { + var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; + + if (timeSinceLastPing < job.PingTimeout) + { + job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout); + return; + } + } + Logger.Debug("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - KillTranscodingJob(job, path => true); + KillTranscodingJob(job, true, path => true); } /// @@ -411,7 +444,7 @@ namespace MediaBrowser.Api foreach (var job in jobs) { - KillTranscodingJob(job, deleteFiles); + KillTranscodingJob(job, false, deleteFiles); } } @@ -419,8 +452,9 @@ namespace MediaBrowser.Api /// Kills the transcoding job. /// /// The job. + /// if set to true [close live stream]. /// The delete. - private void KillTranscodingJob(TranscodingJob job, Func delete) + private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) { job.DisposeKillTimer(); @@ -470,6 +504,18 @@ namespace MediaBrowser.Api { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } + + if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) + { + try + { + await _mediaSourceManager.CloseLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error closing live stream for {0}", ex, job.Path); + } + } } private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) @@ -578,6 +624,11 @@ namespace MediaBrowser.Api /// The play session identifier. public string PlaySessionId { get; set; } /// + /// Gets or sets the live stream identifier. + /// + /// The live stream identifier. + public string LiveStreamId { get; set; } + /// /// Gets or sets the path. /// /// The path. @@ -627,6 +678,9 @@ namespace MediaBrowser.Api private readonly object _timerLock = new object(); + public DateTime LastPingDate { get; set; } + public int PingTimeout { get; set; } + public TranscodingJob(ILogger logger) { Logger = logger; @@ -655,7 +709,12 @@ namespace MediaBrowser.Api } } - public void StartKillTimer(int intervalMs, TimerCallback callback) + public void StartKillTimer(TimerCallback callback) + { + StartKillTimer(callback, PingTimeout); + } + + public void StartKillTimer(TimerCallback callback, int intervalMs) { CheckHasExited(); @@ -674,7 +733,7 @@ namespace MediaBrowser.Api } } - public void ChangeKillTimerIfStarted(int intervalMs) + public void ChangeKillTimerIfStarted() { CheckHasExited(); @@ -682,6 +741,8 @@ namespace MediaBrowser.Api { if (KillTimer != null) { + var intervalMs = PingTimeout; + Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer.Change(intervalMs, Timeout.Infinite); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 520c3b9dc7..923af3816a 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1010,6 +1010,7 @@ namespace MediaBrowser.Api.Playback var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, state.Request.PlaySessionId, + state.MediaSource.LiveStreamId, transcodingId, TranscodingJobType, process, diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 1b11f1f337..a55f54d3c8 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -543,7 +543,9 @@ namespace MediaBrowser.Api.Playback.Hls return false; } - return state.VideoRequest.VideoBitRate.HasValue; + // Having problems in android + return false; + //return state.VideoRequest.VideoBitRate.HasValue; } private void AppendPlaylist(StringBuilder builder, string url, int bitrate, string subtitleGroup) diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 9b00b42f76..43d8dbc169 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -88,7 +88,7 @@ namespace MediaBrowser.Api.UserLibrary var views = user.RootFolder .GetChildren(user, true) - .OfType() + .OfType() .Where(i => IsEligibleForSpecialView(i)) .ToList(); @@ -105,9 +105,9 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedResult(list); } - private bool IsEligibleForSpecialView(CollectionFolder view) + private bool IsEligibleForSpecialView(ICollectionFolder view) { - var types = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Games, CollectionType.Music }; + var types = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Games, CollectionType.Music, CollectionType.Photos }; return types.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 73ede8179b..22efd3fba7 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1512,7 +1512,6 @@ namespace MediaBrowser.Controller.Entities image.Path = file.FullName; image.DateModified = imageInfo.DateModified; - image.Length = imageInfo.Length; } } @@ -1622,14 +1621,11 @@ namespace MediaBrowser.Controller.Entities return null; } - var fileInfo = new FileInfo(path); - return new ItemImageInfo { Path = path, - DateModified = FileSystem.GetLastWriteTimeUtc(fileInfo), - Type = imageType, - Length = fileInfo.Length + DateModified = FileSystem.GetLastWriteTimeUtc(path), + Type = imageType }; } @@ -1690,7 +1686,6 @@ namespace MediaBrowser.Controller.Entities else { existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage); - existing.Length = ((FileInfo)newImage).Length; } } @@ -1716,8 +1711,7 @@ namespace MediaBrowser.Controller.Entities { Path = file.FullName, Type = type, - DateModified = FileSystem.GetLastWriteTimeUtc(file), - Length = ((FileInfo)file).Length + DateModified = FileSystem.GetLastWriteTimeUtc(file) }; } @@ -1756,15 +1750,9 @@ namespace MediaBrowser.Controller.Entities FileSystem.SwapFiles(path1, path2); - var file1 = new FileInfo(info1.Path); - var file2 = new FileInfo(info2.Path); - // Refresh these values - info1.DateModified = FileSystem.GetLastWriteTimeUtc(file1); - info2.DateModified = FileSystem.GetLastWriteTimeUtc(file2); - - info1.Length = file1.Length; - info2.Length = file2.Length; + info1.DateModified = FileSystem.GetLastWriteTimeUtc(info1.Path); + info2.DateModified = FileSystem.GetLastWriteTimeUtc(info2.Path); return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index 1122de4032..b36b818ffe 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -11,12 +11,6 @@ namespace MediaBrowser.Controller.Entities /// The path. public string Path { get; set; } - /// - /// Gets or sets the length. - /// - /// The length. - public long Length { get; set; } - /// /// Gets or sets the type. /// diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index a6d123fe86..1672bda926 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -121,7 +121,6 @@ namespace MediaBrowser.Controller.Entities } case CollectionType.Books: - case CollectionType.Photos: case CollectionType.HomeVideos: case CollectionType.MusicVideos: return GetResult(queryParent.GetChildren(user, true), queryParent, query); @@ -138,6 +137,9 @@ namespace MediaBrowser.Controller.Entities case CollectionType.BoxSets: return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false); + case CollectionType.Photos: + return await GetPhotosView(queryParent, user, query).ConfigureAwait(false); + case CollectionType.TvShows: return await GetTvView(queryParent, user, query).ConfigureAwait(false); @@ -247,16 +249,16 @@ namespace MediaBrowser.Controller.Entities return GetFavoriteSongs(queryParent, user, query); default: - { - if (queryParent is UserView) { - return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), queryParent, query); + if (queryParent is UserView) + { + return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), queryParent, query); + } + else + { + return GetResult(queryParent.GetChildren(user, true), queryParent, query); + } } - else - { - return GetResult(queryParent.GetChildren(user, true), queryParent, query); - } - } } } @@ -645,6 +647,19 @@ namespace MediaBrowser.Controller.Entities }), parent, query); } + private async Task> GetPhotosView(Folder queryParent, User user, InternalItemsQuery query) + { + if (query.Recursive) + { + var mediaTypes = new[] { MediaType.Video, MediaType.Photo }; + var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Photos, string.Empty }, i => (i is PhotoAlbum || mediaTypes.Contains(i.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) && FilterItem(i, query)); + + return PostFilterAndSort(items, queryParent, null, query); + } + + return GetResult(queryParent.GetChildren(user, true), queryParent, query); + } + private async Task> GetTvView(Folder parent, User user, InternalItemsQuery query) { if (query.Recursive) diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index d16589f07f..c009121154 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -309,6 +309,7 @@ namespace MediaBrowser.Controller.Library /// The parent identifier. /// Type of the view. /// Name of the sort. + /// The unique identifier. /// The cancellation token. /// Task<UserView>. Task GetNamedView(User user, @@ -316,6 +317,7 @@ namespace MediaBrowser.Controller.Library string parentId, string viewType, string sortName, + string uniqueId, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs index cdf2f0ef52..46cf4dd98c 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.LiveTv; +using System.Collections.Generic; namespace MediaBrowser.Controller.LiveTv { @@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.LiveTv /// The identifier. public string Id { get; set; } + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + /// /// Gets or sets the status. /// diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 1aa8d7f1f1..87378fd5a7 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -488,6 +488,9 @@ Dto\ItemIndex.cs + + Dto\ItemLayout.cs + Dto\MediaSourceInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 5d4633f507..61057e5bbb 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -453,6 +453,9 @@ Dto\ItemIndex.cs + + Dto\ItemLayout.cs + Dto\MediaSourceInfo.cs diff --git a/MediaBrowser.Model/ApiClient/ServerCredentials.cs b/MediaBrowser.Model/ApiClient/ServerCredentials.cs index 6ba2a80c87..b5a1a7b499 100644 --- a/MediaBrowser.Model/ApiClient/ServerCredentials.cs +++ b/MediaBrowser.Model/ApiClient/ServerCredentials.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Model.ApiClient { var index = 0; - foreach (var server in servers) + foreach (ServerInfo server in servers) { if (StringHelper.EqualsIgnoreCase(id, server.Id)) { @@ -110,5 +110,18 @@ namespace MediaBrowser.Model.ApiClient return -1; } + + public ServerInfo GetServer(string id) + { + foreach (ServerInfo server in Servers) + { + if (StringHelper.EqualsIgnoreCase(id, server.Id)) + { + return server; + } + } + + return null; + } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 72f9174388..ac9bd6b08f 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Configuration /// /// true if [enable user specific user views]; otherwise, false. public bool EnableUserSpecificUserViews { get; set; } - + /// /// Gets or sets the value pointing to the file system where the ssl certiifcate is located.. /// diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 02f77899f6..30da8bb4c9 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -277,7 +277,7 @@ namespace MediaBrowser.Model.Dlna // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play - if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options))) + if (item.SupportsDirectPlay && IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options))) { playMethods.Add(PlayMethod.DirectPlay); } @@ -456,10 +456,8 @@ namespace MediaBrowser.Model.Dlna playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue); } - if (!playlistItem.AudioBitrate.HasValue) - { - playlistItem.AudioBitrate = GetAudioBitrate(playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec); - } + int audioBitrate = GetAudioBitrate(playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec); + playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); int? maxBitrateSetting = options.GetMaxBitrate(); // Honor max rate @@ -472,9 +470,9 @@ namespace MediaBrowser.Model.Dlna videoBitrate -= playlistItem.AudioBitrate.Value; } + // Make sure the video bitrate is lower than bitrate settings but at least 64k int currentValue = playlistItem.VideoBitrate ?? videoBitrate; - - playlistItem.VideoBitrate = Math.Min(videoBitrate, currentValue); + playlistItem.VideoBitrate = Math.Max(Math.Min(videoBitrate, currentValue), 64000); } } @@ -640,7 +638,7 @@ namespace MediaBrowser.Model.Dlna } } - if (isEligibleForDirectPlay) + if (isEligibleForDirectPlay && mediaSource.SupportsDirectPlay) { if (mediaSource.Protocol == MediaProtocol.Http) { @@ -659,12 +657,9 @@ namespace MediaBrowser.Model.Dlna } } - if (isEligibleForDirectStream) + if (isEligibleForDirectStream && mediaSource.SupportsDirectStream) { - if (mediaSource.SupportsDirectStream) - { - return PlayMethod.DirectStream; - } + return PlayMethod.DirectStream; } return null; diff --git a/MediaBrowser.Model/Dto/ItemLayout.cs b/MediaBrowser.Model/Dto/ItemLayout.cs new file mode 100644 index 0000000000..c85818390d --- /dev/null +++ b/MediaBrowser.Model/Dto/ItemLayout.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Dto +{ + public static class ItemLayout + { + public static double? GetDisplayAspectRatio(BaseItemDto item) + { + List items = new List(); + items.Add(item); + return GetDisplayAspectRatio(items); + } + + public static double? GetDisplayAspectRatio(List items) + { + List values = new List(); + + foreach (BaseItemDto item in items) + { + if (item.PrimaryImageAspectRatio.HasValue) + { + values.Add(item.PrimaryImageAspectRatio.Value); + } + } + + if (values.Count == 0) + { + return null; + } + + values.Sort(); + + double halfDouble = values.Count; + halfDouble /= 2; + int half = Convert.ToInt32(Math.Floor(halfDouble)); + + double result; + + if (values.Count % 2 > 0) + result = values[half]; + else + result = (values[half - 1] + values[half]) / 2.0; + + // If really close to 2:3 (poster image), just return 2:3 + if (Math.Abs(0.66666666667 - result) <= .15) + { + return 0.66666666667; + } + + // If really close to 16:9 (episode image), just return 16:9 + if (Math.Abs(1.777777778 - result) <= .2) + { + return 1.777777778; + } + + // If really close to 1 (square image), just return 1 + if (Math.Abs(1 - result) <= .15) + { + return 1.0; + } + + // If really close to 4:3 (poster image), just return 2:3 + if (Math.Abs(1.33333333333 - result) <= .15) + { + return 1.33333333333; + } + + return result; + } + } +} diff --git a/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs b/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs index 28e8c158ae..fcb19427ba 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs @@ -22,6 +22,12 @@ namespace MediaBrowser.Model.LiveTv /// The identifier. public string Id { get; set; } + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + /// /// Gets or sets the status. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index eb36712c2f..af0cbb1665 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -139,6 +139,7 @@ + diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index a3b5691500..fc47b0259a 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -384,7 +384,6 @@ namespace MediaBrowser.Providers.Manager else { currentImage.DateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo); - currentImage.Length = ((FileInfo) image.FileInfo).Length; } } else diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 3cfecdeafb..334e148506 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -482,6 +482,11 @@ namespace MediaBrowser.Providers.Manager protected virtual bool IsFullLocalMetadata(TItemType item) { + if (string.IsNullOrWhiteSpace(item.Name)) + { + return false; + } + return true; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index f4d8ddb1a8..b53e313168 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -218,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - FetchEmbeddedInfo(video, mediaInfo); + FetchEmbeddedInfo(video, mediaInfo, options); video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270); @@ -358,11 +358,13 @@ namespace MediaBrowser.Providers.MediaInfo return _blurayExaminer.GetDiscInfo(path); } - private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data) + private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options) { + var isFullRefresh = options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; + if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) { - if (!string.IsNullOrWhiteSpace(data.OfficialRating)) + if (!string.IsNullOrWhiteSpace(data.OfficialRating) || isFullRefresh) { video.OfficialRating = data.OfficialRating; } @@ -370,54 +372,75 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.LockedFields.Contains(MetadataFields.Cast)) { - video.People.Clear(); - - foreach (var person in data.People) + if (video.People.Count == 0 || isFullRefresh) { - video.AddPerson(new PersonInfo + video.People.Clear(); + + foreach (var person in data.People) { - Name = person.Name, - Type = person.Type, - Role = person.Role - }); + video.AddPerson(new PersonInfo + { + Name = person.Name, + Type = person.Type, + Role = person.Role + }); + } } } if (!video.LockedFields.Contains(MetadataFields.Genres)) { - video.Genres.Clear(); - - foreach (var genre in data.Genres) + if (video.Genres.Count == 0 || isFullRefresh) { - video.AddGenre(genre); + video.Genres.Clear(); + + foreach (var genre in data.Genres) + { + video.AddGenre(genre); + } } } if (!video.LockedFields.Contains(MetadataFields.Studios)) { - video.Studios.Clear(); - - foreach (var studio in data.Studios) + if (video.Studios.Count == 0 || isFullRefresh) { - video.AddStudio(studio); + video.Studios.Clear(); + + foreach (var studio in data.Studios) + { + video.AddStudio(studio); + } } } if (data.ProductionYear.HasValue) { - video.ProductionYear = data.ProductionYear; + if (!video.ProductionYear.HasValue || isFullRefresh) + { + video.ProductionYear = data.ProductionYear; + } } if (data.PremiereDate.HasValue) { - video.PremiereDate = data.PremiereDate; + if (!video.PremiereDate.HasValue || isFullRefresh) + { + video.PremiereDate = data.PremiereDate; + } } if (data.IndexNumber.HasValue) { - video.IndexNumber = data.IndexNumber; + if (!video.IndexNumber.HasValue || isFullRefresh) + { + video.IndexNumber = data.IndexNumber; + } } if (data.ParentIndexNumber.HasValue) { - video.ParentIndexNumber = data.ParentIndexNumber; + if (!video.ParentIndexNumber.HasValue || isFullRefresh) + { + video.ParentIndexNumber = data.ParentIndexNumber; + } } // If we don't have a ProductionYear try and get it from PremiereDate @@ -428,7 +451,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.LockedFields.Contains(MetadataFields.Overview)) { - if (!string.IsNullOrWhiteSpace(data.Overview)) + if (string.IsNullOrWhiteSpace(video.Overview) || isFullRefresh) { video.Overview = data.Overview; } diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index 172ae6814c..f886deb007 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -36,10 +36,6 @@ namespace MediaBrowser.Providers.Movies protected override bool IsFullLocalMetadata(Movie item) { - if (string.IsNullOrWhiteSpace(item.Name)) - { - return false; - } if (string.IsNullOrWhiteSpace(item.Overview)) { return false; diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index a5959f0b75..b0cd7382a1 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -77,10 +77,6 @@ namespace MediaBrowser.Providers.TV protected override bool IsFullLocalMetadata(Series item) { - if (string.IsNullOrWhiteSpace(item.Name)) - { - return false; - } if (string.IsNullOrWhiteSpace(item.Overview)) { return false; diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs index de1a4c6f93..a31cc1e0c1 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs @@ -243,7 +243,15 @@ namespace MediaBrowser.Providers.TV await SanitizeXmlFile(file).ConfigureAwait(false); } - await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false); + var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml"); + var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml"); + + if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase)) + { + File.Copy(downloadLangaugeXmlFile, saveAsLanguageXmlFile, true); + } + + await ExtractEpisodes(seriesDataPath, downloadLangaugeXmlFile, lastTvDbUpdateTime).ConfigureAwait(false); } public TvdbOptions GetTvDbOptions() diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 190b8fcf4c..f44b7b5a84 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -828,14 +828,11 @@ namespace MediaBrowser.Server.Implementations.Dto if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) { - var file = new FileInfo(chapterInfo.ImagePath); - dto.ImageTag = GetImageCacheTag(item, new ItemImageInfo { Path = chapterInfo.ImagePath, Type = ImageType.Chapter, - DateModified = _fileSystem.GetLastWriteTimeUtc(file), - Length = file.Length + DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath) }); } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 966e0a3e48..3fa0df7608 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,5 +1,4 @@ -using System.Linq; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; @@ -139,55 +138,24 @@ namespace MediaBrowser.Server.Implementations.EntryPoints // On some systems the device discovered event seems to fire repeatedly // This check will help ensure we're not trying to port map the same device over and over - List currentMappings = null; - - try - { - currentMappings = device.GetAllMappings().ToList(); - } - catch (NotSupportedException) - { - } - var address = device.LocalAddress.ToString(); if (!_createdRules.Contains(address)) { _createdRules.Add(address); - CreatePortMap(device, currentMappings, _appHost.HttpPort, _config.Configuration.PublicPort); - CreatePortMap(device, currentMappings, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort); + CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort); + CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort); } } - private void CreatePortMap(INatDevice device, List currentMappings, int privatePort, int publicPort) + private void CreatePortMap(INatDevice device, int privatePort, int publicPort) { - var hasMapping = false; - - if (currentMappings != null) + _logger.Debug("Creating port map on port {0}", privatePort); + device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) { - hasMapping = currentMappings.Any(i => i.PublicPort == publicPort && i.PrivatePort == privatePort); - } - else - { - try - { - var mapping = device.GetSpecificMapping(Protocol.Tcp, publicPort); - hasMapping = mapping != null; - } - catch (NotSupportedException) - { - } - } - - if (!hasMapping) - { - _logger.Debug("Creating port map on port {0}", privatePort); - device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) - { - Description = _appHost.Name - }); - } + Description = _appHost.Name + }); } // As I said before, this method will be never invoked. You can remove it. diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 313985d9df..dcdf2aa7dc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1602,7 +1602,7 @@ namespace MediaBrowser.Server.Implementations.Library { if (ConfigurationManager.Configuration.EnableUserSpecificUserViews) { - return await GetNamedViewInternal(user, name, null, viewType, sortName, cancellationToken) + return await GetNamedViewInternal(user, name, null, viewType, sortName, null, cancellationToken) .ConfigureAwait(false); } @@ -1662,6 +1662,7 @@ namespace MediaBrowser.Server.Implementations.Library string parentId, string viewType, string sortName, + string uniqueId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(parentId)) @@ -1669,7 +1670,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("parentId"); } - return GetNamedViewInternal(user, name, parentId, viewType, sortName, cancellationToken); + return GetNamedViewInternal(user, name, parentId, viewType, sortName, uniqueId, cancellationToken); } private async Task GetNamedViewInternal(User user, @@ -1677,6 +1678,7 @@ namespace MediaBrowser.Server.Implementations.Library string parentId, string viewType, string sortName, + string uniqueId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(name)) @@ -1684,7 +1686,13 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("name"); } - var id = GetNewItemId("37_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty), typeof(UserView)); + var idValues = "37_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty); + if (!string.IsNullOrWhiteSpace(uniqueId)) + { + idValues += uniqueId; + } + + var id = GetNewItemId(idValues, typeof(UserView)); var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N")); @@ -1723,7 +1731,7 @@ namespace MediaBrowser.Server.Implementations.Library await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); } - var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 12; + var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 24; if (refresh) { diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index a21b19e04c..5cff9046a3 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library if (parents.Count > 0) { - list.Add(await GetUserView(parents, CollectionType.TvShows, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(parents, list, CollectionType.TvShows, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) || string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType)) @@ -105,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Library if (parents.Count > 0) { - list.Add(await GetUserView(parents, CollectionType.Music, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(parents, list, CollectionType.Music, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType)) @@ -113,7 +113,7 @@ namespace MediaBrowser.Server.Implementations.Library if (parents.Count > 0) { - list.Add(await GetUserView(parents, CollectionType.Movies, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(parents, list, CollectionType.Movies, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase)) @@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Library if (parents.Count > 0) { - list.Add(await GetUserView(parents, CollectionType.Games, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(parents, list, CollectionType.Games, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) @@ -129,7 +129,7 @@ namespace MediaBrowser.Server.Implementations.Library if (parents.Count > 0) { - list.Add(await GetUserView(parents, CollectionType.BoxSets, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(parents, list, CollectionType.BoxSets, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) @@ -137,12 +137,12 @@ namespace MediaBrowser.Server.Implementations.Library if (parents.Count > 0) { - list.Add(await GetUserView(parents, CollectionType.Playlists, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(parents, list, CollectionType.Playlists, string.Empty, user, cancellationToken).ConfigureAwait(false)); } if (user.Configuration.DisplayFoldersView) { - list.Add(await GetUserView(new List(), CollectionType.Folders, "zz_" + CollectionType.Folders, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(new List(), list, CollectionType.Folders, "zz_" + CollectionType.Folders, user, cancellationToken).ConfigureAwait(false)); } if (query.IncludeExternalContent) @@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Library if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId)) { //list.Add(await _liveTvManager.GetInternalLiveTvFolder(query.UserId, cancellationToken).ConfigureAwait(false)); - list.Add(await GetUserView(new List(), CollectionType.LiveTv, string.Empty, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(new List(), list, CollectionType.LiveTv, string.Empty, user, cancellationToken).ConfigureAwait(false)); } } @@ -190,7 +190,9 @@ namespace MediaBrowser.Server.Implementations.Library public Task GetUserSubView(string name, string parentId, string type, User user, string sortName, CancellationToken cancellationToken) { - return _libraryManager.GetNamedView(user, name, parentId, type, sortName, cancellationToken); + var uniqueId = parentId + "subview" + type; + + return _libraryManager.GetNamedView(user, name, parentId, type, sortName, uniqueId, cancellationToken); } public Task GetUserSubView(string parentId, string type, User user, string sortName, CancellationToken cancellationToken) @@ -200,16 +202,27 @@ namespace MediaBrowser.Server.Implementations.Library return GetUserSubView(name, parentId, type, user, sortName, cancellationToken); } - public async Task GetUserView(List parents, string viewType, string sortName, User user, CancellationToken cancellationToken) + public async Task GetUserView(List parents, List currentViews, string viewType, string sortName, User user, CancellationToken cancellationToken) { + var name = _localizationManager.GetLocalizedString("ViewType" + viewType); + if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase))) { - var name = parents[0].Name; + if (!string.IsNullOrWhiteSpace(parents[0].Name)) + { + name = parents[0].Name; + } + var parentId = parents[0].Id; var enableRichView = !user.Configuration.PlainFolderViews.Contains(parentId.ToString("N"), StringComparer.OrdinalIgnoreCase); - if (_config.Configuration.EnableUserSpecificUserViews || !enableRichView) + if (!enableRichView || currentViews.OfType().Any(i => string.Equals(i.ViewType, viewType, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase))) + { + return await GetUserView(parentId, name, viewType, enableRichView, sortName, user, cancellationToken).ConfigureAwait(false); + } + + if (_config.Configuration.EnableUserSpecificUserViews) { viewType = enableRichView ? viewType : null; var view = await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); @@ -224,18 +237,14 @@ namespace MediaBrowser.Server.Implementations.Library return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); } - else - { - var name = _localizationManager.GetLocalizedString("ViewType" + viewType); - return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); - } + return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); } public Task GetUserView(Guid parentId, string name, string viewType, bool enableRichView, string sortName, User user, CancellationToken cancellationToken) { viewType = enableRichView ? viewType : null; - return _libraryManager.GetNamedView(user, name, parentId.ToString("N"), viewType, sortName, cancellationToken); + return _libraryManager.GetNamedView(user, name, parentId.ToString("N"), viewType, sortName, null, cancellationToken); } public List>> GetLatestItems(LatestItemsQuery request) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 61017ffb35..00c15fdfcc 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -293,7 +293,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv ProgramName = info.ProgramName, SourceType = info.SourceType, Status = info.Status, - ChannelName = channelName + ChannelName = channelName, + Url = info.Url }; if (!string.IsNullOrEmpty(info.ChannelId)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 84b4053a19..38c93a6964 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -122,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) { - var inputPaths = new[] { mediaSource.Path }; + var originalRuntime = mediaSource.RunTimeTicks; var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest { @@ -131,8 +131,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, ExtractChapters = false - }, cancellationToken) - .ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); mediaSource.Bitrate = info.Bitrate; mediaSource.Container = info.Container; @@ -146,6 +145,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv mediaSource.DefaultSubtitleStreamIndex = null; + // Null this out so that it will be treated like a live stream + if (!originalRuntime.HasValue) + { + mediaSource.RunTimeTicks = null; + } + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio); if (audioStream == null || audioStream.Index == -1) diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 1affc43bf8..f464dad4ba 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -1433,5 +1433,7 @@ "ToAccessPreferencesHelp": "To access your preferences later, click your user icon in the top right header and select My Preferences.", "HeaderViewStyles": "View Styles", "LabelSelectViewStyles": "Enable rich presentations for:", - "LabelSelectViewStylesHelp": "If enabled, views will be built with metadata to offer categories such as Suggestions, Latest, Genres, and more. If disabled, they'll be displayed with simple folders." + "LabelSelectViewStylesHelp": "If enabled, views will be built with metadata to offer categories such as Suggestions, Latest, Genres, and more. If disabled, they'll be displayed with simple folders.", + "TabPhotos": "Photos", + "TabVideos": "Videos" } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 6f0310e3c3..93a8fb86a5 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -727,10 +727,14 @@ namespace MediaBrowser.Server.Implementations.Sync } }); - return jobItemResult.Items + var readyItems = jobItemResult.Items .Select(GetJobItemInfo) .Where(i => i != null) .ToList(); + + _logger.Debug("Returning {0} ready sync items for targetId {1}", readyItems.Count, targetId); + + return readyItems; } public async Task SyncData(SyncDataRequest request) diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 90af13b579..030d687918 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -470,6 +470,7 @@ namespace MediaBrowser.WebDashboard.Api "notificationlist.js", "notificationsetting.js", "notificationsettings.js", + "photos.js", "playlists.js", "playlistedit.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 7abe2ded98..5c4aab1e4f 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -132,6 +132,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -147,6 +150,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index f3f5bbd8c4..269fc720a0 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.620 + 3.0.621 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 9804371d7d..e2577369a0 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.620 + 3.0.621 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 8a47ff07a8..cb467b5e5b 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.620 + 3.0.621 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index fbe9e66c6f..a576cab49f 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.620 + 3.0.621 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + diff --git a/SharedVersion.cs b/SharedVersion.cs index 5d75f7887f..49945f62ac 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5582.4")] +[assembly: AssemblyVersion("3.0.5588.0")]