diff options
Diffstat (limited to 'MediaBrowser.Controller')
22 files changed, 639 insertions, 513 deletions
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 8eb27888a..c8b432ecb 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -95,12 +95,5 @@ namespace MediaBrowser.Controller.Channels /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The item media sources.</returns> IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); - - /// <summary> - /// Whether the item supports media probe. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>Whether media probe should be enabled.</returns> - bool EnableMediaProbe(BaseItem item); } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index d789033f1..b225f22df 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -184,7 +185,7 @@ namespace MediaBrowser.Controller.Entities /// <exception cref="ArgumentNullException">The id is empty.</exception> public BaseItem FindVirtualChild(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 18d948a62..11cdf8444 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities.Audio public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo<ArtistInfo> { [JsonIgnore] - public bool IsAccessedByName => ParentId.Equals(default); + public bool IsAccessedByName => ParentId.IsEmpty(); [JsonIgnore] public override bool IsFolder => !IsAccessedByName; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index fdbceac96..ddcc994a0 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -240,7 +240,7 @@ namespace MediaBrowser.Controller.Entities { get { - if (!ChannelId.Equals(default)) + if (!ChannelId.IsEmpty()) { return SourceType.Channel; } @@ -530,7 +530,7 @@ namespace MediaBrowser.Controller.Entities get { var id = DisplayParentId; - if (id.Equals(default)) + if (id.IsEmpty()) { return null; } @@ -746,7 +746,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool StopRefreshIfLocalMetadataFound => true; [JsonIgnore] - protected virtual bool SupportsOwnedItems => !ParentId.Equals(default) && IsFileProtocol; + protected virtual bool SupportsOwnedItems => !ParentId.IsEmpty() && IsFileProtocol; [JsonIgnore] public virtual bool SupportsPeople => false; @@ -823,7 +823,7 @@ namespace MediaBrowser.Controller.Entities public BaseItem GetOwner() { var ownerId = OwnerId; - return ownerId.Equals(default) ? null : LibraryManager.GetItemById(ownerId); + return ownerId.IsEmpty() ? null : LibraryManager.GetItemById(ownerId); } public bool CanDelete(User user, List<Folder> allCollectionFolders) @@ -968,7 +968,7 @@ namespace MediaBrowser.Controller.Entities public BaseItem GetParent() { var parentId = ParentId; - if (parentId.Equals(default)) + if (parentId.IsEmpty()) { return null; } @@ -1361,7 +1361,7 @@ namespace MediaBrowser.Controller.Entities var tasks = extras.Select(i => { var subOptions = new MetadataRefreshOptions(options); - if (!i.OwnerId.Equals(ownerId) || !i.ParentId.Equals(default)) + if (!i.OwnerId.Equals(ownerId) || !i.ParentId.IsEmpty()) { i.OwnerId = ownerId; i.ParentId = Guid.Empty; @@ -1673,7 +1673,7 @@ namespace MediaBrowser.Controller.Entities // First get using the cached Id if (info.ItemId.HasValue) { - if (info.ItemId.Value.Equals(default)) + if (info.ItemId.Value.IsEmpty()) { return null; } @@ -2439,7 +2439,7 @@ namespace MediaBrowser.Controller.Entities return Task.FromResult(true); } - if (video.OwnerId.Equals(default)) + if (video.OwnerId.IsEmpty()) { video.OwnerId = this.Id; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index e707eedbf..74eb089de 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Collections; @@ -198,7 +199,7 @@ namespace MediaBrowser.Controller.Entities { item.SetParent(this); - if (item.Id.Equals(default)) + if (item.Id.IsEmpty()) { item.Id = LibraryManager.GetNewItemId(item.Path, item.GetType()); } @@ -697,7 +698,7 @@ namespace MediaBrowser.Controller.Entities if (this is not UserRootFolder && this is not AggregateFolder - && query.ParentId.Equals(default)) + && query.ParentId.IsEmpty()) { query.Parent = this; } @@ -840,7 +841,7 @@ namespace MediaBrowser.Controller.Entities return true; } - if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default)) + if (!query.AdjacentTo.IsNullOrEmpty()) { Logger.LogDebug("Query requires post-filtering due to AdjacentTo"); return true; @@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities #pragma warning restore CA1309 // This must be the last filter - if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default)) + if (!query.AdjacentTo.IsNullOrEmpty()) { items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value); } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index bf31508c1..37e241414 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -74,12 +75,12 @@ namespace MediaBrowser.Controller.Entities.TV get { var seriesId = SeriesId; - if (seriesId.Equals(default)) + if (seriesId.IsEmpty()) { seriesId = FindSeriesId(); } - return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series); + return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series); } } @@ -89,12 +90,12 @@ namespace MediaBrowser.Controller.Entities.TV get { var seasonId = SeasonId; - if (seasonId.Equals(default)) + if (seasonId.IsEmpty()) { seasonId = FindSeasonId(); } - return seasonId.Equals(default) ? null : (LibraryManager.GetItemById(seasonId) as Season); + return seasonId.IsEmpty() ? null : (LibraryManager.GetItemById(seasonId) as Season); } } @@ -271,7 +272,7 @@ namespace MediaBrowser.Controller.Entities.TV var seasonId = SeasonId; - if (!seasonId.Equals(default) && !list.Contains(seasonId)) + if (!seasonId.IsEmpty() && !list.Contains(seasonId)) { list.Add(seasonId); } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 0a040a3c2..c29cefc15 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Querying; @@ -48,12 +49,12 @@ namespace MediaBrowser.Controller.Entities.TV get { var seriesId = SeriesId; - if (seriesId.Equals(default)) + if (seriesId.IsEmpty()) { seriesId = FindSeriesId(); } - return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series); + return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series); } } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index eb026deb4..c93488a85 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -70,11 +70,11 @@ namespace MediaBrowser.Controller.Entities /// <inheritdoc /> public override IEnumerable<Guid> GetIdsForAncestorQuery() { - if (!DisplayParentId.Equals(default)) + if (!DisplayParentId.IsEmpty()) { yield return DisplayParentId; } - else if (!ParentId.Equals(default)) + else if (!ParentId.IsEmpty()) { yield return ParentId; } @@ -95,11 +95,11 @@ namespace MediaBrowser.Controller.Entities { var parent = this as Folder; - if (!DisplayParentId.Equals(default)) + if (!DisplayParentId.IsEmpty()) { parent = LibraryManager.GetItemById(DisplayParentId) as Folder ?? parent; } - else if (!ParentId.Equals(default)) + else if (!ParentId.IsEmpty()) { parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index a3525c862..4af000557 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -433,7 +433,7 @@ namespace MediaBrowser.Controller.Entities var user = query.User; // This must be the last filter - if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default)) + if (!query.AdjacentTo.IsNullOrEmpty()) { items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value); } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index be2eb4d28..5adadec39 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Entities foreach (var child in LinkedAlternateVersions) { // Reset the cached value - if (child.ItemId.HasValue && child.ItemId.Value.Equals(default)) + if (child.ItemId.IsNullOrEmpty()) { child.ItemId = null; } diff --git a/MediaBrowser.Controller/LiveTv/IGuideManager.cs b/MediaBrowser.Controller/LiveTv/IGuideManager.cs new file mode 100644 index 000000000..9883b9283 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/IGuideManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv; + +/// <summary> +/// Service responsible for managing the Live TV guide. +/// </summary> +public interface IGuideManager +{ + /// <summary> + /// Gets the guide information. + /// </summary> + /// <returns>The <see cref="GuideInfo"/>.</returns> + GuideInfo GetGuideInfo(); + + /// <summary> + /// Refresh the guide. + /// </summary> + /// <param name="progress">The <see cref="IProgress{T}"/> to use.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param> + /// <returns>Task representing the refresh operation.</returns> + Task RefreshGuide(IProgress<double> progress, CancellationToken cancellationToken); +} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 4206159e7..2dbc2cf82 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv /// Adds the parts. /// </summary> /// <param name="services">The services.</param> - /// <param name="tunerHosts">The tuner hosts.</param> /// <param name="listingProviders">The listing providers.</param> - void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders); + void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders); /// <summary> /// Gets the timer. @@ -176,12 +175,6 @@ namespace MediaBrowser.Controller.LiveTv Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken); /// <summary> - /// Gets the guide information. - /// </summary> - /// <returns>GuideInfo.</returns> - GuideInfo GetGuideInfo(); - - /// <summary> /// Gets the recommended programs. /// </summary> /// <param name="query">The query.</param> @@ -254,14 +247,6 @@ namespace MediaBrowser.Controller.LiveTv Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null); /// <summary> - /// Saves the tuner host. - /// </summary> - /// <param name="info">Turner host to save.</param> - /// <param name="dataSourceChanged">Option to specify that data source has changed.</param> - /// <returns>Tuner host information wrapped in a task.</returns> - Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true); - - /// <summary> /// Saves the listing provider. /// </summary> /// <param name="info">The information.</param> @@ -298,10 +283,6 @@ namespace MediaBrowser.Controller.LiveTv Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); - List<NameIdPair> GetTunerHostTypes(); - - Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); - string GetEmbyTvActiveRecordingPath(string id); ActiveRecordingInfo GetActiveRecordingInfo(string path); diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index ce34954e3..52fb15648 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -141,14 +141,6 @@ namespace MediaBrowser.Controller.LiveTv Task CloseLiveStream(string id, CancellationToken cancellationToken); /// <summary> - /// Records the live stream. - /// </summary> - /// <param name="id">The identifier.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task RecordLiveStream(string id, CancellationToken cancellationToken); - - /// <summary> /// Resets the tuner. /// </summary> /// <param name="id">The identifier.</param> @@ -180,9 +172,4 @@ namespace MediaBrowser.Controller.LiveTv { Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); } - - public interface ISupportsUpdatingDefaults - { - Task UpdateTimerDefaults(SeriesTimerInfo info, CancellationToken cancellationToken); - } } diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index b98309158..3689a2adf 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -36,13 +36,6 @@ namespace MediaBrowser.Controller.LiveTv Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken); /// <summary> - /// Gets the tuner infos. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task<List<LiveTvTunerInfo>>.</returns> - Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken); - - /// <summary> /// Gets the channel stream. /// </summary> /// <param name="channelId">The channel identifier.</param> diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs new file mode 100644 index 000000000..3df6066f6 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv; + +/// <summary> +/// Service responsible for managing the <see cref="ITunerHost"/>s. +/// </summary> +public interface ITunerHostManager +{ + /// <summary> + /// Gets the available <see cref="ITunerHost"/>s. + /// </summary> + IReadOnlyList<ITunerHost> TunerHosts { get; } + + /// <summary> + /// Gets the <see cref="NameIdPair"/>s for the available <see cref="ITunerHost"/>s. + /// </summary> + /// <returns>The <see cref="NameIdPair"/>s.</returns> + IEnumerable<NameIdPair> GetTunerHostTypes(); + + /// <summary> + /// Saves the tuner host. + /// </summary> + /// <param name="info">Turner host to save.</param> + /// <param name="dataSourceChanged">Option to specify that data source has changed.</param> + /// <returns>Tuner host information wrapped in a task.</returns> + Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true); + + /// <summary> + /// Discovers the available tuners. + /// </summary> + /// <param name="newDevicesOnly">A value indicating whether to only return new devices.</param> + /// <returns>The <see cref="TunerHostInfo"/>s.</returns> + IAsyncEnumerable<TunerHostInfo> DiscoverTuners(bool newDevicesOnly); + + /// <summary> + /// Scans for tuner devices that have changed URLs. + /// </summary> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param> + /// <returns>A task that represents the scanning operation.</returns> + Task ScanForTunerDeviceChanges(CancellationToken cancellationToken); +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs deleted file mode 100644 index eb3babc18..000000000 --- a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class LiveTvServiceStatusInfo - { - public LiveTvServiceStatusInfo() - { - Tuners = new List<LiveTvTunerInfo>(); - IsVisible = true; - } - - /// <summary> - /// Gets or sets the status. - /// </summary> - /// <value>The status.</value> - public LiveTvServiceStatus Status { get; set; } - - /// <summary> - /// Gets or sets the status message. - /// </summary> - /// <value>The status message.</value> - public string StatusMessage { get; set; } - - /// <summary> - /// Gets or sets the version. - /// </summary> - /// <value>The version.</value> - public string Version { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance has update available. - /// </summary> - /// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value> - public bool HasUpdateAvailable { get; set; } - - /// <summary> - /// Gets or sets the tuners. - /// </summary> - /// <value>The tuners.</value> - public List<LiveTvTunerInfo> Tuners { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is visible. - /// </summary> - /// <value><c>true</c> if this instance is visible; otherwise, <c>false</c>.</value> - public bool IsVisible { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs deleted file mode 100644 index aa5eb59d1..000000000 --- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs +++ /dev/null @@ -1,77 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class LiveTvTunerInfo - { - public LiveTvTunerInfo() - { - Clients = new List<string>(); - } - - /// <summary> - /// Gets or sets the type of the source. - /// </summary> - /// <value>The type of the source.</value> - public string SourceType { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the identifier. - /// </summary> - /// <value>The identifier.</value> - public string Id { get; set; } - - /// <summary> - /// Gets or sets the URL. - /// </summary> - /// <value>The URL.</value> - public string Url { get; set; } - - /// <summary> - /// Gets or sets the status. - /// </summary> - /// <value>The status.</value> - public LiveTvTunerStatus Status { get; set; } - - /// <summary> - /// Gets or sets the channel identifier. - /// </summary> - /// <value>The channel identifier.</value> - public string ChannelId { get; set; } - - /// <summary> - /// Gets or sets the recording identifier. - /// </summary> - /// <value>The recording identifier.</value> - public string RecordingId { get; set; } - - /// <summary> - /// Gets or sets the name of the program. - /// </summary> - /// <value>The name of the program.</value> - public string ProgramName { get; set; } - - /// <summary> - /// Gets or sets the clients. - /// </summary> - /// <value>The clients.</value> - public List<string> Clients { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance can reset. - /// </summary> - /// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value> - public bool CanReset { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs deleted file mode 100644 index 1dcf7a58f..000000000 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ /dev/null @@ -1,210 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class RecordingInfo - { - public RecordingInfo() - { - Genres = new List<string>(); - } - - /// <summary> - /// Gets or sets the id of the recording. - /// </summary> - public string Id { get; set; } - - /// <summary> - /// Gets or sets the series timer identifier. - /// </summary> - /// <value>The series timer identifier.</value> - public string SeriesTimerId { get; set; } - - /// <summary> - /// Gets or sets the timer identifier. - /// </summary> - /// <value>The timer identifier.</value> - public string TimerId { get; set; } - - /// <summary> - /// Gets or sets the channelId of the recording. - /// </summary> - public string ChannelId { get; set; } - - /// <summary> - /// Gets or sets the type of the channel. - /// </summary> - /// <value>The type of the channel.</value> - public ChannelType ChannelType { get; set; } - - /// <summary> - /// Gets or sets the name of the recording. - /// </summary> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - public string Path { get; set; } - - /// <summary> - /// Gets or sets the URL. - /// </summary> - /// <value>The URL.</value> - public string Url { get; set; } - - /// <summary> - /// Gets or sets the overview. - /// </summary> - /// <value>The overview.</value> - public string Overview { get; set; } - - /// <summary> - /// Gets or sets the start date of the recording, in UTC. - /// </summary> - public DateTime StartDate { get; set; } - - /// <summary> - /// Gets or sets the end date of the recording, in UTC. - /// </summary> - public DateTime EndDate { get; set; } - - /// <summary> - /// Gets or sets the program identifier. - /// </summary> - /// <value>The program identifier.</value> - public string ProgramId { get; set; } - - /// <summary> - /// Gets or sets the status. - /// </summary> - /// <value>The status.</value> - public RecordingStatus Status { get; set; } - - /// <summary> - /// Gets or sets the genre of the program. - /// </summary> - public List<string> Genres { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is repeat. - /// </summary> - /// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value> - public bool IsRepeat { get; set; } - - /// <summary> - /// Gets or sets the episode title. - /// </summary> - /// <value>The episode title.</value> - public string EpisodeTitle { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is hd. - /// </summary> - /// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value> - public bool? IsHD { get; set; } - - /// <summary> - /// Gets or sets the audio. - /// </summary> - /// <value>The audio.</value> - public ProgramAudio? Audio { get; set; } - - /// <summary> - /// Gets or sets the original air date. - /// </summary> - /// <value>The original air date.</value> - public DateTime? OriginalAirDate { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is movie. - /// </summary> - /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value> - public bool IsMovie { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is sports. - /// </summary> - /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value> - public bool IsSports { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is series. - /// </summary> - /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> - public bool IsSeries { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is live. - /// </summary> - /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> - public bool IsLive { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is news. - /// </summary> - /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value> - public bool IsNews { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is kids. - /// </summary> - /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> - public bool IsKids { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is premiere. - /// </summary> - /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> - public bool IsPremiere { get; set; } - - /// <summary> - /// Gets or sets the official rating. - /// </summary> - /// <value>The official rating.</value> - public string OfficialRating { get; set; } - - /// <summary> - /// Gets or sets the community rating. - /// </summary> - /// <value>The community rating.</value> - public float? CommunityRating { get; set; } - - /// <summary> - /// Gets or sets the image path if it can be accessed directly from the file system. - /// </summary> - /// <value>The image path.</value> - public string ImagePath { get; set; } - - /// <summary> - /// Gets or sets the image url if it can be downloaded. - /// </summary> - /// <value>The image URL.</value> - public string ImageUrl { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance has image. - /// </summary> - /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value> - public bool? HasImage { get; set; } - - /// <summary> - /// Gets or sets the show identifier. - /// </summary> - /// <value>The show identifier.</value> - public string ShowId { get; set; } - - /// <summary> - /// Gets or sets the date last updated. - /// </summary> - /// <value>The date last updated.</value> - public DateTime DateLastUpdated { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs deleted file mode 100644 index 0b943c939..000000000 --- a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class RecordingStatusChangedEventArgs : EventArgs - { - public string RecordingId { get; set; } - - public RecordingStatus NewStatus { get; set; } - } -} diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 400e7f40f..1c95192f1 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.MediaEncoding private const string VaapiAlias = "va"; private const string D3d11vaAlias = "dx11"; private const string VideotoolboxAlias = "vt"; + private const string RkmppAlias = "rk"; private const string OpenclAlias = "ocl"; private const string CudaAlias = "cu"; private const string DrmAlias = "dr"; @@ -161,6 +162,7 @@ namespace MediaBrowser.Controller.MediaEncoding { "vaapi", hwEncoder + "_vaapi" }, { "videotoolbox", hwEncoder + "_videotoolbox" }, { "v4l2m2m", hwEncoder + "_v4l2m2m" }, + { "rkmpp", hwEncoder + "_rkmpp" }, }; if (!string.IsNullOrEmpty(hwType) @@ -217,6 +219,14 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilter("hwupload_vaapi"); } + private bool IsRkmppFullSupported() + { + return _mediaEncoder.SupportsHwaccel("rkmpp") + && _mediaEncoder.SupportsFilter("scale_rkrga") + && _mediaEncoder.SupportsFilter("vpp_rkrga") + && _mediaEncoder.SupportsFilter("overlay_rkrga"); + } + private bool IsOpenclFullSupported() { return _mediaEncoder.SupportsHwaccel("opencl") @@ -696,6 +706,14 @@ namespace MediaBrowser.Controller.MediaEncoding return codec.ToLowerInvariant(); } + private string GetRkmppDeviceArgs(string alias) + { + alias ??= RkmppAlias; + + // device selection in rk is not supported. + return " -init_hw_device rkmpp=" + alias; + } + private string GetVideoToolboxDeviceArgs(string alias) { alias ??= VideotoolboxAlias; @@ -835,30 +853,25 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetGraphicalSubCanvasSize(EncodingJobInfo state) { - // DVBSUB and DVDSUB use the fixed canvas size 720x576 + // DVBSUB uses the fixed canvas size 720x576 if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !state.SubtitleStream.IsTextSubtitleStream - && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase) - && !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase)) + && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)) { - var inW = state.VideoStream?.Width; - var inH = state.VideoStream?.Height; - var reqW = state.BaseRequest.Width; - var reqH = state.BaseRequest.Height; - var reqMaxW = state.BaseRequest.MaxWidth; - var reqMaxH = state.BaseRequest.MaxHeight; - - // setup a relative small canvas_size for overlay_qsv/vaapi to reduce transfer overhead - var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080); + var subtitleWidth = state.SubtitleStream?.Width; + var subtitleHeight = state.SubtitleStream?.Height; - if (overlayW.HasValue && overlayH.HasValue) + if (subtitleWidth.HasValue + && subtitleHeight.HasValue + && subtitleWidth.Value > 0 + && subtitleHeight.Value > 0) { return string.Format( CultureInfo.InvariantCulture, " -canvas_size {0}x{1}", - overlayW.Value, - overlayH.Value); + subtitleWidth.Value, + subtitleHeight.Value); } } @@ -1061,6 +1074,33 @@ namespace MediaBrowser.Controller.MediaEncoding // no videotoolbox hw filter. args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias)); } + else if (string.Equals(optHwaccelType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp")) + { + return string.Empty; + } + + var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + if (!isRkmppDecoder && !isRkmppEncoder) + { + return string.Empty; + } + + args.Append(GetRkmppDeviceArgs(RkmppAlias)); + + var filterDevArgs = string.Empty; + var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported(); + + if (doOclTonemap && !isRkmppDecoder) + { + args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias)); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); + } + + args.Append(filterDevArgs); + } if (!string.IsNullOrEmpty(vidDecoder)) { @@ -1477,8 +1517,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase) @@ -1918,20 +1960,22 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "constrained_baseline"; } - // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case. + // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this case. if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)) && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase)) { profile = "baseline"; } - // libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case. + // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case. if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)) && profile.Contains("high", StringComparison.OrdinalIgnoreCase)) { profile = "high"; @@ -2015,6 +2059,11 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -level " + level; } } + else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)) + { + param += " -level " + level; + } else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; @@ -2833,6 +2882,48 @@ namespace MediaBrowser.Controller.MediaEncoding return (outputWidth, outputHeight); } + public static bool IsScaleRatioSupported( + int? videoWidth, + int? videoHeight, + int? requestedWidth, + int? requestedHeight, + int? requestedMaxWidth, + int? requestedMaxHeight, + double? maxScaleRatio) + { + var (outWidth, outHeight) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + + if (!videoWidth.HasValue + || !videoHeight.HasValue + || !outWidth.HasValue + || !outHeight.HasValue + || !maxScaleRatio.HasValue + || (maxScaleRatio.Value < 1.0f)) + { + return false; + } + + var minScaleRatio = 1.0f / maxScaleRatio; + var scaleRatioW = (double)outWidth / (double)videoWidth; + var scaleRatioH = (double)outHeight / (double)videoHeight; + + if (scaleRatioW < minScaleRatio + || scaleRatioW > maxScaleRatio + || scaleRatioH < minScaleRatio + || scaleRatioH > maxScaleRatio) + { + return false; + } + + return true; + } + public static string GetHwScaleFilter( string hwScaleSuffix, string videoFormat, @@ -2877,7 +2968,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public static string GetCustomSwScaleFilter( + public static string GetGraphicalSubPreProcessFilters( int? videoWidth, int? videoHeight, int? requestedWidth, @@ -2897,7 +2988,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Format( CultureInfo.InvariantCulture, - "scale=s={0}x{1}:flags=fast_bilinear", + @"scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}", outWidth.Value, outHeight.Value); } @@ -2913,7 +3004,7 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedHeight, int? requestedMaxWidth, int? requestedMaxHeight, - int? framerate) + float? framerate) { var reqTicks = state.BaseRequest.StartTimeTicks ?? 0; var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture); @@ -2932,7 +3023,7 @@ namespace MediaBrowser.Controller.MediaEncoding "alphasrc=s={0}x{1}:r={2}:start='{3}'", outWidth.Value, outHeight.Value, - framerate ?? 10, + framerate ?? 25, reqTicks > 0 ? startTime : 0); } @@ -3340,9 +3431,8 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasGraphicalSubs) { - // [0:s]scale=s=1280x720 - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -3504,15 +3594,17 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale=s=1280x720,format=yuva420p,hwupload - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); subFilters.Add("format=yuva420p"); } else if (hasTextSubs) { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=yuva420p"); @@ -3527,8 +3619,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -3702,15 +3794,17 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale=s=1280x720,format=yuva420p,hwupload - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); subFilters.Add("format=yuva420p"); } else if (hasTextSubs) { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=yuva420p"); @@ -3727,8 +3821,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -3938,16 +4032,18 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale,format=bgra,hwupload - // overlay_qsv can handle overlay scaling, - // add a dummy scale filter to pair with -canvas_size. - subFilters.Add("scale=flags=fast_bilinear"); + // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -3973,8 +4069,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -4158,12 +4254,17 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - subFilters.Add("scale=flags=fast_bilinear"); + // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) { - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4189,8 +4290,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -4425,12 +4526,17 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - subFilters.Add("scale=flags=fast_bilinear"); + // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) { - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4454,8 +4560,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); if (isVaapiEncoder) @@ -4599,14 +4705,16 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale=s=1280x720,format=bgra,hwupload - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) { - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4815,8 +4923,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); if (isVaapiEncoder) @@ -4899,6 +5007,237 @@ namespace MediaBrowser.Controller.MediaEncoding } /// <summary> + /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain. + /// </summary> + /// <param name="state">Encoding state.</param> + /// <param name="options">Encoding options.</param> + /// <param name="vidEncoder">Video encoder to use.</param> + /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns> + public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain( + EncodingJobInfo state, + EncodingOptions options, + string vidEncoder) + { + if (!string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return (null, null, null); + } + + var isLinux = OperatingSystem.IsLinux(); + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported(); + + if ((isSwDecoder && isSwEncoder) + || !isRkmppOclSupported + || !_mediaEncoder.SupportsFilter("alphasrc")) + { + return GetSwVidFilterChain(state, options, vidEncoder); + } + + // prefered rkmpp + rkrga + opencl filters pipeline + if (isRkmppOclSupported) + { + return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } + + return (null, null, null); + } + + public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isSwDecoder = !isRkmppDecoder; + var isSwEncoder = !isRkmppEncoder; + var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder; + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; + var doOclTonemap = IsHwTonemapAvailable(state, options); + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List<string>(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap)); + + if (isSwDecoder) + { + // INPUT sw surface(memory) + // sw deint + if (doDeintH2645) + { + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); + } + + var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + if (!string.IsNullOrEmpty(swScaleFilter)) + { + swScaleFilter += ":flags=fast_bilinear"; + } + + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) + { + mainFilters.Add("hwupload=derive_device=opencl"); + } + } + else if (isRkmppDecoder) + { + // INPUT rkmpp/drm surface(gem/dma-heap) + + var isFullAfbcPipeline = isDrmInDrmOut && !doOclTonemap; + var outFormat = doOclTonemap ? "p010" : "nv12"; + var hwScaleFilter = GetHwScaleFilter("rkrga", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter2 = GetHwScaleFilter("rkrga", string.Empty, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + + if (!hasSubs + || !isFullAfbcPipeline + || !string.IsNullOrEmpty(hwScaleFilter2)) + { + // try enabling AFBC to save DDR bandwidth + if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline) + { + hwScaleFilter += ":afbc=1"; + } + + // hw scale + mainFilters.Add(hwScaleFilter); + } + } + + if (doOclTonemap && isRkmppDecoder) + { + // map from rkmpp/drm to opencl via drm-opencl interop. + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); + } + + // ocl tonemap + if (doOclTonemap) + { + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + // enable tradeoffs for performance + if (!string.IsNullOrEmpty(tonemapFilter)) + { + tonemapFilter += ":tradeoff=1"; + } + + mainFilters.Add(tonemapFilter); + } + + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doOclTonemap; + if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap) + { + memoryOutput = true; + + // OUTPUT nv12 surface(memory) + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } + + // OUTPUT nv12 surface(memory) + if (isSwDecoder && isRkmppEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) + { + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + } + + if (isDrmInDrmOut) + { + if (doOclTonemap) + { + // OUTPUT drm(nv12) surface(gem/dma-heap) + // reverse-mapping via drm-opencl interop. + mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1"); + mainFilters.Add("format=drm_prime"); + } + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List<string>(); + var overlayFilters = new List<string>(); + if (isDrmInDrmOut) + { + if (hasSubs) + { + if (hasGraphicalSubs) + { + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); + subFilters.Add("format=bgra"); + } + else if (hasTextSubs) + { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=bgra"); + subFilters.Add(subTextSubtitlesFilter); + } + + subFilters.Add("hwupload=derive_device=rkmpp"); + + // try enabling AFBC to save DDR bandwidth + overlayFilters.Add("overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12:afbc=1"); + } + } + else if (memoryOutput) + { + if (hasGraphicalSubs) + { + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); + } + } + + return (mainFilters, subFilters, overlayFilters); + } + + /// <summary> /// Gets the parameter of video processing filters. /// </summary> /// <param name="state">Encoding state.</param> @@ -4944,6 +5283,10 @@ namespace MediaBrowser.Controller.MediaEncoding { (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec); } + else if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + (mainFilters, subFilters, overlayFilters) = GetRkmppVidFilterChain(state, options, outputVideoCodec); + } else { (mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec); @@ -5075,18 +5418,21 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) { return 8; } if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) { return 10; } if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) { return 12; @@ -5139,7 +5485,12 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) { - return null; + // One exception is that RKMPP decoder can handle H.264 High 10. + if (!(string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) + && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))) + { + return null; + } } if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) @@ -5166,6 +5517,11 @@ namespace MediaBrowser.Controller.MediaEncoding { return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth); } + + if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return GetRkmppVidDecoder(state, options, videoStream, bitDepth); + } } var whichCodec = videoStream.Codec; @@ -5231,6 +5587,11 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + return isCodecAvailable ? (" -c:v " + decoderName) : null; } @@ -5253,6 +5614,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported(); var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv"); var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox"); + var isRkmppSupported = isLinux && IsRkmppFullSupported(); var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase); var ffmpegVersion = _mediaEncoder.EncoderVersion; @@ -5355,6 +5717,14 @@ namespace MediaBrowser.Controller.MediaEncoding return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty); } + // Rockchip rkmpp + if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) + && isRkmppSupported + && isCodecAvailable) + { + return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime" : string.Empty); + } + return null; } @@ -5661,6 +6031,102 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) + { + var isLinux = OperatingSystem.IsLinux(); + + if (!isLinux + || !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + + // rkrga RGA2e supports range from 1/16 to 16 + if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f)) + { + return null; + } + + var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported(); + var hwSurface = isRkmppOclSupported + && _mediaEncoder.SupportsFilter("alphasrc"); + + // rkrga RGA3 supports range from 1/8 to 8 + var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f); + + // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool + var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase) + || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp; + + // nv15 and nv20 are bit-stream only formats + if (is10bitSwFormatsRkmpp && !hwSurface) + { + return null; + } + + if (is8bitSwFormatsRkmpp) + { + if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface); + } + + if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface); + } + + if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface); + } + + if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface); + } + } + + if (is8_10bitSwFormatsRkmpp) + { + if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface); + return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty); + } + + if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)) + { + var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface); + return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty); + } + + if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)) + { + var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface); + return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty); + } + + if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "av1", bitDepth, hwSurface); + } + } + + return null; + } + /// <summary> /// Gets the number of threads. /// </summary> diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 53df7133b..5a47236f9 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -111,7 +111,8 @@ namespace MediaBrowser.Controller.Session /// Reports the session ended. /// </summary> /// <param name="sessionId">The session identifier.</param> - void ReportSessionEnded(string sessionId); + /// <returns>Task.</returns> + ValueTask ReportSessionEnded(string sessionId); /// <summary> /// Sends the general command. diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 3e30c8dc4..3a12a56f1 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Session /// <summary> /// Class SessionInfo. /// </summary> - public sealed class SessionInfo : IAsyncDisposable, IDisposable + public sealed class SessionInfo : IAsyncDisposable { // 1 second private const long ProgressIncrement = 10000000; @@ -374,26 +374,6 @@ namespace MediaBrowser.Controller.Session } } - /// <inheritdoc /> - public void Dispose() - { - _disposed = true; - - StopAutomaticProgress(); - - var controllers = SessionControllers.ToList(); - SessionControllers = Array.Empty<ISessionController>(); - - foreach (var controller in controllers) - { - if (controller is IDisposable disposable) - { - _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name); - disposable.Dispose(); - } - } - } - public async ValueTask DisposeAsync() { _disposed = true; @@ -401,6 +381,7 @@ namespace MediaBrowser.Controller.Session StopAutomaticProgress(); var controllers = SessionControllers.ToList(); + SessionControllers = Array.Empty<ISessionController>(); foreach (var controller in controllers) { @@ -409,6 +390,11 @@ namespace MediaBrowser.Controller.Session _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name); await disposableAsync.DisposeAsync().ConfigureAwait(false); } + else if (controller is IDisposable disposable) + { + _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name); + disposable.Dispose(); + } } } } |
