diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index f59b1b7df2..e76dcb4a48 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; } @@ -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) { @@ -353,6 +361,18 @@ namespace MediaBrowser.Api { 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); + } + } } /// @@ -365,9 +385,11 @@ namespace MediaBrowser.Api if (!job.HasExited && job.Type != TranscodingJobType.Progressive) { - if ((DateTime.UtcNow - job.LastPingDate).TotalMilliseconds < job.PingTimeout) + var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; + + if (timeSinceLastPing < job.PingTimeout) { - job.StartKillTimer(OnTranscodeKillTimerStopped); + job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout); return; } } @@ -589,6 +611,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. @@ -670,13 +697,16 @@ namespace MediaBrowser.Api } public void StartKillTimer(TimerCallback callback) + { + StartKillTimer(callback, PingTimeout); + } + + public void StartKillTimer(TimerCallback callback, int intervalMs) { CheckHasExited(); lock (_timerLock) { - var intervalMs = PingTimeout; - if (KillTimer == null) { Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); 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.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.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 e8ef171aa7..30da8bb4c9 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -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); } } 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.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index e99d01d54f..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")); diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index 823599fe51..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,7 +202,7 @@ 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); @@ -215,7 +217,12 @@ namespace MediaBrowser.Server.Implementations.Library 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); @@ -230,16 +237,14 @@ namespace MediaBrowser.Server.Implementations.Library return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); } - else - { - 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)