From 2a25c5a2e3e37e734993d17b7462598babcb0b97 Mon Sep 17 00:00:00 2001 From: ArabCoders Date: Mon, 13 Nov 2023 15:51:06 +0300 Subject: Refactored api call logic handling. --- MediaBrowser.Controller/Library/IUserDataManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 034c405910..8849c098fb 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -35,6 +35,15 @@ namespace MediaBrowser.Controller.Library void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + /// + /// Save the provided user data for the given user. + /// + /// The user. + /// The item. + /// The reason for updating the user data. + /// The reason. + void SaveUserData(User user, BaseItem item, UserDataDto userDataDto, UserDataSaveReason reason); + UserItemData GetUserData(User user, BaseItem item); UserItemData GetUserData(Guid userId, BaseItem item); -- cgit v1.2.3 From c4013d2e10c2c526e21dd0229d60ab9e6c51d252 Mon Sep 17 00:00:00 2001 From: ArabCoders Date: Wed, 15 Nov 2023 13:55:14 +0300 Subject: Updated the summary and the Dto name. --- .../Library/UserDataManager.cs | 3 +- Jellyfin.Api/Controllers/ItemsController.cs | 3 +- .../Library/IUserDataManager.cs | 2 +- MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs | 38 ++++++++++++++++++++ MediaBrowser.Model/Dto/UserDataDto.cs | 40 ---------------------- 5 files changed, 41 insertions(+), 45 deletions(-) create mode 100644 MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs delete mode 100644 MediaBrowser.Model/Dto/UserDataDto.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 585fdd4f09..8beeb8041f 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.Reflection; using System.Threading; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; @@ -82,7 +81,7 @@ namespace Emby.Server.Implementations.Library }); } - public void SaveUserData(User user, BaseItem item, UserDataDto userDataDto, UserDataSaveReason reason) + public void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(item); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index dd54e6ca78..ae80d15e60 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -1,7 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Threading; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; @@ -928,7 +927,7 @@ public class ItemsController : BaseJellyfinApiController public ActionResult UpdateItemUserData( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, - [FromBody, Required] UserDataDto userDataDto) + [FromBody, Required] UpdateUserItemDataDto userDataDto) { if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) { diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 8849c098fb..43cccfc65c 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Controller.Library /// The item. /// The reason for updating the user data. /// The reason. - void SaveUserData(User user, BaseItem item, UserDataDto userDataDto, UserDataSaveReason reason); + void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason); UserItemData GetUserData(User user, BaseItem item); diff --git a/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs new file mode 100644 index 0000000000..9ff09cb22c --- /dev/null +++ b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Dto +{ + /// + /// This is used by the api to get information about a item user data. + /// + public class UpdateUserItemDataDto : UserItemDataDto + { + /// + /// Gets or sets the playback position ticks. + /// + /// The playback position ticks. + public new long? PlaybackPositionTicks { get; set; } + + /// + /// Gets or sets the play count. + /// + /// The play count. + public new int? PlayCount { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is favorite. + /// + /// true if this instance is favorite; otherwise, false. + public new bool? IsFavorite { get; set; } + + /// + /// Gets or sets a value indicating whether this is likes. + /// + /// null if [likes] contains no value, true if [likes]; otherwise, false. + public new bool? Likes { get; set; } + + /// + /// Gets or sets a value indicating whether this is played. + /// + /// true if played; otherwise, false. + public new bool? Played { get; set; } + } +} diff --git a/MediaBrowser.Model/Dto/UserDataDto.cs b/MediaBrowser.Model/Dto/UserDataDto.cs deleted file mode 100644 index 6d8c8969b0..0000000000 --- a/MediaBrowser.Model/Dto/UserDataDto.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace MediaBrowser.Model.Dto -{ - /// - /// Class UserDataDto extends UserItemDataDto to allow nullable members. - /// This change allow us to implement the new /Users/{UserId}/Items/{ItemId}/UserData endpoint. - /// This object allows the requestor to update all or specific user data fields without altering the non-nullable members state. - /// - public class UserDataDto : UserItemDataDto - { - /// - /// Gets or sets the playback position ticks. - /// - /// The playback position ticks. - public new long? PlaybackPositionTicks { get; set; } - - /// - /// Gets or sets the play count. - /// - /// The play count. - public new int? PlayCount { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is favorite. - /// - /// true if this instance is favorite; otherwise, false. - public new bool? IsFavorite { get; set; } - - /// - /// Gets or sets a value indicating whether this is likes. - /// - /// null if [likes] contains no value, true if [likes]; otherwise, false. - public new bool? Likes { get; set; } - - /// - /// Gets or sets a value indicating whether this is played. - /// - /// true if played; otherwise, false. - public new bool? Played { get; set; } - } -} -- cgit v1.2.3 From 5c593b120a5f3b90daa6305a96086eecfe546039 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 5 Dec 2023 12:36:24 -0500 Subject: Don't use List directly in ITunerHost method signatures --- Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs | 6 +++--- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs | 2 +- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index a4e45f4796..da597056a4 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return list; } - protected virtual List GetTunerHosts() + protected virtual IList GetTunerHosts() { return GetConfiguration().TunerHosts .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) @@ -166,9 +166,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return new List(); } - protected abstract Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List currentLiveStreams, CancellationToken cancellationToken); + protected abstract Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken); - public async Task GetChannelStream(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken) + public async Task GetChannelStream(string channelId, string streamId, IList currentLiveStreams, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrEmpty(channelId); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 8cd0c4ffb7..79e15a82e2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -527,7 +527,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return list; } - protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List currentLiveStreams, CancellationToken cancellationToken) + protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = tunerHost.TunerCount; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index db5e81df5f..11bf03b182 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List currentLiveStreams, CancellationToken cancellationToken) + protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = tunerHost.TunerCount; diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 24820abb90..b983091588 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.LiveTv /// The current live streams. /// The cancellation token to cancel operation. /// Live stream wrapped in a task. - Task GetChannelStream(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken); + Task GetChannelStream(string channelId, string streamId, IList currentLiveStreams, CancellationToken cancellationToken); /// /// Gets the channel stream media sources. -- cgit v1.2.3 From 192559db32ef60b2a56b7acf689b6edc3cdc3487 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 5 Dec 2023 14:26:35 -0500 Subject: Make ILiveStream an IDisposable --- .../Library/ExclusiveLiveStream.cs | 7 ++++++- .../LiveTv/TunerHosts/LiveStream.cs | 15 +++++++++++++++ MediaBrowser.Controller/Library/ILiveStream.cs | 3 ++- 3 files changed, 23 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 868071a992..b1649afad5 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.Dto; namespace Emby.Server.Implementations.Library { - public class ExclusiveLiveStream : ILiveStream + public sealed class ExclusiveLiveStream : ILiveStream { private readonly Func _closeFn; @@ -51,5 +51,10 @@ namespace Emby.Server.Implementations.Library { return Task.CompletedTask; } + + /// + public void Dispose() + { + } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index c0ba8710ff..c18594a29b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -112,6 +112,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return stream; } + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + LiveStreamCancellationTokenSource?.Dispose(); + } + } + protected async Task DeleteTempFiles(string path, int retryCount = 0) { if (retryCount == 0) diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs index 4c44a17fdd..bf64aca0f0 100644 --- a/MediaBrowser.Controller/Library/ILiveStream.cs +++ b/MediaBrowser.Controller/Library/ILiveStream.cs @@ -2,6 +2,7 @@ #pragma warning disable CA1711, CS1591 +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Library { - public interface ILiveStream + public interface ILiveStream : IDisposable { int ConsumerCount { get; set; } -- cgit v1.2.3 From 033cfa59c499e5adbb949b708f06e56bf8932401 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 8 Dec 2023 15:45:36 -0700 Subject: Convert CollectionType to use lowercase enum names --- .../Images/CollectionFolderImageProvider.cs | 18 +++--- .../Images/DynamicImageProvider.cs | 8 +-- .../Library/LibraryManager.cs | 4 +- .../Library/Resolvers/Audio/AudioResolver.cs | 6 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 +- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 2 +- .../Library/Resolvers/Books/BookResolver.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 34 +++++------ .../Library/Resolvers/PhotoAlbumResolver.cs | 4 +- .../Library/Resolvers/PhotoResolver.cs | 4 +- .../Library/Resolvers/PlaylistResolver.cs | 2 +- .../Library/Resolvers/TV/EpisodeResolver.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 4 +- .../Library/UserViewManager.cs | 22 +++---- .../LiveTv/LiveTvManager.cs | 2 +- .../Playlists/PlaylistsFolder.cs | 2 +- Jellyfin.Api/Controllers/GenresController.cs | 4 +- Jellyfin.Api/Controllers/ItemUpdateController.cs | 2 +- Jellyfin.Api/Controllers/ItemsController.cs | 2 +- Jellyfin.Api/Controllers/LibraryController.cs | 18 +++--- Jellyfin.Data/Enums/CollectionType.cs | 57 +++++++++--------- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Entities/UserView.cs | 18 +++--- .../Entities/UserViewBuilder.cs | 68 +++++++++++----------- .../Library/AudioResolverTests.cs | 2 +- .../Library/EpisodeResolverTest.cs | 4 +- 26 files changed, 148 insertions(+), 147 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 6e8f77977e..34c722e41d 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -32,26 +32,26 @@ namespace Emby.Server.Implementations.Images switch (viewType) { - case CollectionType.Movies: + case CollectionType.movies: includeItemTypes = new[] { BaseItemKind.Movie }; break; - case CollectionType.TvShows: + case CollectionType.tvshows: includeItemTypes = new[] { BaseItemKind.Series }; break; - case CollectionType.Music: + case CollectionType.music: includeItemTypes = new[] { BaseItemKind.MusicAlbum }; break; - case CollectionType.MusicVideos: + case CollectionType.musicvideos: includeItemTypes = new[] { BaseItemKind.MusicVideo }; break; - case CollectionType.Books: + case CollectionType.books: includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; break; - case CollectionType.BoxSets: + case CollectionType.boxsets: includeItemTypes = new[] { BaseItemKind.BoxSet }; break; - case CollectionType.HomeVideos: - case CollectionType.Photos: + case CollectionType.homevideos: + case CollectionType.photos: includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; break; default: @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Images break; } - var recursive = viewType != CollectionType.Playlists; + var recursive = viewType != CollectionType.playlists; return view.GetItemList(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 5de53df739..6b2ae23b3c 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Images var view = (UserView)item; var isUsingCollectionStrip = IsUsingCollectionStrip(view); - var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.BoxSets && view.ViewType != CollectionType.Playlists; + var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.boxsets && view.ViewType != CollectionType.playlists; var result = view.GetItemList(new InternalItemsQuery { @@ -114,9 +114,9 @@ namespace Emby.Server.Implementations.Images { CollectionType[] collectionStripViewTypes = { - CollectionType.Movies, - CollectionType.TvShows, - CollectionType.Playlists + CollectionType.movies, + CollectionType.tvshows, + CollectionType.playlists }; return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f40177fa77..a79ffd9cb1 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1514,7 +1514,7 @@ namespace Emby.Server.Implementations.Library { if (item is UserView view) { - if (view.ViewType == CollectionType.LiveTv) + if (view.ViewType == CollectionType.livetv) { return new[] { view.Id }; } @@ -1543,7 +1543,7 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user is not null && view.ViewType != CollectionType.Unknown && UserView.IsEligibleForGrouping(view.ViewType) + if (user is not null && view.ViewType != CollectionType.unknown && UserView.IsEligibleForGrouping(view.ViewType) && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index ac423ed091..dbf05c1dbb 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio List files, CollectionType? collectionType) { - if (collectionType == CollectionType.Books) + if (collectionType == CollectionType.books) { return ResolveMultipleAudio(parent, files, true); } @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - var isBooksCollectionType = collectionType == CollectionType.Books; + var isBooksCollectionType = collectionType == CollectionType.books; if (args.IsDirectory) { @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio MediaBrowser.Controller.Entities.Audio.Audio item = null; - var isMusicCollectionType = collectionType == CollectionType.Music; + var isMusicCollectionType = collectionType == CollectionType.music; // Use regular audio type for mixed libraries, owned items and music if (isMixedCollectionType || diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 06e292f4cf..0bfb7fbe6a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio protected override MusicAlbum Resolve(ItemResolveArgs args) { var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = collectionType == CollectionType.Music; + var isMusicMediaFolder = collectionType == CollectionType.music; // If there's a collection type and it's not music, don't allow it. if (!isMusicMediaFolder) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 7d6f97b121..1bdae7f62b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = collectionType == CollectionType.Music; + var isMusicMediaFolder = collectionType == CollectionType.music; // If there's a collection type and it's not music, it can't be a music artist if (!isMusicMediaFolder) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index b76bfe4274..464a548ab9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books var collectionType = args.GetCollectionType(); // Only process items that are in a collection folder containing books - if (collectionType != CollectionType.Books) + if (collectionType != CollectionType.books) { return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 50fd8b8779..1a210e3cc8 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -31,11 +31,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static readonly CollectionType[] _validCollectionTypes = new[] { - CollectionType.Movies, - CollectionType.HomeVideos, - CollectionType.MusicVideos, - CollectionType.TvShows, - CollectionType.Photos + CollectionType.movies, + CollectionType.homevideos, + CollectionType.musicvideos, + CollectionType.tvshows, + CollectionType.photos }; /// @@ -100,12 +100,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies Video movie = null; var files = args.GetActualFileSystemChildren().ToList(); - if (collectionType == CollectionType.MusicVideos) + if (collectionType == CollectionType.musicvideos) { movie = FindMovie(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } - if (collectionType == CollectionType.HomeVideos) + if (collectionType == CollectionType.homevideos) { movie = FindMovie /// The transcode job. - public void OnTranscodeEndRequest(TranscodingJobDto job) + public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount); @@ -775,7 +748,7 @@ public class TranscodingJobHelper : IDisposable /// The process. /// The job. /// The state. - private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state) + private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state) { job.HasExited = true; job.ExitCode = process.ExitCode; @@ -826,8 +799,8 @@ public class TranscodingJobHelper : IDisposable /// /// The path. /// The type. - /// The . - public TranscodingJobDto? OnTranscodeBeginRequest(string path, TranscodingJobType type) + /// The . + public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type) { lock (_activeTranscodingJobs) { @@ -844,7 +817,7 @@ public class TranscodingJobHelper : IDisposable } } - private void OnTranscodeBeginRequest(TranscodingJobDto job) + private void OnTranscodeBeginRequest(TranscodingJob job) { job.ActiveRequestCount++; diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index 4b69392ef4..439f8052c1 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -140,7 +140,7 @@ public class StreamState : EncodingJobInfo, IDisposable /// /// Gets or sets the transcoding job. /// - public TranscodingJobDto? TranscodingJob { get; set; } + public TranscodingJob? TranscodingJob { get; set; } /// public void Dispose() diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs new file mode 100644 index 0000000000..1e6d5933c8 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs @@ -0,0 +1,280 @@ +using System; +using System.Diagnostics; +using System.Threading; +using MediaBrowser.Model.Dto; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Controller.MediaEncoding; + +/// +/// Class TranscodingJob. +/// +public sealed class TranscodingJob : IDisposable +{ + private readonly ILogger _logger; + private readonly object _processLock = new(); + private readonly object _timerLock = new(); + + private Timer? _killTimer; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public TranscodingJob(ILogger logger) + { + _logger = logger; + } + + /// + /// Gets or sets the play session identifier. + /// + public string? PlaySessionId { get; set; } + + /// + /// Gets or sets the live stream identifier. + /// + public string? LiveStreamId { get; set; } + + /// + /// Gets or sets a value indicating whether is live output. + /// + public bool IsLiveOutput { get; set; } + + /// + /// Gets or sets the path. + /// + public MediaSourceInfo? MediaSource { get; set; } + + /// + /// Gets or sets path. + /// + public string? Path { get; set; } + + /// + /// Gets or sets the type. + /// + public TranscodingJobType Type { get; set; } + + /// + /// Gets or sets the process. + /// + public Process? Process { get; set; } + + /// + /// Gets or sets the active request count. + /// + public int ActiveRequestCount { get; set; } + + /// + /// Gets or sets device id. + /// + public string? DeviceId { get; set; } + + /// + /// Gets or sets cancellation token source. + /// + public CancellationTokenSource? CancellationTokenSource { get; set; } + + /// + /// Gets or sets a value indicating whether has exited. + /// + public bool HasExited { get; set; } + + /// + /// Gets or sets exit code. + /// + public int ExitCode { get; set; } + + /// + /// Gets or sets a value indicating whether is user paused. + /// + public bool IsUserPaused { get; set; } + + /// + /// Gets or sets id. + /// + public string? Id { get; set; } + + /// + /// Gets or sets framerate. + /// + public float? Framerate { get; set; } + + /// + /// Gets or sets completion percentage. + /// + public double? CompletionPercentage { get; set; } + + /// + /// Gets or sets bytes downloaded. + /// + public long BytesDownloaded { get; set; } + + /// + /// Gets or sets bytes transcoded. + /// + public long? BytesTranscoded { get; set; } + + /// + /// Gets or sets bit rate. + /// + public int? BitRate { get; set; } + + /// + /// Gets or sets transcoding position ticks. + /// + public long? TranscodingPositionTicks { get; set; } + + /// + /// Gets or sets download position ticks. + /// + public long? DownloadPositionTicks { get; set; } + + /// + /// Gets or sets transcoding throttler. + /// + public TranscodingThrottler? TranscodingThrottler { get; set; } + + /// + /// Gets or sets last ping date. + /// + public DateTime LastPingDate { get; set; } + + /// + /// Gets or sets ping timeout. + /// + public int PingTimeout { get; set; } + + /// + /// Stop kill timer. + /// + public void StopKillTimer() + { + lock (_timerLock) + { + _killTimer?.Change(Timeout.Infinite, Timeout.Infinite); + } + } + + /// + /// Dispose kill timer. + /// + public void DisposeKillTimer() + { + lock (_timerLock) + { + if (_killTimer is not null) + { + _killTimer.Dispose(); + _killTimer = null; + } + } + } + + /// + /// Start kill timer. + /// + /// Callback action. + public void StartKillTimer(Action callback) + { + StartKillTimer(callback, PingTimeout); + } + + /// + /// Start kill timer. + /// + /// Callback action. + /// Callback interval. + public void StartKillTimer(Action callback, int intervalMs) + { + if (HasExited) + { + return; + } + + lock (_timerLock) + { + if (_killTimer is null) + { + _logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + _killTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite); + } + else + { + _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + _killTimer.Change(intervalMs, Timeout.Infinite); + } + } + } + + /// + /// Change kill timer if started. + /// + public void ChangeKillTimerIfStarted() + { + if (HasExited) + { + return; + } + + lock (_timerLock) + { + if (_killTimer is not null) + { + var intervalMs = PingTimeout; + + _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + _killTimer.Change(intervalMs, Timeout.Infinite); + } + } + } + + /// + /// Stops the transcoding job. + /// + public void Stop() + { + lock (_processLock) + { +#pragma warning disable CA1849 // Can't await in lock block + TranscodingThrottler?.Stop().GetAwaiter().GetResult(); + + var process = Process; + + if (!HasExited) + { + try + { + _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", Path); + + process!.StandardInput.WriteLine("q"); + + // Need to wait because killing is asynchronous. + if (!process.WaitForExit(5000)) + { + _logger.LogInformation("Killing FFmpeg process for {Path}", Path); + process.Kill(); + } + } + catch (InvalidOperationException) + { + } + } +#pragma warning restore CA1849 + } + } + + /// + public void Dispose() + { + Process?.Dispose(); + Process = null; + _killTimer?.Dispose(); + _killTimer = null; + CancellationTokenSource?.Dispose(); + CancellationTokenSource = null; + TranscodingThrottler?.Dispose(); + TranscodingThrottler = null; + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingJobDto.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJobDto.cs deleted file mode 100644 index 6f929204ff..0000000000 --- a/MediaBrowser.Controller/MediaEncoding/TranscodingJobDto.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using MediaBrowser.Model.Dto; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Controller.MediaEncoding; - -/// -/// Class TranscodingJob. -/// -public class TranscodingJobDto : IDisposable -{ - /// - /// The process lock. - /// - [SuppressMessage("Microsoft.Performance", "CA1051:NoVisibleInstanceFields", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")] - [SuppressMessage("Microsoft.Performance", "SA1401:PrivateField", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")] - public readonly object ProcessLock = new object(); - - /// - /// Timer lock. - /// - private readonly object _timerLock = new object(); - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public TranscodingJobDto(ILogger logger) - { - Logger = logger; - } - - /// - /// Gets or sets the play session identifier. - /// - /// 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 a value indicating whether is live output. - /// - public bool IsLiveOutput { get; set; } - - /// - /// Gets or sets the path. - /// - /// The path. - public MediaSourceInfo? MediaSource { get; set; } - - /// - /// Gets or sets path. - /// - public string? Path { get; set; } - - /// - /// Gets or sets the type. - /// - /// The type. - public TranscodingJobType Type { get; set; } - - /// - /// Gets or sets the process. - /// - /// The process. - public Process? Process { get; set; } - - /// - /// Gets logger. - /// - public ILogger Logger { get; private set; } - - /// - /// Gets or sets the active request count. - /// - /// The active request count. - public int ActiveRequestCount { get; set; } - - /// - /// Gets or sets the kill timer. - /// - /// The kill timer. - private Timer? KillTimer { get; set; } - - /// - /// Gets or sets device id. - /// - public string? DeviceId { get; set; } - - /// - /// Gets or sets cancellation token source. - /// - public CancellationTokenSource? CancellationTokenSource { get; set; } - - /// - /// Gets or sets a value indicating whether has exited. - /// - public bool HasExited { get; set; } - - /// - /// Gets or sets exit code. - /// - public int ExitCode { get; set; } - - /// - /// Gets or sets a value indicating whether is user paused. - /// - public bool IsUserPaused { get; set; } - - /// - /// Gets or sets id. - /// - public string? Id { get; set; } - - /// - /// Gets or sets framerate. - /// - public float? Framerate { get; set; } - - /// - /// Gets or sets completion percentage. - /// - public double? CompletionPercentage { get; set; } - - /// - /// Gets or sets bytes downloaded. - /// - public long BytesDownloaded { get; set; } - - /// - /// Gets or sets bytes transcoded. - /// - public long? BytesTranscoded { get; set; } - - /// - /// Gets or sets bit rate. - /// - public int? BitRate { get; set; } - - /// - /// Gets or sets transcoding position ticks. - /// - public long? TranscodingPositionTicks { get; set; } - - /// - /// Gets or sets download position ticks. - /// - public long? DownloadPositionTicks { get; set; } - - /// - /// Gets or sets transcoding throttler. - /// - public TranscodingThrottler? TranscodingThrottler { get; set; } - - /// - /// Gets or sets last ping date. - /// - public DateTime LastPingDate { get; set; } - - /// - /// Gets or sets ping timeout. - /// - public int PingTimeout { get; set; } - - /// - /// Stop kill timer. - /// - public void StopKillTimer() - { - lock (_timerLock) - { - KillTimer?.Change(Timeout.Infinite, Timeout.Infinite); - } - } - - /// - /// Dispose kill timer. - /// - public void DisposeKillTimer() - { - lock (_timerLock) - { - if (KillTimer is not null) - { - KillTimer.Dispose(); - KillTimer = null; - } - } - } - - /// - /// Start kill timer. - /// - /// Callback action. - public void StartKillTimer(Action callback) - { - StartKillTimer(callback, PingTimeout); - } - - /// - /// Start kill timer. - /// - /// Callback action. - /// Callback interval. - public void StartKillTimer(Action callback, int intervalMs) - { - if (HasExited) - { - return; - } - - lock (_timerLock) - { - if (KillTimer is null) - { - Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite); - } - else - { - Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer.Change(intervalMs, Timeout.Infinite); - } - } - } - - /// - /// Change kill timer if started. - /// - public void ChangeKillTimerIfStarted() - { - if (HasExited) - { - return; - } - - lock (_timerLock) - { - if (KillTimer is not null) - { - var intervalMs = PingTimeout; - - Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer.Change(intervalMs, Timeout.Infinite); - } - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose all resources. - /// - /// Whether to dispose all resources. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - Process?.Dispose(); - Process = null; - KillTimer?.Dispose(); - KillTimer = null; - CancellationTokenSource?.Dispose(); - CancellationTokenSource = null; - TranscodingThrottler?.Dispose(); - TranscodingThrottler = null; - } - } -} diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs index aa08af54f0..813f13eaef 100644 --- a/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs +++ b/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.MediaEncoding; /// public class TranscodingThrottler : IDisposable { - private readonly TranscodingJobDto _job; + private readonly TranscodingJob _job; private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IFileSystem _fileSystem; @@ -29,7 +29,7 @@ public class TranscodingThrottler : IDisposable /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - public TranscodingThrottler(TranscodingJobDto job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder) + public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder) { _job = job; _logger = logger; @@ -145,7 +145,7 @@ public class TranscodingThrottler : IDisposable } } - private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds) + private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds) { var bytesDownloaded = job.BytesDownloaded; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; -- cgit v1.2.3 From 9215a4d40ae24e5996a5e16dfa296b09a7befc40 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 31 Oct 2023 13:26:37 -0400 Subject: Add ITranscodeManager service --- Emby.Server.Implementations/ApplicationHost.cs | 3 +- Jellyfin.Api/Controllers/AudioController.cs | 1 + Jellyfin.Api/Controllers/DynamicHlsController.cs | 46 +- Jellyfin.Api/Controllers/HlsSegmentController.cs | 14 +- Jellyfin.Api/Controllers/LiveTvController.cs | 11 +- Jellyfin.Api/Controllers/PlaystateController.cs | 17 +- .../Controllers/UniversalAudioController.cs | 1 + Jellyfin.Api/Controllers/VideosController.cs | 16 +- Jellyfin.Api/Helpers/AudioHelper.cs | 16 +- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 12 +- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 21 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 1 + Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 12 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 10 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 896 --------------------- .../Models/StreamingDtos/HlsAudioRequestDto.cs | 4 +- .../Models/StreamingDtos/HlsVideoRequestDto.cs | 4 +- Jellyfin.Api/Models/StreamingDtos/StreamState.cs | 184 ----- .../Models/StreamingDtos/StreamingRequestDto.cs | 49 -- .../Models/StreamingDtos/VideoRequestDto.cs | 23 - .../MediaEncoding/ITranscodeManager.cs | 104 +++ MediaBrowser.Controller/Streaming/StreamState.cs | 183 +++++ .../Streaming/StreamingRequestDto.cs | 49 ++ .../Streaming/VideoRequestDto.cs | 23 + .../Transcoding/TranscodeManager.cs | 750 +++++++++++++++++ 25 files changed, 1213 insertions(+), 1237 deletions(-) delete mode 100644 Jellyfin.Api/Helpers/TranscodingJobHelper.cs delete mode 100644 Jellyfin.Api/Models/StreamingDtos/StreamState.cs delete mode 100644 Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs delete mode 100644 Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs create mode 100644 MediaBrowser.Controller/Streaming/StreamState.cs create mode 100644 MediaBrowser.Controller/Streaming/StreamingRequestDto.cs create mode 100644 MediaBrowser.Controller/Streaming/VideoRequestDto.cs create mode 100644 MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index dce56e0a4f..f385f6a512 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -76,6 +76,7 @@ using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Subtitles; +using MediaBrowser.MediaEncoding.Transcoding; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; @@ -583,7 +584,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 5bc5330861..cd09d2bfab 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dlna; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 260ed4787b..dda1e9d561 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; @@ -18,6 +19,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; @@ -50,7 +52,7 @@ public class DynamicHlsController : BaseJellyfinApiController private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; private readonly ILogger _logger; private readonly EncodingHelper _encodingHelper; private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator; @@ -66,7 +68,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the class. + /// Instance of the interface. /// Instance of the interface. /// Instance of . /// Instance of . @@ -78,7 +80,7 @@ public class DynamicHlsController : BaseJellyfinApiController IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, ILogger logger, DynamicHlsHelper dynamicHlsHelper, EncodingHelper encodingHelper, @@ -90,7 +92,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; _fileSystem = fileSystem; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _logger = logger; _dynamicHlsHelper = dynamicHlsHelper; _encodingHelper = encodingHelper; @@ -282,7 +284,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, TranscodingJobType, cancellationToken) .ConfigureAwait(false); @@ -292,7 +294,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (!System.IO.File.Exists(playlistPath)) { - var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); + var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -301,11 +303,11 @@ public class DynamicHlsController : BaseJellyfinApiController // If the playlist doesn't already exist, startup ffmpeg try { - job = await _transcodingJobHelper.StartFfMpeg( + job = await _transcodeManager.StartFfMpeg( state, playlistPath, GetCommandLineArguments(playlistPath, state, true, 0), - Request, + Request.HttpContext.User.GetUserId(), TranscodingJobType, cancellationTokenSource) .ConfigureAwait(false); @@ -330,11 +332,11 @@ public class DynamicHlsController : BaseJellyfinApiController } } - job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); if (job is not null) { - _transcodingJobHelper.OnTranscodeEndRequest(job); + _transcodeManager.OnTranscodeEndRequest(job); } var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state); @@ -1382,7 +1384,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, TranscodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -1420,7 +1422,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, TranscodingJobType, cancellationToken) .ConfigureAwait(false); @@ -1435,12 +1437,12 @@ public class DynamicHlsController : BaseJellyfinApiController if (System.IO.File.Exists(segmentPath)) { - job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } - var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); + var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); var released = false; var startTranscoding = false; @@ -1449,7 +1451,7 @@ public class DynamicHlsController : BaseJellyfinApiController { if (System.IO.File.Exists(segmentPath)) { - job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); transcodingLock.Release(); released = true; _logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); @@ -1487,7 +1489,7 @@ public class DynamicHlsController : BaseJellyfinApiController // If the playlist doesn't already exist, startup ffmpeg try { - await _transcodingJobHelper.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false) + await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false) .ConfigureAwait(false); if (currentTranscodingIndex.HasValue) @@ -1498,11 +1500,11 @@ public class DynamicHlsController : BaseJellyfinApiController streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks; state.WaitForPath = segmentPath; - job = await _transcodingJobHelper.StartFfMpeg( + job = await _transcodeManager.StartFfMpeg( state, playlistPath, GetCommandLineArguments(playlistPath, state, false, segmentId), - Request, + Request.HttpContext.User.GetUserId(), TranscodingJobType, cancellationTokenSource).ConfigureAwait(false); } @@ -1516,7 +1518,7 @@ public class DynamicHlsController : BaseJellyfinApiController } else { - job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); if (job?.TranscodingThrottler is not null) { await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false); @@ -1533,7 +1535,7 @@ public class DynamicHlsController : BaseJellyfinApiController } _logger.LogDebug("returning {0} [general case]", segmentPath); - job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } @@ -2000,7 +2002,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (transcodingJob is not null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); - _transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); + _transcodeManager.OnTranscodeEndRequest(transcodingJob); } return Task.CompletedTask; @@ -2011,7 +2013,7 @@ public class DynamicHlsController : BaseJellyfinApiController private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) { - var job = _transcodingJobHelper.GetTranscodingJob(playlist, TranscodingJobType); + var job = _transcodeManager.GetTranscodingJob(playlist, TranscodingJobType); if (job is null || job.HasExited) { diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 392d9955fb..1927a332b2 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -24,22 +24,22 @@ public class HlsSegmentController : BaseJellyfinApiController { private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _serverConfigurationManager; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. - /// Initialized instance of the . + /// Instance of the interface. public HlsSegmentController( IFileSystem fileSystem, IServerConfigurationManager serverConfigurationManager, - TranscodingJobHelper transcodingJobHelper) + ITranscodeManager transcodeManager) { _fileSystem = fileSystem; _serverConfigurationManager = serverConfigurationManager; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; } /// @@ -112,7 +112,7 @@ public class HlsSegmentController : BaseJellyfinApiController [FromQuery, Required] string deviceId, [FromQuery, Required] string playSessionId) { - _transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true); + _transcodeManager.KillTranscodingJobs(deviceId, playSessionId, _ => true); return NoContent(); } @@ -174,13 +174,13 @@ public class HlsSegmentController : BaseJellyfinApiController private ActionResult GetFileResult(string path, string playlistPath) { - var transcodingJob = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); + var transcodingJob = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); Response.OnCompleted(() => { if (transcodingJob is not null) { - _transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); + _transcodeManager.OnTranscodeEndRequest(transcodingJob); } return Task.CompletedTask; diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 425086895b..a40f273ae4 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -24,6 +24,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -47,7 +48,7 @@ public class LiveTvController : BaseJellyfinApiController private readonly IDtoService _dtoService; private readonly IMediaSourceManager _mediaSourceManager; private readonly IConfigurationManager _configurationManager; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; /// /// Initializes a new instance of the class. @@ -59,7 +60,7 @@ public class LiveTvController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the class. + /// Instance of the interface. public LiveTvController( ILiveTvManager liveTvManager, IUserManager userManager, @@ -68,7 +69,7 @@ public class LiveTvController : BaseJellyfinApiController IDtoService dtoService, IMediaSourceManager mediaSourceManager, IConfigurationManager configurationManager, - TranscodingJobHelper transcodingJobHelper) + ITranscodeManager transcodeManager) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -77,7 +78,7 @@ public class LiveTvController : BaseJellyfinApiController _dtoService = dtoService; _mediaSourceManager = mediaSourceManager; _configurationManager = configurationManager; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; } /// @@ -1171,7 +1172,7 @@ public class LiveTvController : BaseJellyfinApiController return NotFound(); } - var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper); + var stream = new ProgressiveFileStream(path, null, _transcodeManager); return new FileStreamResult(stream, MimeTypes.GetMimeType(path)); } diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index 8ad553bcb8..bde2f4d1ac 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; @@ -30,7 +31,7 @@ public class PlaystateController : BaseJellyfinApiController private readonly ILibraryManager _libraryManager; private readonly ISessionManager _sessionManager; private readonly ILogger _logger; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; /// /// Initializes a new instance of the class. @@ -40,14 +41,14 @@ public class PlaystateController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Th singleton. + /// Instance of the interface. public PlaystateController( IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, ISessionManager sessionManager, ILoggerFactory loggerFactory, - TranscodingJobHelper transcodingJobHelper) + ITranscodeManager transcodeManager) { _userManager = userManager; _userDataRepository = userDataRepository; @@ -55,7 +56,7 @@ public class PlaystateController : BaseJellyfinApiController _sessionManager = sessionManager; _logger = loggerFactory.CreateLogger(); - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; } /// @@ -188,7 +189,7 @@ public class PlaystateController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId) { - _transcodingJobHelper.PingTranscodingJob(playSessionId, null); + _transcodeManager.PingTranscodingJob(playSessionId, null); return NoContent(); } @@ -205,7 +206,7 @@ public class PlaystateController : BaseJellyfinApiController _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) { - await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); @@ -354,7 +355,7 @@ public class PlaystateController : BaseJellyfinApiController _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) { - await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); @@ -388,7 +389,7 @@ public class PlaystateController : BaseJellyfinApiController { if (method == PlayMethod.Transcode) { - var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodingJobHelper.GetTranscodingJob(playSessionId); + var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodeManager.GetTranscodingJob(playSessionId); if (job is null) { return PlayMethod.DirectPlay; diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 7177a04403..0a416aedbe 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -11,6 +11,7 @@ using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.MediaInfo; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 5d9868eb9f..c231c147fc 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -11,7 +11,6 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -20,6 +19,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -43,7 +43,7 @@ public class VideosController : BaseJellyfinApiController private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; private readonly IHttpClientFactory _httpClientFactory; private readonly EncodingHelper _encodingHelper; @@ -58,7 +58,7 @@ public class VideosController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the class. + /// Instance of the interface. /// Instance of the interface. /// Instance of . public VideosController( @@ -68,7 +68,7 @@ public class VideosController : BaseJellyfinApiController IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, IHttpClientFactory httpClientFactory, EncodingHelper encodingHelper) { @@ -78,7 +78,7 @@ public class VideosController : BaseJellyfinApiController _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _httpClientFactory = httpClientFactory; _encodingHelper = encodingHelper; } @@ -427,7 +427,7 @@ public class VideosController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, _transcodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -466,7 +466,7 @@ public class VideosController : BaseJellyfinApiController if (state.MediaSource.IsInfiniteStream) { - var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper); + var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager); return File(liveStream, contentType); } @@ -482,7 +482,7 @@ public class VideosController : BaseJellyfinApiController state, isHeadRequest, HttpContext, - _transcodingJobHelper, + _transcodeManager, ffmpegCommandLineArguments, _transcodingJobType, cancellationTokenSource).ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 926ce99dd8..c80a9d582d 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -2,13 +2,13 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Http; @@ -26,7 +26,7 @@ public class AudioHelper private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpContextAccessor _httpContextAccessor; private readonly EncodingHelper _encodingHelper; @@ -39,7 +39,7 @@ public class AudioHelper /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of . + /// Instance of interface. /// Instance of the interface. /// Instance of the interface. /// Instance of . @@ -49,7 +49,7 @@ public class AudioHelper IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor, EncodingHelper encodingHelper) @@ -59,7 +59,7 @@ public class AudioHelper _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _httpClientFactory = httpClientFactory; _httpContextAccessor = httpContextAccessor; _encodingHelper = encodingHelper; @@ -94,7 +94,7 @@ public class AudioHelper _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, transcodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -133,7 +133,7 @@ public class AudioHelper if (state.MediaSource.IsInfiniteStream) { - var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper); + var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager); return new FileStreamResult(stream, contentType); } @@ -149,7 +149,7 @@ public class AudioHelper state, isHeadRequest, _httpContextAccessor.HttpContext, - _transcodingJobHelper, + _transcodeManager, ffmpegCommandLineArguments, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 05f7d44bf0..fa81fc284d 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Extensions; @@ -18,6 +17,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Controller.Trickplay; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; @@ -39,7 +39,7 @@ public class DynamicHlsHelper private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; private readonly INetworkManager _networkManager; private readonly ILogger _logger; private readonly IHttpContextAccessor _httpContextAccessor; @@ -54,7 +54,7 @@ public class DynamicHlsHelper /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of . + /// Instance of . /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. @@ -66,7 +66,7 @@ public class DynamicHlsHelper IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, INetworkManager networkManager, ILogger logger, IHttpContextAccessor httpContextAccessor, @@ -78,7 +78,7 @@ public class DynamicHlsHelper _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _networkManager = networkManager; _logger = logger; _httpContextAccessor = httpContextAccessor; @@ -130,7 +130,7 @@ public class DynamicHlsHelper _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, transcodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index fafa2c0551..5385979d4a 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -4,8 +4,9 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Api.Extensions; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; @@ -64,7 +65,7 @@ public static class FileStreamResponseHelpers /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The current http context. - /// The singleton. + /// The singleton. /// The command line arguments to start ffmpeg. /// The . /// The . @@ -73,7 +74,7 @@ public static class FileStreamResponseHelpers StreamState state, bool isHeadRequest, HttpContext httpContext, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, string ffmpegCommandLineArguments, TranscodingJobType transcodingJobType, CancellationTokenSource cancellationTokenSource) @@ -92,22 +93,28 @@ public static class FileStreamResponseHelpers return new OkResult(); } - var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); + var transcodingLock = transcodeManager.GetTranscodingLock(outputPath); await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { TranscodingJob? job; if (!File.Exists(outputPath)) { - job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, httpContext.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); + job = await transcodeManager.StartFfMpeg( + state, + outputPath, + ffmpegCommandLineArguments, + httpContext.User.GetUserId(), + transcodingJobType, + cancellationTokenSource).ConfigureAwait(false); } else { - job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); + job = transcodeManager.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); state.Dispose(); } - var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper); + var stream = new ProgressiveFileStream(outputPath, job, transcodeManager); return new FileStreamResult(stream, contentType); } finally diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index e2d3bfb193..c8a36c562e 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 18088483d3..98ea844a96 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -15,7 +15,7 @@ public class ProgressiveFileStream : Stream { private readonly Stream _stream; private readonly TranscodingJob? _job; - private readonly TranscodingJobHelper? _transcodingJobHelper; + private readonly ITranscodeManager? _transcodeManager; private readonly int _timeoutMs; private bool _disposed; @@ -24,12 +24,12 @@ public class ProgressiveFileStream : Stream /// /// The path to the transcoded file. /// The transcoding job information. - /// The transcoding job helper. + /// The transcode manager. /// The timeout duration in milliseconds. - public ProgressiveFileStream(string filePath, TranscodingJob? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000) + public ProgressiveFileStream(string filePath, TranscodingJob? job, ITranscodeManager transcodeManager, int timeoutMs = 30000) { _job = job; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _timeoutMs = timeoutMs; _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); @@ -43,7 +43,7 @@ public class ProgressiveFileStream : Stream public ProgressiveFileStream(Stream stream, int timeoutMs = 30000) { _job = null; - _transcodingJobHelper = null; + _transcodeManager = null; _timeoutMs = timeoutMs; _stream = stream; } @@ -153,7 +153,7 @@ public class ProgressiveFileStream : Stream if (_job is not null) { - _transcodingJobHelper?.OnTranscodeEndRequest(_job); + _transcodeManager?.OnTranscodeEndRequest(_job); } } } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 71c62b2356..78943f7b58 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; @@ -14,6 +13,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -38,7 +38,7 @@ public static class StreamingHelpers /// Instance of the interface. /// Instance of the interface. /// Instance of . - /// Initialized . + /// Instance of the interface. /// The . /// The . /// A containing the current . @@ -51,7 +51,7 @@ public static class StreamingHelpers IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, EncodingHelper encodingHelper, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, TranscodingJobType transcodingJobType, CancellationToken cancellationToken) { @@ -74,7 +74,7 @@ public static class StreamingHelpers streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); } - var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) + var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager) { Request = streamingRequest, RequestedUrl = url, @@ -115,7 +115,7 @@ public static class StreamingHelpers if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) { var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId) - ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId) + ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId) : null; if (currentJob is not null) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs deleted file mode 100644 index 9a6ec17fdb..0000000000 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ /dev/null @@ -1,896 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Api.Extensions; -using Jellyfin.Api.Models.StreamingDtos; -using Jellyfin.Data.Enums; -using MediaBrowser.Common; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Session; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Api.Helpers; - -/// -/// Transcoding job helpers. -/// -public class TranscodingJobHelper : IDisposable -{ - /// - /// The active transcoding jobs. - /// - private static readonly List _activeTranscodingJobs = new List(); - - /// - /// The transcoding locks. - /// - private static readonly Dictionary _transcodingLocks = new Dictionary(); - - private readonly IAttachmentExtractor _attachmentExtractor; - private readonly IApplicationPaths _appPaths; - private readonly EncodingHelper _encodingHelper; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - private readonly IMediaEncoder _mediaEncoder; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IServerConfigurationManager _serverConfigurationManager; - private readonly ISessionManager _sessionManager; - private readonly ILoggerFactory _loggerFactory; - private readonly IUserManager _userManager; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of . - /// Instance of the interface. - /// Instance of the interface. - public TranscodingJobHelper( - IAttachmentExtractor attachmentExtractor, - IApplicationPaths appPaths, - ILogger logger, - IMediaSourceManager mediaSourceManager, - IFileSystem fileSystem, - IMediaEncoder mediaEncoder, - IServerConfigurationManager serverConfigurationManager, - ISessionManager sessionManager, - EncodingHelper encodingHelper, - ILoggerFactory loggerFactory, - IUserManager userManager) - { - _attachmentExtractor = attachmentExtractor; - _appPaths = appPaths; - _logger = logger; - _mediaSourceManager = mediaSourceManager; - _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; - _serverConfigurationManager = serverConfigurationManager; - _sessionManager = sessionManager; - _encodingHelper = encodingHelper; - _loggerFactory = loggerFactory; - _userManager = userManager; - - DeleteEncodedMediaCache(); - - sessionManager.PlaybackProgress += OnPlaybackProgress; - sessionManager.PlaybackStart += OnPlaybackProgress; - } - - /// - /// Get transcoding job. - /// - /// Playback session id. - /// The transcoding job. - public TranscodingJob? GetTranscodingJob(string playSessionId) - { - lock (_activeTranscodingJobs) - { - return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); - } - } - - /// - /// Get transcoding job. - /// - /// Path to the transcoding file. - /// The . - /// The transcoding job. - public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type) - { - lock (_activeTranscodingJobs) - { - return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - } - } - - /// - /// Ping transcoding job. - /// - /// Play session id. - /// Is user paused. - /// Play session id is null. - public void PingTranscodingJob(string playSessionId, bool? isUserPaused) - { - ArgumentException.ThrowIfNullOrEmpty(playSessionId); - - _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused); - - List jobs; - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably. - jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - foreach (var job in jobs) - { - if (isUserPaused.HasValue) - { - _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id); - job.IsUserPaused = isUserPaused.Value; - } - - PingTimer(job, true); - } - } - - private void PingTimer(TranscodingJob job, bool isProgressCheckIn) - { - if (job.HasExited) - { - job.StopKillTimer(); - return; - } - - var timerDuration = 10000; - - if (job.Type != TranscodingJobType.Progressive) - { - 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(OnTranscodeKillTimerStopped); - } - else - { - job.ChangeKillTimerIfStarted(); - } - } - - /// - /// Called when [transcode kill timer stopped]. - /// - /// The state. - private async void OnTranscodeKillTimerStopped(object? state) - { - var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(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.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - - await KillTranscodingJob(job, true, path => true).ConfigureAwait(false); - } - - /// - /// Kills the single transcoding job. - /// - /// The device id. - /// The play session identifier. - /// The delete files. - /// Task. - public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func deleteFiles) - { - return KillTranscodingJobs( - j => string.IsNullOrWhiteSpace(playSessionId) - ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase) - : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), - deleteFiles); - } - - /// - /// Kills the transcoding jobs. - /// - /// The kill job. - /// The delete files. - /// Task. - private Task KillTranscodingJobs(Func killJob, Func deleteFiles) - { - var jobs = new List(); - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably. - jobs.AddRange(_activeTranscodingJobs.Where(killJob)); - } - - if (jobs.Count == 0) - { - return Task.CompletedTask; - } - - IEnumerable GetKillJobs() - { - foreach (var job in jobs) - { - yield return KillTranscodingJob(job, false, deleteFiles); - } - } - - return Task.WhenAll(GetKillJobs()); - } - - /// - /// Kills the transcoding job. - /// - /// The job. - /// if set to true [close live stream]. - /// The delete. - private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) - { - job.DisposeKillTimer(); - - _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - - lock (_activeTranscodingJobs) - { - _activeTranscodingJobs.Remove(job); - - if (job.CancellationTokenSource?.IsCancellationRequested == false) - { -#pragma warning disable CA1849 // Can't await in lock block - job.CancellationTokenSource.Cancel(); -#pragma warning restore CA1849 - } - } - - lock (_transcodingLocks) - { - _transcodingLocks.Remove(job.Path!); - } - - job.Stop(); - - if (delete(job.Path!)) - { - await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); - if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) - { - var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat"); - if (File.Exists(concatFilePath)) - { - _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); - File.Delete(concatFilePath); - } - } - } - - if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing live stream for {Path}", job.Path); - } - } - } - - private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) - { - if (retryCount >= 10) - { - return; - } - - _logger.LogInformation("Deleting partial stream file(s) {Path}", path); - - await Task.Delay(delayMs).ConfigureAwait(false); - - try - { - if (jobType == TranscodingJobType.Progressive) - { - DeleteProgressivePartialStreamFiles(path); - } - else - { - DeleteHlsPartialStreamFiles(path); - } - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); - - await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); - } - } - - /// - /// Deletes the progressive partial stream files. - /// - /// The output file path. - private void DeleteProgressivePartialStreamFiles(string outputFilePath) - { - if (File.Exists(outputFilePath)) - { - _fileSystem.DeleteFile(outputFilePath); - } - } - - /// - /// Deletes the HLS partial stream files. - /// - /// The output file path. - private void DeleteHlsPartialStreamFiles(string outputFilePath) - { - var directory = Path.GetDirectoryName(outputFilePath) - ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath)); - - var name = Path.GetFileNameWithoutExtension(outputFilePath); - - var filesToDelete = _fileSystem.GetFilePaths(directory) - .Where(f => f.Contains(name, StringComparison.OrdinalIgnoreCase)); - - List? exs = null; - foreach (var file in filesToDelete) - { - try - { - _logger.LogDebug("Deleting HLS file {0}", file); - _fileSystem.DeleteFile(file); - } - catch (IOException ex) - { - (exs ??= new List(4)).Add(ex); - _logger.LogError(ex, "Error deleting HLS file {Path}", file); - } - } - - if (exs is not null) - { - throw new AggregateException("Error deleting HLS files", exs); - } - } - - /// - /// Report the transcoding progress to the session manager. - /// - /// The of which the progress will be reported. - /// The of the current transcoding job. - /// The current transcoding position. - /// The framerate of the transcoding job. - /// The completion percentage of the transcode. - /// The number of bytes transcoded. - /// The bitrate of the transcoding job. - public void ReportTranscodingProgress( - TranscodingJob job, - StreamState state, - TimeSpan? transcodingPosition, - float? framerate, - double? percentComplete, - long? bytesTranscoded, - int? bitRate) - { - var ticks = transcodingPosition?.Ticks; - - if (job is not null) - { - job.Framerate = framerate; - job.CompletionPercentage = percentComplete; - job.TranscodingPositionTicks = ticks; - job.BytesTranscoded = bytesTranscoded; - job.BitRate = bitRate; - } - - var deviceId = state.Request.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; - var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; - HardwareEncodingType? hardwareAccelerationType = null; - if (Enum.TryParse(hardwareAccelerationTypeString, out var parsedHardwareAccelerationType)) - { - hardwareAccelerationType = parsedHardwareAccelerationType; - } - - _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo - { - Bitrate = bitRate ?? state.TotalOutputBitrate, - AudioCodec = audioCodec, - VideoCodec = videoCodec, - Container = state.OutputContainer, - Framerate = framerate, - CompletionPercentage = percentComplete, - Width = state.OutputWidth, - Height = state.OutputHeight, - AudioChannels = state.OutputAudioChannels, - IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec), - IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec), - HardwareAccelerationType = hardwareAccelerationType, - TranscodeReasons = state.TranscodeReasons - }); - } - } - - /// - /// Starts FFmpeg. - /// - /// The state. - /// The output path. - /// The command line arguments for FFmpeg. - /// The . - /// The . - /// The cancellation token source. - /// The working directory. - /// Task. - public async Task StartFfMpeg( - StreamState state, - string outputPath, - string commandLineArguments, - HttpRequest request, - TranscodingJobType transcodingJobType, - CancellationTokenSource cancellationTokenSource, - string? workingDirectory = null) - { - var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); - Directory.CreateDirectory(directory); - - await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); - - if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) - { - var userId = request.HttpContext.User.GetUserId(); - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); - if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) - { - this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); - - throw new ArgumentException("User does not have access to video transcoding."); - } - } - - ArgumentException.ThrowIfNullOrEmpty(_mediaEncoder.EncoderPath); - - // If subtitles get burned in fonts may need to be extracted from the media file - if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) - { - var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); - if (state.VideoType != VideoType.Dvd) - { - await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); - } - - if (state.SubtitleStream.IsExternal && Path.GetExtension(state.SubtitleStream.Path.AsSpan()).Equals(".mks", StringComparison.OrdinalIgnoreCase)) - { - string subtitlePath = state.SubtitleStream.Path; - string subtitlePathArgument = string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", subtitlePath.Replace("\"", "\\\"", StringComparison.Ordinal)); - string subtitleId = subtitlePath.GetMD5().ToString("N", CultureInfo.InvariantCulture); - - await _attachmentExtractor.ExtractAllAttachmentsExternal(subtitlePathArgument, subtitleId, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - // RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - FileName = _mediaEncoder.EncoderPath, - Arguments = commandLineArguments, - WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? string.Empty : workingDirectory, - ErrorDialog = false - }, - EnableRaisingEvents = true - }; - - var transcodingJob = this.OnTranscodeBeginning( - outputPath, - state.Request.PlaySessionId, - state.MediaSource.LiveStreamId, - Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - transcodingJobType, - process, - state.Request.DeviceId, - state, - cancellationTokenSource); - - _logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); - - var logFilePrefix = "FFmpeg.Transcode-"; - if (state.VideoRequest is not null - && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) - { - logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec) - ? "FFmpeg.Remux-" - : "FFmpeg.DirectStream-"; - } - - var logFilePath = Path.Combine( - _serverConfigurationManager.ApplicationPaths.LogDirectoryPath, - $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); - - // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting FFmpeg"); - - this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); - - throw; - } - - _logger.LogDebug("Launched FFmpeg process"); - state.TranscodingJob = transcodingJob; - - // Important - don't await the log task or we won't be able to kill FFmpeg when the user stops playback - _ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError, logStream); - - // Wait for the file to exist before proceeding - var ffmpegTargetFile = state.WaitForPath ?? outputPath; - _logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile); - while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited) - { - await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); - } - - _logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile); - - if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) - { - await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); - - if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited) - { - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - if (!transcodingJob.HasExited) - { - StartThrottler(state, transcodingJob); - } - else if (transcodingJob.ExitCode != 0) - { - throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "FFmpeg exited with code {0}", transcodingJob.ExitCode)); - } - - _logger.LogDebug("StartFfMpeg() finished successfully"); - - return transcodingJob; - } - - private void StartThrottler(StreamState state, TranscodingJob transcodingJob) - { - if (EnableThrottling(state)) - { - transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, _loggerFactory.CreateLogger(), _serverConfigurationManager, _fileSystem, _mediaEncoder); - transcodingJob.TranscodingThrottler.Start(); - } - } - - private bool EnableThrottling(StreamState state) - { - var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); - - return state.InputProtocol == MediaProtocol.File && - state.RunTimeTicks.HasValue && - state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && - state.IsInputVideo && - state.VideoType == VideoType.VideoFile; - } - - /// - /// Called when [transcode beginning]. - /// - /// The path. - /// The play session identifier. - /// The live stream identifier. - /// The transcoding job identifier. - /// The type. - /// The process. - /// The device id. - /// The state. - /// The cancellation token source. - /// TranscodingJob. - public TranscodingJob OnTranscodeBeginning( - string path, - string? playSessionId, - string? liveStreamId, - string transcodingJobId, - TranscodingJobType type, - Process process, - string? deviceId, - StreamState state, - CancellationTokenSource cancellationTokenSource) - { - lock (_activeTranscodingJobs) - { - var job = new TranscodingJob(_loggerFactory.CreateLogger()) - { - Type = type, - Path = path, - Process = process, - ActiveRequestCount = 1, - DeviceId = deviceId, - CancellationTokenSource = cancellationTokenSource, - Id = transcodingJobId, - PlaySessionId = playSessionId, - LiveStreamId = liveStreamId, - MediaSource = state.MediaSource - }; - - _activeTranscodingJobs.Add(job); - - ReportTranscodingProgress(job, state, null, null, null, null, null); - - return job; - } - } - - /// - /// Called when [transcode end]. - /// - /// The transcode job. - public void OnTranscodeEndRequest(TranscodingJob job) - { - job.ActiveRequestCount--; - _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount); - if (job.ActiveRequestCount <= 0) - { - PingTimer(job, false); - } - } - - /// - /// - /// The progressive - /// - /// Called when [transcode failed to start]. - /// - /// The path. - /// The type. - /// The state. - public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state) - { - lock (_activeTranscodingJobs) - { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - - if (job is not null) - { - _activeTranscodingJobs.Remove(job); - } - } - - lock (_transcodingLocks) - { - _transcodingLocks.Remove(path); - } - - if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) - { - _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); - } - } - - /// - /// Processes the exited. - /// - /// The process. - /// The job. - /// The state. - private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state) - { - job.HasExited = true; - job.ExitCode = process.ExitCode; - - ReportTranscodingProgress(job, state, null, null, null, null, null); - - _logger.LogDebug("Disposing stream resources"); - state.Dispose(); - - if (process.ExitCode == 0) - { - _logger.LogInformation("FFmpeg exited with code 0"); - } - else - { - _logger.LogError("FFmpeg exited with code {0}", process.ExitCode); - } - - job.Dispose(); - } - - private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource) - { - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId)) - { - var liveStreamResponse = await _mediaSourceManager.OpenLiveStream( - new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken }, - cancellationTokenSource.Token) - .ConfigureAwait(false); - var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); - - _encodingHelper.AttachMediaSourceInfo(state, encodingOptions, liveStreamResponse.MediaSource, state.RequestedUrl); - - if (state.VideoRequest is not null) - { - _encodingHelper.TryStreamCopy(state); - } - } - - if (state.MediaSource.BufferMs.HasValue) - { - await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - /// - /// Called when [transcode begin request]. - /// - /// The path. - /// The type. - /// The . - public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type) - { - lock (_activeTranscodingJobs) - { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - - if (job is null) - { - return null; - } - - OnTranscodeBeginRequest(job); - - return job; - } - } - - private void OnTranscodeBeginRequest(TranscodingJob job) - { - job.ActiveRequestCount++; - - if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive) - { - job.StopKillTimer(); - } - } - - /// - /// Gets the transcoding lock. - /// - /// The output path of the transcoded file. - /// A . - public SemaphoreSlim GetTranscodingLock(string outputPath) - { - lock (_transcodingLocks) - { - if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result)) - { - result = new SemaphoreSlim(1, 1); - _transcodingLocks[outputPath] = result; - } - - return result; - } - } - - private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e) - { - if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) - { - PingTranscodingJob(e.PlaySessionId, e.IsPaused); - } - } - - /// - /// Deletes the encoded media cache. - /// - private void DeleteEncodedMediaCache() - { - var path = _serverConfigurationManager.GetTranscodePath(); - if (!Directory.Exists(path)) - { - return; - } - - foreach (var file in _fileSystem.GetFilePaths(path, true)) - { - _fileSystem.DeleteFile(file); - } - } - - /// - /// Dispose transcoding job helper. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose throttler. - /// - /// Disposing. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _loggerFactory.Dispose(); - _sessionManager.PlaybackProgress -= OnPlaybackProgress; - _sessionManager.PlaybackStart -= OnPlaybackProgress; - } - } -} diff --git a/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs index 4f1abb1ffb..bd176bb6a5 100644 --- a/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs +++ b/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs @@ -1,4 +1,6 @@ -namespace Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.Streaming; + +namespace Jellyfin.Api.Models.StreamingDtos; /// /// The hls video request dto. diff --git a/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs index 1cd3d01323..53b6d7575b 100644 --- a/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs +++ b/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs @@ -1,4 +1,6 @@ -namespace Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.Streaming; + +namespace Jellyfin.Api.Models.StreamingDtos; /// /// The hls video request dto. diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs deleted file mode 100644 index 439f8052c1..0000000000 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using Jellyfin.Api.Helpers; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; - -namespace Jellyfin.Api.Models.StreamingDtos; - -/// -/// The stream state dto. -/// -public class StreamState : EncodingJobInfo, IDisposable -{ - private readonly IMediaSourceManager _mediaSourceManager; - private readonly TranscodingJobHelper _transcodingJobHelper; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// The . - /// The singleton. - public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper) - : base(transcodingType) - { - _mediaSourceManager = mediaSourceManager; - _transcodingJobHelper = transcodingJobHelper; - } - - /// - /// Gets or sets the requested url. - /// - public string? RequestedUrl { get; set; } - - /// - /// Gets or sets the request. - /// - public StreamingRequestDto Request - { - get => (StreamingRequestDto)BaseRequest; - set - { - BaseRequest = value; - IsVideoRequest = VideoRequest is not null; - } - } - - /// - /// Gets the video request. - /// - public VideoRequestDto? VideoRequest => Request as VideoRequestDto; - - /// - /// Gets or sets the direct stream provicer. - /// - /// - /// Deprecated. - /// - public IDirectStreamProvider? DirectStreamProvider { get; set; } - - /// - /// Gets or sets the path to wait for. - /// - public string? WaitForPath { get; set; } - - /// - /// Gets a value indicating whether the request outputs video. - /// - public bool IsOutputVideo => Request is VideoRequestDto; - - /// - /// Gets the segment length. - /// - public int SegmentLength - { - get - { - if (Request.SegmentLength.HasValue) - { - return Request.SegmentLength.Value; - } - - if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) - { - var userAgent = UserAgent ?? string.Empty; - - if (userAgent.Contains("AppleTV", StringComparison.OrdinalIgnoreCase) - || userAgent.Contains("cfnetwork", StringComparison.OrdinalIgnoreCase) - || userAgent.Contains("ipad", StringComparison.OrdinalIgnoreCase) - || userAgent.Contains("iphone", StringComparison.OrdinalIgnoreCase) - || userAgent.Contains("ipod", StringComparison.OrdinalIgnoreCase)) - { - return 6; - } - - if (IsSegmentedLiveStream) - { - return 3; - } - - return 6; - } - - return 3; - } - } - - /// - /// Gets the minimum number of segments. - /// - public int MinSegments - { - get - { - if (Request.MinSegments.HasValue) - { - return Request.MinSegments.Value; - } - - return SegmentLength >= 10 ? 2 : 3; - } - } - - /// - /// Gets or sets the user agent. - /// - public string? UserAgent { get; set; } - - /// - /// Gets or sets a value indicating whether to estimate the content length. - /// - public bool EstimateContentLength { get; set; } - - /// - /// Gets or sets the transcode seek info. - /// - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - - /// - /// Gets or sets the transcoding job. - /// - public TranscodingJob? TranscodingJob { get; set; } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - _transcodingJobHelper.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); - } - - /// - /// Disposes the stream state. - /// - /// Whether the object is currently being disposed. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - // REVIEW: Is this the right place for this? - if (MediaSource.RequiresClosing - && string.IsNullOrWhiteSpace(Request.LiveStreamId) - && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) - { - _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult(); - } - } - - TranscodingJob = null; - - _disposed = true; - } -} diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs deleted file mode 100644 index a357498d4c..0000000000 --- a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Controller.MediaEncoding; - -namespace Jellyfin.Api.Models.StreamingDtos; - -/// -/// The audio streaming request dto. -/// -public class StreamingRequestDto : BaseEncodingJobOptions -{ - /// - /// Gets or sets the params. - /// - public string? Params { get; set; } - - /// - /// Gets or sets the play session id. - /// - public string? PlaySessionId { get; set; } - - /// - /// Gets or sets the tag. - /// - public string? Tag { get; set; } - - /// - /// Gets or sets the segment container. - /// - public string? SegmentContainer { get; set; } - - /// - /// Gets or sets the segment length. - /// - public int? SegmentLength { get; set; } - - /// - /// Gets or sets the min segments. - /// - public int? MinSegments { get; set; } - - /// - /// Gets or sets the position of the requested segment in ticks. - /// - public long CurrentRuntimeTicks { get; set; } - - /// - /// Gets or sets the actual segment length in ticks. - /// - public long ActualSegmentLengthTicks { get; set; } -} diff --git a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs deleted file mode 100644 index 8548fec1a1..0000000000 --- a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Jellyfin.Api.Models.StreamingDtos; - -/// -/// The video request dto. -/// -public class VideoRequestDto : StreamingRequestDto -{ - /// - /// Gets a value indicating whether this instance has fixed resolution. - /// - /// true if this instance has fixed resolution; otherwise, false. - public bool HasFixedResolution => Width.HasValue || Height.HasValue; - - /// - /// Gets or sets a value indicating whether to enable subtitles in the manifest. - /// - public bool EnableSubtitlesInManifest { get; set; } - - /// - /// Gets or sets a value indicating whether to enable trickplay images. - /// - public bool EnableTrickplay { get; set; } -} diff --git a/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs new file mode 100644 index 0000000000..c19a12ae7a --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs @@ -0,0 +1,104 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Streaming; + +namespace MediaBrowser.Controller.MediaEncoding; + +/// +/// A service for managing media transcoding. +/// +public interface ITranscodeManager +{ + /// + /// Get transcoding job. + /// + /// Playback session id. + /// The transcoding job. + public TranscodingJob? GetTranscodingJob(string playSessionId); + + /// + /// Get transcoding job. + /// + /// Path to the transcoding file. + /// The . + /// The transcoding job. + public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type); + + /// + /// Ping transcoding job. + /// + /// Play session id. + /// Is user paused. + /// Play session id is null. + public void PingTranscodingJob(string playSessionId, bool? isUserPaused); + + /// + /// Kills the single transcoding job. + /// + /// The device id. + /// The play session identifier. + /// The delete files. + /// Task. + public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func deleteFiles); + + /// + /// Report the transcoding progress to the session manager. + /// + /// The of which the progress will be reported. + /// The of the current transcoding job. + /// The current transcoding position. + /// The framerate of the transcoding job. + /// The completion percentage of the transcode. + /// The number of bytes transcoded. + /// The bitrate of the transcoding job. + public void ReportTranscodingProgress( + TranscodingJob job, + StreamState state, + TimeSpan? transcodingPosition, + float? framerate, + double? percentComplete, + long? bytesTranscoded, + int? bitRate); + + /// + /// Starts FFMpeg. + /// + /// The state. + /// The output path. + /// The command line arguments for FFmpeg. + /// The user id. + /// The . + /// The cancellation token source. + /// The working directory. + /// Task. + public Task StartFfMpeg( + StreamState state, + string outputPath, + string commandLineArguments, + Guid userId, + TranscodingJobType transcodingJobType, + CancellationTokenSource cancellationTokenSource, + string? workingDirectory = null); + + /// + /// Called when [transcode begin request]. + /// + /// The path. + /// The type. + /// The . + public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type); + + /// + /// Called when [transcode end]. + /// + /// The transcode job. + public void OnTranscodeEndRequest(TranscodingJob job); + + /// + /// Gets the transcoding lock. + /// + /// The output path of the transcoded file. + /// A . + public SemaphoreSlim GetTranscodingLock(string outputPath); +} diff --git a/MediaBrowser.Controller/Streaming/StreamState.cs b/MediaBrowser.Controller/Streaming/StreamState.cs new file mode 100644 index 0000000000..b5dbe29ec7 --- /dev/null +++ b/MediaBrowser.Controller/Streaming/StreamState.cs @@ -0,0 +1,183 @@ +using System; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; + +namespace MediaBrowser.Controller.Streaming; + +/// +/// The stream state dto. +/// +public class StreamState : EncodingJobInfo, IDisposable +{ + private readonly IMediaSourceManager _mediaSourceManager; + private readonly ITranscodeManager _transcodeManager; + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// The . + /// The singleton. + public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, ITranscodeManager transcodeManager) + : base(transcodingType) + { + _mediaSourceManager = mediaSourceManager; + _transcodeManager = transcodeManager; + } + + /// + /// Gets or sets the requested url. + /// + public string? RequestedUrl { get; set; } + + /// + /// Gets or sets the request. + /// + public StreamingRequestDto Request + { + get => (StreamingRequestDto)BaseRequest; + set + { + BaseRequest = value; + IsVideoRequest = VideoRequest is not null; + } + } + + /// + /// Gets the video request. + /// + public VideoRequestDto? VideoRequest => Request as VideoRequestDto; + + /// + /// Gets or sets the direct stream provicer. + /// + /// + /// Deprecated. + /// + public IDirectStreamProvider? DirectStreamProvider { get; set; } + + /// + /// Gets or sets the path to wait for. + /// + public string? WaitForPath { get; set; } + + /// + /// Gets a value indicating whether the request outputs video. + /// + public bool IsOutputVideo => Request is VideoRequestDto; + + /// + /// Gets the segment length. + /// + public int SegmentLength + { + get + { + if (Request.SegmentLength.HasValue) + { + return Request.SegmentLength.Value; + } + + if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) + { + var userAgent = UserAgent ?? string.Empty; + + if (userAgent.Contains("AppleTV", StringComparison.OrdinalIgnoreCase) + || userAgent.Contains("cfnetwork", StringComparison.OrdinalIgnoreCase) + || userAgent.Contains("ipad", StringComparison.OrdinalIgnoreCase) + || userAgent.Contains("iphone", StringComparison.OrdinalIgnoreCase) + || userAgent.Contains("ipod", StringComparison.OrdinalIgnoreCase)) + { + return 6; + } + + if (IsSegmentedLiveStream) + { + return 3; + } + + return 6; + } + + return 3; + } + } + + /// + /// Gets the minimum number of segments. + /// + public int MinSegments + { + get + { + if (Request.MinSegments.HasValue) + { + return Request.MinSegments.Value; + } + + return SegmentLength >= 10 ? 2 : 3; + } + } + + /// + /// Gets or sets the user agent. + /// + public string? UserAgent { get; set; } + + /// + /// Gets or sets a value indicating whether to estimate the content length. + /// + public bool EstimateContentLength { get; set; } + + /// + /// Gets or sets the transcode seek info. + /// + public TranscodeSeekInfo TranscodeSeekInfo { get; set; } + + /// + /// Gets or sets the transcoding job. + /// + public TranscodingJob? TranscodingJob { get; set; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) + { + _transcodeManager.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); + } + + /// + /// Disposes the stream state. + /// + /// Whether the object is currently being disposed. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + // REVIEW: Is this the right place for this? + if (MediaSource.RequiresClosing + && string.IsNullOrWhiteSpace(Request.LiveStreamId) + && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) + { + _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult(); + } + } + + TranscodingJob = null; + + _disposed = true; + } +} diff --git a/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs b/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs new file mode 100644 index 0000000000..e47ef65f06 --- /dev/null +++ b/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs @@ -0,0 +1,49 @@ +using MediaBrowser.Controller.MediaEncoding; + +namespace MediaBrowser.Controller.Streaming; + +/// +/// The audio streaming request dto. +/// +public class StreamingRequestDto : BaseEncodingJobOptions +{ + /// + /// Gets or sets the params. + /// + public string? Params { get; set; } + + /// + /// Gets or sets the play session id. + /// + public string? PlaySessionId { get; set; } + + /// + /// Gets or sets the tag. + /// + public string? Tag { get; set; } + + /// + /// Gets or sets the segment container. + /// + public string? SegmentContainer { get; set; } + + /// + /// Gets or sets the segment length. + /// + public int? SegmentLength { get; set; } + + /// + /// Gets or sets the min segments. + /// + public int? MinSegments { get; set; } + + /// + /// Gets or sets the position of the requested segment in ticks. + /// + public long CurrentRuntimeTicks { get; set; } + + /// + /// Gets or sets the actual segment length in ticks. + /// + public long ActualSegmentLengthTicks { get; set; } +} diff --git a/MediaBrowser.Controller/Streaming/VideoRequestDto.cs b/MediaBrowser.Controller/Streaming/VideoRequestDto.cs new file mode 100644 index 0000000000..44dc831fdc --- /dev/null +++ b/MediaBrowser.Controller/Streaming/VideoRequestDto.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.Streaming; + +/// +/// The video request dto. +/// +public class VideoRequestDto : StreamingRequestDto +{ + /// + /// Gets a value indicating whether this instance has fixed resolution. + /// + /// true if this instance has fixed resolution; otherwise, false. + public bool HasFixedResolution => Width.HasValue || Height.HasValue; + + /// + /// Gets or sets a value indicating whether to enable subtitles in the manifest. + /// + public bool EnableSubtitlesInManifest { get; set; } + + /// + /// Gets or sets a value indicating whether to enable trickplay images. + /// + public bool EnableTrickplay { get; set; } +} diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs new file mode 100644 index 0000000000..483d0a1d82 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -0,0 +1,750 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Streaming; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.MediaEncoding.Transcoding; + +/// +public sealed class TranscodeManager : ITranscodeManager, IDisposable +{ + private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly IApplicationPaths _appPaths; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + private readonly EncodingHelper _encodingHelper; + private readonly IMediaEncoder _mediaEncoder; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IAttachmentExtractor _attachmentExtractor; + + private readonly List _activeTranscodingJobs = new(); + private readonly Dictionary _transcodingLocks = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public TranscodeManager( + ILoggerFactory loggerFactory, + IFileSystem fileSystem, + IApplicationPaths appPaths, + IServerConfigurationManager serverConfigurationManager, + IUserManager userManager, + ISessionManager sessionManager, + EncodingHelper encodingHelper, + IMediaEncoder mediaEncoder, + IMediaSourceManager mediaSourceManager, + IAttachmentExtractor attachmentExtractor) + { + _loggerFactory = loggerFactory; + _fileSystem = fileSystem; + _appPaths = appPaths; + _serverConfigurationManager = serverConfigurationManager; + _userManager = userManager; + _sessionManager = sessionManager; + _encodingHelper = encodingHelper; + _mediaEncoder = mediaEncoder; + _mediaSourceManager = mediaSourceManager; + _attachmentExtractor = attachmentExtractor; + + _logger = loggerFactory.CreateLogger(); + DeleteEncodedMediaCache(); + _sessionManager.PlaybackProgress += OnPlaybackProgress; + _sessionManager.PlaybackStart += OnPlaybackProgress; + } + + /// + public TranscodingJob? GetTranscodingJob(string playSessionId) + { + lock (_activeTranscodingJobs) + { + return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); + } + } + + /// + public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type) + { + lock (_activeTranscodingJobs) + { + return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); + } + } + + /// + public void PingTranscodingJob(string playSessionId, bool? isUserPaused) + { + ArgumentException.ThrowIfNullOrEmpty(playSessionId); + + _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused); + + List jobs; + + lock (_activeTranscodingJobs) + { + // This is really only needed for HLS. + // Progressive streams can stop on their own reliably. + jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + foreach (var job in jobs) + { + if (isUserPaused.HasValue) + { + _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id); + job.IsUserPaused = isUserPaused.Value; + } + + PingTimer(job, true); + } + } + + private void PingTimer(TranscodingJob job, bool isProgressCheckIn) + { + if (job.HasExited) + { + job.StopKillTimer(); + return; + } + + var timerDuration = 10000; + + if (job.Type != TranscodingJobType.Progressive) + { + 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(OnTranscodeKillTimerStopped); + } + else + { + job.ChangeKillTimerIfStarted(); + } + } + + private async void OnTranscodeKillTimerStopped(object? state) + { + var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(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.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); + + await KillTranscodingJob(job, true, path => true).ConfigureAwait(false); + } + + /// + public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func deleteFiles) + { + var jobs = new List(); + + lock (_activeTranscodingJobs) + { + // This is really only needed for HLS. + // Progressive streams can stop on their own reliably. + jobs.AddRange(_activeTranscodingJobs.Where(j => string.IsNullOrWhiteSpace(playSessionId) + ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase) + : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase))); + } + + return Task.WhenAll(GetKillJobs()); + + IEnumerable GetKillJobs() + { + foreach (var job in jobs) + { + yield return KillTranscodingJob(job, false, deleteFiles); + } + } + } + + private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) + { + job.DisposeKillTimer(); + + _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); + + lock (_activeTranscodingJobs) + { + _activeTranscodingJobs.Remove(job); + + if (job.CancellationTokenSource?.IsCancellationRequested == false) + { +#pragma warning disable CA1849 // Can't await in lock block + job.CancellationTokenSource.Cancel(); +#pragma warning restore CA1849 + } + } + + lock (_transcodingLocks) + { + _transcodingLocks.Remove(job.Path!); + } + + job.Stop(); + + if (delete(job.Path!)) + { + await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); + if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) + { + var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat"); + if (File.Exists(concatFilePath)) + { + _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); + File.Delete(concatFilePath); + } + } + } + + if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) + { + try + { + await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error closing live stream for {Path}", job.Path); + } + } + } + + private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) + { + if (retryCount >= 10) + { + return; + } + + _logger.LogInformation("Deleting partial stream file(s) {Path}", path); + + await Task.Delay(delayMs).ConfigureAwait(false); + + try + { + if (jobType == TranscodingJobType.Progressive) + { + DeleteProgressivePartialStreamFiles(path); + } + else + { + DeleteHlsPartialStreamFiles(path); + } + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); + + await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); + } + } + + private void DeleteProgressivePartialStreamFiles(string outputFilePath) + { + if (File.Exists(outputFilePath)) + { + _fileSystem.DeleteFile(outputFilePath); + } + } + + private void DeleteHlsPartialStreamFiles(string outputFilePath) + { + var directory = Path.GetDirectoryName(outputFilePath) + ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath)); + + var name = Path.GetFileNameWithoutExtension(outputFilePath); + + var filesToDelete = _fileSystem.GetFilePaths(directory) + .Where(f => f.Contains(name, StringComparison.OrdinalIgnoreCase)); + + List? exs = null; + foreach (var file in filesToDelete) + { + try + { + _logger.LogDebug("Deleting HLS file {0}", file); + _fileSystem.DeleteFile(file); + } + catch (IOException ex) + { + (exs ??= new List(4)).Add(ex); + _logger.LogError(ex, "Error deleting HLS file {Path}", file); + } + } + + if (exs is not null) + { + throw new AggregateException("Error deleting HLS files", exs); + } + } + + /// + public void ReportTranscodingProgress( + TranscodingJob job, + StreamState state, + TimeSpan? transcodingPosition, + float? framerate, + double? percentComplete, + long? bytesTranscoded, + int? bitRate) + { + var ticks = transcodingPosition?.Ticks; + + if (job is not null) + { + job.Framerate = framerate; + job.CompletionPercentage = percentComplete; + job.TranscodingPositionTicks = ticks; + job.BytesTranscoded = bytesTranscoded; + job.BitRate = bitRate; + } + + var deviceId = state.Request.DeviceId; + + if (!string.IsNullOrWhiteSpace(deviceId)) + { + var audioCodec = state.ActualOutputAudioCodec; + var videoCodec = state.ActualOutputVideoCodec; + var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; + HardwareEncodingType? hardwareAccelerationType = null; + if (Enum.TryParse(hardwareAccelerationTypeString, out var parsedHardwareAccelerationType)) + { + hardwareAccelerationType = parsedHardwareAccelerationType; + } + + _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo + { + Bitrate = bitRate ?? state.TotalOutputBitrate, + AudioCodec = audioCodec, + VideoCodec = videoCodec, + Container = state.OutputContainer, + Framerate = framerate, + CompletionPercentage = percentComplete, + Width = state.OutputWidth, + Height = state.OutputHeight, + AudioChannels = state.OutputAudioChannels, + IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec), + IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec), + HardwareAccelerationType = hardwareAccelerationType, + TranscodeReasons = state.TranscodeReasons + }); + } + } + + /// + public async Task StartFfMpeg( + StreamState state, + string outputPath, + string commandLineArguments, + Guid userId, + TranscodingJobType transcodingJobType, + CancellationTokenSource cancellationTokenSource, + string? workingDirectory = null) + { + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); + Directory.CreateDirectory(directory); + + await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); + + if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + { + var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) + { + this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); + + throw new ArgumentException("User does not have access to video transcoding."); + } + } + + ArgumentException.ThrowIfNullOrEmpty(_mediaEncoder.EncoderPath); + + // If subtitles get burned in fonts may need to be extracted from the media file + if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + { + var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); + if (state.VideoType != VideoType.Dvd) + { + await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); + } + + if (state.SubtitleStream.IsExternal && Path.GetExtension(state.SubtitleStream.Path.AsSpan()).Equals(".mks", StringComparison.OrdinalIgnoreCase)) + { + string subtitlePath = state.SubtitleStream.Path; + string subtitlePathArgument = string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", subtitlePath.Replace("\"", "\\\"", StringComparison.Ordinal)); + string subtitleId = subtitlePath.GetMD5().ToString("N", CultureInfo.InvariantCulture); + + await _attachmentExtractor.ExtractAllAttachmentsExternal(subtitlePathArgument, subtitleId, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); + } + } + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = false, + + // Must consume both stdout and stderr or deadlocks may occur + // RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + FileName = _mediaEncoder.EncoderPath, + Arguments = commandLineArguments, + WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? string.Empty : workingDirectory, + ErrorDialog = false + }, + EnableRaisingEvents = true + }; + + var transcodingJob = this.OnTranscodeBeginning( + outputPath, + state.Request.PlaySessionId, + state.MediaSource.LiveStreamId, + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), + transcodingJobType, + process, + state.Request.DeviceId, + state, + cancellationTokenSource); + + _logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); + + var logFilePrefix = "FFmpeg.Transcode-"; + if (state.VideoRequest is not null + && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + { + logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec) + ? "FFmpeg.Remux-" + : "FFmpeg.DirectStream-"; + } + + var logFilePath = Path.Combine( + _serverConfigurationManager.ApplicationPaths.LogDirectoryPath, + $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); + + // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. + Stream logStream = new FileStream( + logFilePath, + FileMode.Create, + FileAccess.Write, + FileShare.Read, + IODefaults.FileStreamBufferSize, + FileOptions.Asynchronous); + + var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; + var commandLineLogMessageBytes = Encoding.UTF8.GetBytes( + JsonSerializer.Serialize(state.MediaSource) + + Environment.NewLine + + Environment.NewLine + + commandLineLogMessage + + Environment.NewLine + + Environment.NewLine); + + await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false); + + process.Exited += (_, _) => OnFfMpegProcessExited(process, transcodingJob, state); + + try + { + process.Start(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting FFmpeg"); + this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); + + throw; + } + + _logger.LogDebug("Launched FFmpeg process"); + state.TranscodingJob = transcodingJob; + + // Important - don't await the log task or we won't be able to kill FFmpeg when the user stops playback + _ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError, logStream); + + // Wait for the file to exist before proceeding + var ffmpegTargetFile = state.WaitForPath ?? outputPath; + _logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile); + while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited) + { + await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); + } + + _logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile); + + if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) + { + await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); + + if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited) + { + await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); + } + } + + if (!transcodingJob.HasExited) + { + StartThrottler(state, transcodingJob); + } + else if (transcodingJob.ExitCode != 0) + { + throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "FFmpeg exited with code {0}", transcodingJob.ExitCode)); + } + + _logger.LogDebug("StartFfMpeg() finished successfully"); + + return transcodingJob; + } + + private void StartThrottler(StreamState state, TranscodingJob transcodingJob) + { + if (EnableThrottling(state)) + { + transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, _loggerFactory.CreateLogger(), _serverConfigurationManager, _fileSystem, _mediaEncoder); + transcodingJob.TranscodingThrottler.Start(); + } + } + + private static bool EnableThrottling(StreamState state) + => state.InputProtocol == MediaProtocol.File + && state.RunTimeTicks.HasValue + && state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks + && state.IsInputVideo + && state.VideoType == VideoType.VideoFile; + + private TranscodingJob OnTranscodeBeginning( + string path, + string? playSessionId, + string? liveStreamId, + string transcodingJobId, + TranscodingJobType type, + Process process, + string? deviceId, + StreamState state, + CancellationTokenSource cancellationTokenSource) + { + lock (_activeTranscodingJobs) + { + var job = new TranscodingJob(_loggerFactory.CreateLogger()) + { + Type = type, + Path = path, + Process = process, + ActiveRequestCount = 1, + DeviceId = deviceId, + CancellationTokenSource = cancellationTokenSource, + Id = transcodingJobId, + PlaySessionId = playSessionId, + LiveStreamId = liveStreamId, + MediaSource = state.MediaSource + }; + + _activeTranscodingJobs.Add(job); + + ReportTranscodingProgress(job, state, null, null, null, null, null); + + return job; + } + } + + /// + public void OnTranscodeEndRequest(TranscodingJob job) + { + job.ActiveRequestCount--; + _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount); + if (job.ActiveRequestCount <= 0) + { + PingTimer(job, false); + } + } + + private void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state) + { + lock (_activeTranscodingJobs) + { + var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); + + if (job is not null) + { + _activeTranscodingJobs.Remove(job); + } + } + + lock (_transcodingLocks) + { + _transcodingLocks.Remove(path); + } + + if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) + { + _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); + } + } + + private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state) + { + job.HasExited = true; + job.ExitCode = process.ExitCode; + + ReportTranscodingProgress(job, state, null, null, null, null, null); + + _logger.LogDebug("Disposing stream resources"); + state.Dispose(); + + if (process.ExitCode == 0) + { + _logger.LogInformation("FFmpeg exited with code 0"); + } + else + { + _logger.LogError("FFmpeg exited with code {0}", process.ExitCode); + } + + job.Dispose(); + } + + private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource) + { + if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId)) + { + var liveStreamResponse = await _mediaSourceManager.OpenLiveStream( + new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken }, + cancellationTokenSource.Token) + .ConfigureAwait(false); + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); + + _encodingHelper.AttachMediaSourceInfo(state, encodingOptions, liveStreamResponse.MediaSource, state.RequestedUrl); + + if (state.VideoRequest is not null) + { + _encodingHelper.TryStreamCopy(state); + } + } + + if (state.MediaSource.BufferMs.HasValue) + { + await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false); + } + } + + /// + public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type) + { + lock (_activeTranscodingJobs) + { + var job = _activeTranscodingJobs + .FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); + + if (job is null) + { + return null; + } + + job.ActiveRequestCount++; + if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive) + { + job.StopKillTimer(); + } + + return job; + } + } + + /// + public SemaphoreSlim GetTranscodingLock(string outputPath) + { + lock (_transcodingLocks) + { + if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result)) + { + result = new SemaphoreSlim(1, 1); + _transcodingLocks[outputPath] = result; + } + + return result; + } + } + + private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e) + { + if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) + { + PingTranscodingJob(e.PlaySessionId, e.IsPaused); + } + } + + private void DeleteEncodedMediaCache() + { + var path = _serverConfigurationManager.GetTranscodePath(); + if (!Directory.Exists(path)) + { + return; + } + + foreach (var file in _fileSystem.GetFilePaths(path, true)) + { + _fileSystem.DeleteFile(file); + } + } + + /// + public void Dispose() + { + _sessionManager.PlaybackProgress -= OnPlaybackProgress; + _sessionManager.PlaybackStart -= OnPlaybackProgress; + } +} -- cgit v1.2.3 From c49539cbe0152d23d82d7710ff2bc32e5d3d187b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 21 Dec 2023 12:20:30 -0500 Subject: Move ProgressiveFileStream to Controller --- .../LiveTv/EmbyTV/DirectRecorder.cs | 1 + Jellyfin.Api/Controllers/LiveTvController.cs | 1 + Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 182 --------------------- .../Streaming/ProgressiveFileStream.cs | 182 +++++++++++++++++++++ 4 files changed, 184 insertions(+), 182 deletions(-) delete mode 100644 Jellyfin.Api/Helpers/ProgressiveFileStream.cs create mode 100644 MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index ddf7b882a7..7df66d3585 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index a40f273ae4..5502836239 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -25,6 +25,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs deleted file mode 100644 index 98ea844a96..0000000000 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.IO; - -namespace Jellyfin.Api.Helpers; - -/// -/// A progressive file stream for transferring transcoded files as they are written to. -/// -public class ProgressiveFileStream : Stream -{ - private readonly Stream _stream; - private readonly TranscodingJob? _job; - private readonly ITranscodeManager? _transcodeManager; - private readonly int _timeoutMs; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - /// The path to the transcoded file. - /// The transcoding job information. - /// The transcode manager. - /// The timeout duration in milliseconds. - public ProgressiveFileStream(string filePath, TranscodingJob? job, ITranscodeManager transcodeManager, int timeoutMs = 30000) - { - _job = job; - _transcodeManager = transcodeManager; - _timeoutMs = timeoutMs; - - _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); - } - - /// - /// Initializes a new instance of the class. - /// - /// The stream to progressively copy. - /// The timeout duration in milliseconds. - public ProgressiveFileStream(Stream stream, int timeoutMs = 30000) - { - _job = null; - _transcodeManager = null; - _timeoutMs = timeoutMs; - _stream = stream; - } - - /// - public override bool CanRead => _stream.CanRead; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => false; - - /// - public override long Length => throw new NotSupportedException(); - - /// - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - /// - public override void Flush() - { - // Not supported - } - - /// - public override int Read(byte[] buffer, int offset, int count) - => Read(buffer.AsSpan(offset, count)); - - /// - public override int Read(Span buffer) - { - int totalBytesRead = 0; - var stopwatch = Stopwatch.StartNew(); - - while (true) - { - totalBytesRead += _stream.Read(buffer); - if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds)) - { - break; - } - - Thread.Sleep(50); - } - - UpdateBytesWritten(totalBytesRead); - - return totalBytesRead; - } - - /// - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false); - - /// - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - int totalBytesRead = 0; - var stopwatch = Stopwatch.StartNew(); - - while (true) - { - totalBytesRead += await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds)) - { - break; - } - - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - } - - UpdateBytesWritten(totalBytesRead); - - return totalBytesRead; - } - - /// - public override long Seek(long offset, SeekOrigin origin) - => throw new NotSupportedException(); - - /// - public override void SetLength(long value) - => throw new NotSupportedException(); - - /// - public override void Write(byte[] buffer, int offset, int count) - => throw new NotSupportedException(); - - /// - protected override void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - try - { - if (disposing) - { - _stream.Dispose(); - - if (_job is not null) - { - _transcodeManager?.OnTranscodeEndRequest(_job); - } - } - } - finally - { - _disposed = true; - base.Dispose(disposing); - } - } - - private void UpdateBytesWritten(int totalBytesRead) - { - if (_job is not null) - { - _job.BytesDownloaded += totalBytesRead; - } - } - - private bool StopReading(int bytesRead, long elapsed) - { - // It should stop reading when anything has been successfully read or if the job has exited - // If the job is null, however, it's a live stream and will require user action to close, - // but don't keep it open indefinitely if it isn't reading anything - return bytesRead > 0 || (_job?.HasExited ?? elapsed >= _timeoutMs); - } -} diff --git a/MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs b/MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs new file mode 100644 index 0000000000..f44dc92d71 --- /dev/null +++ b/MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs @@ -0,0 +1,182 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.Controller.Streaming; + +/// +/// A progressive file stream for transferring transcoded files as they are written to. +/// +public class ProgressiveFileStream : Stream +{ + private readonly Stream _stream; + private readonly TranscodingJob? _job; + private readonly ITranscodeManager? _transcodeManager; + private readonly int _timeoutMs; + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The path to the transcoded file. + /// The transcoding job information. + /// The transcode manager. + /// The timeout duration in milliseconds. + public ProgressiveFileStream(string filePath, TranscodingJob? job, ITranscodeManager transcodeManager, int timeoutMs = 30000) + { + _job = job; + _transcodeManager = transcodeManager; + _timeoutMs = timeoutMs; + + _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); + } + + /// + /// Initializes a new instance of the class. + /// + /// The stream to progressively copy. + /// The timeout duration in milliseconds. + public ProgressiveFileStream(Stream stream, int timeoutMs = 30000) + { + _job = null; + _transcodeManager = null; + _timeoutMs = timeoutMs; + _stream = stream; + } + + /// + public override bool CanRead => _stream.CanRead; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => false; + + /// + public override long Length => throw new NotSupportedException(); + + /// + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + /// + public override void Flush() + { + // Not supported + } + + /// + public override int Read(byte[] buffer, int offset, int count) + => Read(buffer.AsSpan(offset, count)); + + /// + public override int Read(Span buffer) + { + int totalBytesRead = 0; + var stopwatch = Stopwatch.StartNew(); + + while (true) + { + totalBytesRead += _stream.Read(buffer); + if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds)) + { + break; + } + + Thread.Sleep(50); + } + + UpdateBytesWritten(totalBytesRead); + + return totalBytesRead; + } + + /// + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false); + + /// + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + int totalBytesRead = 0; + var stopwatch = Stopwatch.StartNew(); + + while (true) + { + totalBytesRead += await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds)) + { + break; + } + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + + UpdateBytesWritten(totalBytesRead); + + return totalBytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + /// + public override void SetLength(long value) + => throw new NotSupportedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + /// + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + try + { + if (disposing) + { + _stream.Dispose(); + + if (_job is not null) + { + _transcodeManager?.OnTranscodeEndRequest(_job); + } + } + } + finally + { + _disposed = true; + base.Dispose(disposing); + } + } + + private void UpdateBytesWritten(int totalBytesRead) + { + if (_job is not null) + { + _job.BytesDownloaded += totalBytesRead; + } + } + + private bool StopReading(int bytesRead, long elapsed) + { + // It should stop reading when anything has been successfully read or if the job has exited + // If the job is null, however, it's a live stream and will require user action to close, + // but don't keep it open indefinitely if it isn't reading anything + return bytesRead > 0 || (_job?.HasExited ?? elapsed >= _timeoutMs); + } +} -- cgit v1.2.3 From aa71129cffd8d1faf8f862449ae98c5c7888ba53 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Fri, 5 Jan 2024 07:31:45 +0800 Subject: Use -noauto{scale,rotate} for disabling auto filters (#10810) `-auto{scale,rotate} 0` has been dropped in upstream FFmpeg. Signed-off-by: nyanmisaka --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6a16d421cb..400e7f40fb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1068,7 +1068,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // hw transpose filters should be added manually. - args.Append(" -autorotate 0"); + args.Append(" -noautorotate"); return args.ToString().Trim(); } @@ -1159,7 +1159,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options)); if (!isSwDecoder && _mediaEncoder.EncoderVersion >= new Version(4, 4)) { - arg.Append(" -autoscale 0"); + arg.Append(" -noautoscale"); } return arg.ToString(); -- cgit v1.2.3 From 55916a09eb4c88b12906e78d56efb7a67d3dad7e Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 6 Jan 2024 21:33:48 +0100 Subject: Remove some unused client capabilities and sync code (#10812) --- Emby.Server.Implementations/Dto/DtoService.cs | 9 --------- Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 1 - Emby.Server.Implementations/Session/SessionManager.cs | 1 - Jellyfin.Api/Controllers/DevicesController.cs | 5 ++--- Jellyfin.Api/Controllers/SessionController.cs | 3 --- Jellyfin.Api/Controllers/UserViewsController.cs | 1 - .../Models/SessionDtos/ClientCapabilitiesDto.cs | 18 ------------------ .../Devices/DeviceManager.cs | 6 +----- MediaBrowser.Controller/Devices/IDeviceManager.cs | 3 +-- MediaBrowser.Controller/Entities/BaseItem.cs | 2 -- MediaBrowser.Model/Dto/BaseItemDto.cs | 5 ----- MediaBrowser.Model/Querying/ItemFields.cs | 7 ------- MediaBrowser.Model/Session/ClientCapabilities.cs | 6 ------ 13 files changed, 4 insertions(+), 63 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 44b97e8b83..d0d5bb81c1 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -418,15 +418,6 @@ namespace Emby.Server.Implementations.Dto { dto.PlayAccess = item.GetPlayAccess(user); } - - if (options.ContainsField(ItemFields.BasicSyncInfo)) - { - var userCanSync = user is not null && user.HasPermission(PermissionKind.EnableContentDownloading); - if (userCanSync && item.SupportsExternalTransfer) - { - dto.SupportsSync = true; - } - } } private static int GetChildCount(Folder folder, User user) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 0544e2a4b7..ca8e204d7c 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1095,7 +1095,6 @@ namespace Emby.Server.Implementations.LiveTv // Load these now which will prefetch metadata var dtoOptions = new DtoOptions(); var fields = dtoOptions.Fields.ToList(); - fields.Remove(ItemFields.BasicSyncInfo); dtoOptions.Fields = fields.ToArray(); progress.Report(100); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e8e63d286d..6f599e4c7c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1670,7 +1670,6 @@ namespace Emby.Server.Implementations.Session var fields = dtoOptions.Fields.ToList(); - fields.Remove(ItemFields.BasicSyncInfo); fields.Remove(ItemFields.CanDelete); fields.Remove(ItemFields.CanDownload); fields.Remove(ItemFields.ChildCount); diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index aa200a7221..6d9ec343e0 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -42,16 +42,15 @@ public class DevicesController : BaseJellyfinApiController /// /// Get Devices. /// - /// Gets or sets a value indicating whether [supports synchronize]. /// Gets or sets the user identifier. /// Devices retrieved. /// An containing the list of devices. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + public async Task>> GetDevices([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false); + return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false); } /// diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index fdebb3d450..083515a949 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -385,7 +385,6 @@ public class SessionController : BaseJellyfinApiController /// A list of playable media types, comma delimited. Audio, Video, Book, Photo. /// A list of supported remote control commands, comma delimited. /// Determines whether media can be played remotely.. - /// Determines whether sync is supported. /// Determines whether the device supports a unique identifier. /// Capabilities posted. /// A . @@ -397,7 +396,6 @@ public class SessionController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, - [FromQuery] bool supportsSync = false, [FromQuery] bool supportsPersistentIdentifier = true) { if (string.IsNullOrWhiteSpace(id)) @@ -410,7 +408,6 @@ public class SessionController : BaseJellyfinApiController PlayableMediaTypes = playableMediaTypes, SupportedCommands = supportedCommands, SupportsMediaControl = supportsMediaControl, - SupportsSync = supportsSync, SupportsPersistentIdentifier = supportsPersistentIdentifier }); return NoContent(); diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index 0ffa3ab1ad..035d044741 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -90,7 +90,6 @@ public class UserViewsController : BaseJellyfinApiController fields.Add(ItemFields.PrimaryImageAspectRatio); fields.Add(ItemFields.DisplayPreferencesId); - fields.Remove(ItemFields.BasicSyncInfo); dtoOptions.Fields = fields.ToArray(); var user = _userManager.GetUserById(userId); diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs index b021771a0f..acd3f29e34 100644 --- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs +++ b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs @@ -30,26 +30,11 @@ public class ClientCapabilitiesDto /// public bool SupportsMediaControl { get; set; } - /// - /// Gets or sets a value indicating whether session supports content uploading. - /// - public bool SupportsContentUploading { get; set; } - - /// - /// Gets or sets the message callback url. - /// - public string? MessageCallbackUrl { get; set; } - /// /// Gets or sets a value indicating whether session supports a persistent identifier. /// public bool SupportsPersistentIdentifier { get; set; } - /// - /// Gets or sets a value indicating whether session supports sync. - /// - public bool SupportsSync { get; set; } - /// /// Gets or sets the device profile. /// @@ -76,10 +61,7 @@ public class ClientCapabilitiesDto PlayableMediaTypes = PlayableMediaTypes, SupportedCommands = SupportedCommands, SupportsMediaControl = SupportsMediaControl, - SupportsContentUploading = SupportsContentUploading, - MessageCallbackUrl = MessageCallbackUrl, SupportsPersistentIdentifier = SupportsPersistentIdentifier, - SupportsSync = SupportsSync, DeviceProfile = DeviceProfile, AppStoreUrl = AppStoreUrl, IconUrl = IconUrl diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index d8eee12467..5e5d52b6b3 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -167,7 +167,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public async Task> GetDevicesForUser(Guid? userId, bool? supportsSync) + public async Task> GetDevicesForUser(Guid? userId) { var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); await using (dbContext.ConfigureAwait(false)) @@ -178,10 +178,6 @@ namespace Jellyfin.Server.Implementations.Devices .ThenBy(d => d.DeviceId) .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o }) .AsAsyncEnumerable(); - if (supportsSync.HasValue) - { - sessions = sessions.Where(i => GetCapabilities(i.Device.DeviceId).SupportsSync == supportsSync.Value); - } if (userId.HasValue) { diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 8362db1a71..eb181dcc4c 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -59,9 +59,8 @@ namespace MediaBrowser.Controller.Devices /// Gets the devices. /// /// The user's id, or null. - /// A value indicating whether the device supports sync, or null. /// IEnumerable<DeviceInfo>. - Task> GetDevicesForUser(Guid? userId, bool? supportsSync); + Task> GetDevicesForUser(Guid? userId); Task DeleteDevice(Device device); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7c04fcbfc8..fdbceac961 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -773,8 +773,6 @@ namespace MediaBrowser.Controller.Entities /// The remote trailers. public IReadOnlyList RemoteTrailers { get; set; } - public virtual bool SupportsExternalTransfer => false; - public virtual double GetDefaultPrimaryImageAspectRatio() { return 0; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index d257eab920..cfff717db2 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -85,11 +85,6 @@ namespace MediaBrowser.Model.Dto public string PreferredMetadataCountryCode { get; set; } - /// - /// Gets or sets a value indicating whether [supports synchronize]. - /// - public bool? SupportsSync { get; set; } - public string Container { get; set; } /// diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 242a1c6e99..49d7c0bcb0 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -175,13 +175,6 @@ namespace MediaBrowser.Model.Querying /// Studios, - BasicSyncInfo, - - /// - /// The synchronize information. - /// - SyncInfo, - /// /// The taglines of the item. /// diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index 7fefce9cd5..597845fc17 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -23,14 +23,8 @@ namespace MediaBrowser.Model.Session public bool SupportsMediaControl { get; set; } - public bool SupportsContentUploading { get; set; } - - public string MessageCallbackUrl { get; set; } - public bool SupportsPersistentIdentifier { get; set; } - public bool SupportsSync { get; set; } - public DeviceProfile DeviceProfile { get; set; } public string AppStoreUrl { get; set; } -- cgit v1.2.3 From 82f93afa22c9695f960903f7b90c7a2c8b23af74 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Jan 2024 15:33:58 -0500 Subject: Fix More Live TV Warnings (#10818) * Fix CA1819 in LiveTvManager * Call ConfigureAwait in ChannelManager --- .../Channels/ChannelManager.cs | 35 +++++++++++++++------- .../LiveTv/LiveTvManager.cs | 4 +-- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 2 +- 3 files changed, 27 insertions(+), 14 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 8279acb058..c505e8fe13 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -812,11 +812,16 @@ namespace Emby.Server.Implementations.Channels { if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - await using FileStream jsonStream = AsyncFile.OpenRead(cachePath); - var cachedResult = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (cachedResult is not null) + var jsonStream = AsyncFile.OpenRead(cachePath); + await using (jsonStream.ConfigureAwait(false)) { - return null; + var cachedResult = await JsonSerializer + .DeserializeAsync(jsonStream, _jsonOptions, cancellationToken) + .ConfigureAwait(false); + if (cachedResult is not null) + { + return null; + } } } } @@ -835,11 +840,16 @@ namespace Emby.Server.Implementations.Channels { if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - await using FileStream jsonStream = AsyncFile.OpenRead(cachePath); - var cachedResult = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (cachedResult is not null) + var jsonStream = AsyncFile.OpenRead(cachePath); + await using (jsonStream.ConfigureAwait(false)) { - return null; + var cachedResult = await JsonSerializer + .DeserializeAsync(jsonStream, _jsonOptions, cancellationToken) + .ConfigureAwait(false); + if (cachedResult is not null) + { + return null; + } } } } @@ -867,7 +877,7 @@ namespace Emby.Server.Implementations.Channels throw new InvalidOperationException("Channel returned a null result from GetChannelItems"); } - await CacheResponse(result, cachePath); + await CacheResponse(result, cachePath).ConfigureAwait(false); return result; } @@ -883,8 +893,11 @@ namespace Emby.Server.Implementations.Channels { Directory.CreateDirectory(Path.GetDirectoryName(path)); - await using FileStream createStream = File.Create(path); - await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false); + var createStream = File.Create(path); + await using (createStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false); + } } catch (Exception ex) { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index ca8e204d7c..426165de61 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -105,9 +105,9 @@ namespace Emby.Server.Implementations.LiveTv /// The services. public IReadOnlyList Services => _services; - public ITunerHost[] TunerHosts => _tunerHosts; + public IReadOnlyList TunerHosts => _tunerHosts; - public IListingsProvider[] ListingProviders => _listingProviders; + public IReadOnlyList ListingProviders => _listingProviders; private LiveTvOptions GetConfiguration() { diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 3b6a16dee3..4206159e7b 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.LiveTv /// The services. IReadOnlyList Services { get; } - IListingsProvider[] ListingProviders { get; } + IReadOnlyList ListingProviders { get; } /// /// Gets the new timer defaults asynchronous. -- cgit v1.2.3 From 051fa04a803068b5fe8c86e1f991e85a9fbc4d04 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 Dec 2023 14:34:44 -0500 Subject: Move GetRecordingStreamMediaSources to IMediaSourceManager --- .../Library/MediaSourceManager.cs | 35 +++++++++++++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 32 -------------------- .../LiveTv/LiveTvMediaSourceProvider.cs | 2 +- .../Library/IMediaSourceManager.cs | 9 ++++++ 4 files changed, 44 insertions(+), 34 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 96fad9bca8..68eccf311d 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -11,14 +11,15 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using EasyCaching.Core.Configurations; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; @@ -37,6 +38,7 @@ namespace Emby.Server.Implementations.Library // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. private const char LiveStreamIdDelimeter = '_'; + private readonly IServerApplicationHost _appHost; private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; @@ -55,6 +57,7 @@ namespace Emby.Server.Implementations.Library private IMediaSourceProvider[] _providers; public MediaSourceManager( + IServerApplicationHost appHost, IItemRepository itemRepo, IApplicationPaths applicationPaths, ILocalizationManager localizationManager, @@ -66,6 +69,7 @@ namespace Emby.Server.Implementations.Library IMediaEncoder mediaEncoder, IDirectoryService directoryService) { + _appHost = appHost; _itemRepo = itemRepo; _userManager = userManager; _libraryManager = libraryManager; @@ -799,6 +803,35 @@ namespace Emby.Server.Implementations.Library return result.Item1; } + public async Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken) + { + var stream = new MediaSourceInfo + { + EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream", + EncoderProtocol = MediaProtocol.Http, + Path = info.Path, + Protocol = MediaProtocol.File, + Id = info.Id, + SupportsDirectPlay = false, + SupportsDirectStream = true, + SupportsTranscoding = true, + IsInfiniteStream = true, + RequiresOpening = false, + RequiresClosing = false, + BufferMs = 0, + IgnoreDts = true, + IgnoreIndex = true + }; + + await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths) + .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false); + + return new List + { + stream + }; + } + public async Task CloseLiveStream(string id) { ArgumentException.ThrowIfNullOrEmpty(id); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index abe3ff349f..e2e0dfb2bb 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -47,7 +47,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private const int TunerDiscoveryDurationMs = 3000; - private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; @@ -76,7 +75,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private bool _disposed; public EmbyTV( - IServerApplicationHost appHost, IStreamHelper streamHelper, IMediaSourceManager mediaSourceManager, ILogger logger, @@ -91,7 +89,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { Current = this; - _appHost = appHost; _logger = logger; _httpClientFactory = httpClientFactory; _config = config; @@ -1021,35 +1018,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new NotImplementedException(); } - public async Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken) - { - var stream = new MediaSourceInfo - { - EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream", - EncoderProtocol = MediaProtocol.Http, - Path = info.Path, - Protocol = MediaProtocol.File, - Id = info.Id, - SupportsDirectPlay = false, - SupportsDirectStream = true, - SupportsTranscoding = true, - IsInfiniteStream = true, - RequiresOpening = false, - RequiresClosing = false, - BufferMs = 0, - IgnoreDts = true, - IgnoreIndex = true - }; - - await new LiveStreamHelper(_mediaEncoder, _logger, _config.CommonApplicationPaths) - .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false); - - return new List - { - stream - }; - } - public Task CloseLiveStream(string id, CancellationToken cancellationToken) { return Task.CompletedTask; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 6a92fc5997..a5a1a4a4c5 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv { if (activeRecordingInfo is not null) { - sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken) + sources = await _mediaSourceManager.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken) .ConfigureAwait(false); } else diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index f1758a9d80..bace703ada 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -116,6 +117,14 @@ namespace MediaBrowser.Controller.Library /// An instance of . public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId); + /// + /// Gets the media sources for an active recording. + /// + /// The . + /// The . + /// A task containing the 's for the recording. + Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken); + /// /// Closes the media source. /// -- cgit v1.2.3 From 9c2c066e6f62fb713d5bad0fcf5a0b3dcf58e6e1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 20:45:05 -0500 Subject: Add ITunerHostManager service --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Jellyfin.Api/Controllers/LiveTvController.cs | 19 +-- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 15 +- .../LiveTv/ITunerHostManager.cs | 47 ++++++ src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 89 +--------- .../LiveTvServiceCollectionExtensions.cs | 6 + src/Jellyfin.LiveTv/LiveTvManager.cs | 76 +-------- src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs | 181 +++++++++++++++++++++ 8 files changed, 258 insertions(+), 177 deletions(-) create mode 100644 MediaBrowser.Controller/LiveTv/ITunerHostManager.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 48d5d8c6a9..5870fed761 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -695,7 +695,7 @@ namespace Emby.Server.Implementations GetExports(), GetExports()); - Resolve().AddParts(GetExports(), GetExports(), GetExports()); + Resolve().AddParts(GetExports(), GetExports()); Resolve().AddParts(GetExports()); } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 5502836239..1a2a3caae8 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -10,7 +10,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; -using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; @@ -43,6 +42,7 @@ namespace Jellyfin.Api.Controllers; public class LiveTvController : BaseJellyfinApiController { private readonly ILiveTvManager _liveTvManager; + private readonly ITunerHostManager _tunerHostManager; private readonly IUserManager _userManager; private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; @@ -55,6 +55,7 @@ public class LiveTvController : BaseJellyfinApiController /// Initializes a new instance of the class. /// /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. @@ -64,6 +65,7 @@ public class LiveTvController : BaseJellyfinApiController /// Instance of the interface. public LiveTvController( ILiveTvManager liveTvManager, + ITunerHostManager tunerHostManager, IUserManager userManager, IHttpClientFactory httpClientFactory, ILibraryManager libraryManager, @@ -73,6 +75,7 @@ public class LiveTvController : BaseJellyfinApiController ITranscodeManager transcodeManager) { _liveTvManager = liveTvManager; + _tunerHostManager = tunerHostManager; _userManager = userManager; _httpClientFactory = httpClientFactory; _libraryManager = libraryManager; @@ -951,9 +954,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) - { - return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false); - } + => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false); /// /// Deletes a tuner host. @@ -1130,10 +1131,8 @@ public class LiveTvController : BaseJellyfinApiController [HttpGet("TunerHosts/Types")] [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetTunerHostTypes() - { - return _liveTvManager.GetTunerHostTypes(); - } + public IEnumerable GetTunerHostTypes() + => _tunerHostManager.GetTunerHostTypes(); /// /// Discover tuners. @@ -1146,9 +1145,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) - { - return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false); - } + => await _tunerHostManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false); /// /// Gets a live tv recording stream. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 4206159e7b..26f9fe42d3 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv /// Adds the parts. /// /// The services. - /// The tuner hosts. /// The listing providers. - void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders); + void AddParts(IEnumerable services, IEnumerable listingProviders); /// /// Gets the timer. @@ -253,14 +252,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList fields, User user = null); - /// - /// Saves the tuner host. - /// - /// Turner host to save. - /// Option to specify that data source has changed. - /// Tuner host information wrapped in a task. - Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true); - /// /// Saves the listing provider. /// @@ -298,10 +289,6 @@ namespace MediaBrowser.Controller.LiveTv Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); - List GetTunerHostTypes(); - - Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); - string GetEmbyTvActiveRecordingPath(string id); ActiveRecordingInfo GetActiveRecordingInfo(string path); diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs new file mode 100644 index 0000000000..7e4caaf136 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv; + +/// +/// Service responsible for managing the s. +/// +public interface ITunerHostManager +{ + /// + /// Gets the available s. + /// + IReadOnlyList TunerHosts { get; } + + /// + /// Gets the s for the available s. + /// + /// The s. + IEnumerable GetTunerHostTypes(); + + /// + /// Saves the tuner host. + /// + /// Turner host to save. + /// Option to specify that data source has changed. + /// Tuner host information wrapped in a task. + Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true); + + /// + /// Discovers the available tuners. + /// + /// A value indicating whether to only return new devices. + /// The to use. + /// The s. + Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); + + /// + /// Scans for tuner devices that have changed URLs. + /// + /// The to use. + /// A task that represents the scanning operation. + Task ScanForTunerDeviceChanges(CancellationToken cancellationToken); +} diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 532e1c897d..625451fa37 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -44,8 +44,6 @@ namespace Jellyfin.LiveTv.EmbyTV { public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private const int TunerDiscoveryDurationMs = 3000; - private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; @@ -54,6 +52,7 @@ namespace Jellyfin.LiveTv.EmbyTV private readonly TimerManager _timerProvider; private readonly LiveTvManager _liveTvManager; + private readonly ITunerHostManager _tunerHostManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _libraryMonitor; @@ -80,6 +79,7 @@ namespace Jellyfin.LiveTv.EmbyTV IHttpClientFactory httpClientFactory, IServerConfigurationManager config, ILiveTvManager liveTvManager, + ITunerHostManager tunerHostManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, @@ -97,6 +97,7 @@ namespace Jellyfin.LiveTv.EmbyTV _providerManager = providerManager; _mediaEncoder = mediaEncoder; _liveTvManager = (LiveTvManager)liveTvManager; + _tunerHostManager = tunerHostManager; _mediaSourceManager = mediaSourceManager; _streamHelper = streamHelper; @@ -310,7 +311,7 @@ namespace Jellyfin.LiveTv.EmbyTV { var list = new List(); - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -510,7 +511,7 @@ namespace Jellyfin.LiveTv.EmbyTV { var list = new List(); - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -966,7 +967,7 @@ namespace Jellyfin.LiveTv.EmbyTV return result; } - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -998,7 +999,7 @@ namespace Jellyfin.LiveTv.EmbyTV throw new ArgumentNullException(nameof(channelId)); } - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -2537,81 +2538,5 @@ namespace Jellyfin.LiveTv.EmbyTV }; } } - - public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) - { - var list = new List(); - - var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts - .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) - .Select(i => i.DeviceId) - .ToList(); - - foreach (var host in _liveTvManager.TunerHosts) - { - var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - - if (newDevicesOnly) - { - discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - - list.AddRange(discoveredDevices); - } - - return list; - } - - public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken) - { - foreach (var host in _liveTvManager.TunerHosts) - { - await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false); - } - } - - private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) - { - var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - - var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts - .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - foreach (var device in discoveredDevices) - { - var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)); - - if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase)) - { - _logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url); - - configuredDevice.Url = device.Url; - await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false); - } - } - } - - private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) - { - try - { - var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false); - - foreach (var device in discoveredDevices) - { - _logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url); - } - - return discoveredDevices; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error discovering tuner devices"); - - return new List(); - } - } } } diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs index 5865e88af0..5490547ec3 100644 --- a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs +++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using Jellyfin.LiveTv.Channels; +using Jellyfin.LiveTv.TunerHosts; +using Jellyfin.LiveTv.TunerHosts.HdHomerun; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.IO; @@ -21,5 +23,9 @@ public static class LiveTvServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); } } diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 0b3d35731a..71822f3762 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -57,9 +57,9 @@ namespace Jellyfin.LiveTv private readonly IFileSystem _fileSystem; private readonly IChannelManager _channelManager; private readonly LiveTvDtoService _tvDtoService; + private readonly ITunerHostManager _tunerHostManager; private ILiveTvService[] _services = Array.Empty(); - private ITunerHost[] _tunerHosts = Array.Empty(); private IListingsProvider[] _listingProviders = Array.Empty(); public LiveTvManager( @@ -74,7 +74,8 @@ namespace Jellyfin.LiveTv ILocalizationManager localization, IFileSystem fileSystem, IChannelManager channelManager, - LiveTvDtoService liveTvDtoService) + LiveTvDtoService liveTvDtoService, + ITunerHostManager tunerHostManager) { _config = config; _logger = logger; @@ -88,6 +89,7 @@ namespace Jellyfin.LiveTv _userDataManager = userDataManager; _channelManager = channelManager; _tvDtoService = liveTvDtoService; + _tunerHostManager = tunerHostManager; } public event EventHandler> SeriesTimerCancelled; @@ -104,8 +106,6 @@ namespace Jellyfin.LiveTv /// The services. public IReadOnlyList Services => _services; - public IReadOnlyList TunerHosts => _tunerHosts; - public IReadOnlyList ListingProviders => _listingProviders; public string GetEmbyTvActiveRecordingPath(string id) @@ -113,16 +113,10 @@ namespace Jellyfin.LiveTv return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id); } - /// - /// Adds the parts. - /// - /// The services. - /// The tuner hosts. - /// The listing providers. - public void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders) + /// + public void AddParts(IEnumerable services, IEnumerable listingProviders) { _services = services.ToArray(); - _tunerHosts = tunerHosts.Where(i => i.IsSupported).ToArray(); _listingProviders = listingProviders.ToArray(); @@ -154,20 +148,6 @@ namespace Jellyfin.LiveTv })); } - public List GetTunerHostTypes() - { - return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair - { - Name = i.Name, - Id = i.Type - }).ToList(); - } - - public Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) - { - return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken); - } - public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { var user = query.UserId.Equals(default) @@ -1029,7 +1009,7 @@ namespace Jellyfin.LiveTv { await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false); - await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); + await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); var numComplete = 0; double progressPerService = _services.Length == 0 @@ -2166,48 +2146,6 @@ namespace Jellyfin.LiveTv return _libraryManager.GetNamedView(name, CollectionType.livetv, name); } - public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) - { - info = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(info)); - - var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); - - if (provider is null) - { - throw new ResourceNotFoundException(); - } - - if (provider is IConfigurableTunerHost configurable) - { - await configurable.Validate(info).ConfigureAwait(false); - } - - var config = _config.GetLiveTvConfiguration(); - - var list = config.TunerHosts.ToList(); - var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); - - if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) - { - info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - list.Add(info); - config.TunerHosts = list.ToArray(); - } - else - { - config.TunerHosts[index] = info; - } - - _config.SaveConfiguration("livetv", config); - - if (dataSourceChanged) - { - _taskManager.CancelIfRunningAndQueue(); - } - - return info; - } - public async Task SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings) { // Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs new file mode 100644 index 0000000000..04eb8293a1 --- /dev/null +++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using Jellyfin.LiveTv.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.LiveTv.TunerHosts; + +/// +public class TunerHostManager : ITunerHostManager +{ + private const int TunerDiscoveryDurationMs = 3000; + + private readonly ILogger _logger; + private readonly IConfigurationManager _config; + private readonly ITaskManager _taskManager; + private readonly ITunerHost[] _tunerHosts; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + public TunerHostManager( + ILogger logger, + IConfigurationManager config, + ITaskManager taskManager, + IEnumerable tunerHosts) + { + _logger = logger; + _config = config; + _taskManager = taskManager; + _tunerHosts = tunerHosts.Where(t => t.IsSupported).ToArray(); + } + + /// + public IReadOnlyList TunerHosts => _tunerHosts; + + /// + public IEnumerable GetTunerHostTypes() + => _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair + { + Name = i.Name, + Id = i.Type + }); + + /// + public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) + { + info = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(info))!; + + var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); + + if (provider is null) + { + throw new ResourceNotFoundException(); + } + + if (provider is IConfigurableTunerHost configurable) + { + await configurable.Validate(info).ConfigureAwait(false); + } + + var config = _config.GetLiveTvConfiguration(); + + var list = config.TunerHosts.ToList(); + var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); + + if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) + { + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + list.Add(info); + config.TunerHosts = list.ToArray(); + } + else + { + config.TunerHosts[index] = info; + } + + _config.SaveConfiguration("livetv", config); + + if (dataSourceChanged) + { + _taskManager.CancelIfRunningAndQueue(); + } + + return info; + } + + /// + public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) + { + var list = new List(); + + var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts + .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) + .Select(i => i.DeviceId) + .ToList(); + + foreach (var host in _tunerHosts) + { + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); + + if (newDevicesOnly) + { + discoveredDevices = discoveredDevices + .Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + list.AddRange(discoveredDevices); + } + + return list; + } + + /// + public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken) + { + foreach (var host in _tunerHosts) + { + await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false); + } + } + + private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) + { + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); + + var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts + .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var device in discoveredDevices) + { + var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)); + + if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url); + + configuredDevice.Url = device.Url; + await SaveTunerHost(configuredDevice).ConfigureAwait(false); + } + } + } + + private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) + { + try + { + var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false); + + foreach (var device in discoveredDevices) + { + _logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url); + } + + return discoveredDevices; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error discovering tuner devices"); + + return new List(); + } + } +} -- cgit v1.2.3 From 3ce16713dd013b5aabdedebafd025f2224d2475f Mon Sep 17 00:00:00 2001 From: JPVenson Date: Sun, 14 Jan 2024 16:50:09 +0100 Subject: Fixed disposable not being called (#10613) * Fixed disposable not being called * PulledUp usage of IAsyncDisposable for sessioninfo Co-authored-by: Patrick Barron --- .../Session/SessionManager.cs | 12 +++++----- MediaBrowser.Controller/Session/ISessionManager.cs | 3 ++- MediaBrowser.Controller/Session/SessionInfo.cs | 28 ++++++---------------- 3 files changed, 15 insertions(+), 28 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6f599e4c7c..f457a78b3b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -189,7 +189,7 @@ namespace Emby.Server.Implementations.Session _logger); } - private void OnSessionEnded(SessionInfo info) + private async ValueTask OnSessionEnded(SessionInfo info) { EventHelper.QueueEventIfNotNull( SessionEnded, @@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.Session _eventManager.Publish(new SessionEndedEventArgs(info)); - info.Dispose(); + await info.DisposeAsync().ConfigureAwait(false); } /// @@ -301,12 +301,12 @@ namespace Emby.Server.Implementations.Session await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false); } - OnSessionEnded(session); + await OnSessionEnded(session).ConfigureAwait(false); } } /// - public void ReportSessionEnded(string sessionId) + public async ValueTask ReportSessionEnded(string sessionId) { CheckDisposed(); var session = GetSession(sessionId, false); @@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Session _activeConnections.TryRemove(key, out _); - OnSessionEnded(session); + await OnSessionEnded(session).ConfigureAwait(false); } } @@ -1590,7 +1590,7 @@ namespace Emby.Server.Implementations.Session { try { - ReportSessionEnded(session.Id); + await ReportSessionEnded(session.Id).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 53df7133b5..5a47236f92 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. /// /// The session identifier. - void ReportSessionEnded(string sessionId); + /// Task. + ValueTask ReportSessionEnded(string sessionId); /// /// Sends the general command. diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 3e30c8dc45..3a12a56f1e 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Session /// /// Class SessionInfo. /// - 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 } } - /// - public void Dispose() - { - _disposed = true; - - StopAutomaticProgress(); - - var controllers = SessionControllers.ToList(); - SessionControllers = Array.Empty(); - - 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(); 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(); + } } } } -- cgit v1.2.3 From c23a038ba8f275e061c148ea27e458174a9a7cbe Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 20:56:58 -0500 Subject: Remove unnecessary allocations in TunerHostManager --- Jellyfin.Api/Controllers/LiveTvController.cs | 4 ++-- .../LiveTv/ITunerHostManager.cs | 3 +-- src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs | 25 ++++++++-------------- 3 files changed, 12 insertions(+), 20 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 1a2a3caae8..27eb88b60f 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1144,8 +1144,8 @@ public class LiveTvController : BaseJellyfinApiController [HttpGet("Tuners/Discover")] [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) - => await _tunerHostManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false); + public IAsyncEnumerable DiscoverTuners([FromQuery] bool newDevicesOnly = false) + => _tunerHostManager.DiscoverTuners(newDevicesOnly); /// /// Gets a live tv recording stream. diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs index 7e4caaf136..3df6066f66 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs @@ -34,9 +34,8 @@ public interface ITunerHostManager /// Discovers the available tuners. /// /// A value indicating whether to only return new devices. - /// The to use. /// The s. - Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); + IAsyncEnumerable DiscoverTuners(bool newDevicesOnly); /// /// Scans for tuner devices that have changed URLs. diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs index 04eb8293a1..3e4b0e13fc 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Extensions; using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -101,10 +100,8 @@ public class TunerHostManager : ITunerHostManager } /// - public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) + public async IAsyncEnumerable DiscoverTuners(bool newDevicesOnly) { - var list = new List(); - var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) .Select(i => i.DeviceId) @@ -112,19 +109,15 @@ public class TunerHostManager : ITunerHostManager foreach (var host in _tunerHosts) { - var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - - if (newDevicesOnly) + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, CancellationToken.None).ConfigureAwait(false); + foreach (var tuner in discoveredDevices) { - discoveredDevices = discoveredDevices - .Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); + if (!newDevicesOnly || !configuredDeviceIds.Contains(tuner.DeviceId, StringComparer.OrdinalIgnoreCase)) + { + yield return tuner; + } } - - list.AddRange(discoveredDevices); } - - return list; } /// @@ -158,7 +151,7 @@ public class TunerHostManager : ITunerHostManager } } - private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) + private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) { try { @@ -175,7 +168,7 @@ public class TunerHostManager : ITunerHostManager { _logger.LogError(ex, "Error discovering tuner devices"); - return new List(); + return Array.Empty(); } } } -- cgit v1.2.3 From c101d287f24cf53bce0674bf70d88ae61da67ed9 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 21:16:08 -0500 Subject: Remove unused Live TV code --- .../Channels/IChannelManager.cs | 7 - MediaBrowser.Controller/LiveTv/ILiveTvService.cs | 13 -- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 7 - .../LiveTv/LiveTvServiceStatusInfo.cs | 54 ------ MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs | 77 -------- MediaBrowser.Controller/LiveTv/RecordingInfo.cs | 210 --------------------- .../LiveTv/RecordingStatusChangedEventArgs.cs | 16 -- MediaBrowser.Model/IO/IStreamHelper.cs | 2 - MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs | 12 -- src/Jellyfin.LiveTv/Channels/ChannelManager.cs | 34 ---- src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 5 - src/Jellyfin.LiveTv/StreamHelper.cs | 30 --- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 184 +----------------- src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs | 16 -- 14 files changed, 10 insertions(+), 657 deletions(-) delete mode 100644 MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs delete mode 100644 MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs delete mode 100644 MediaBrowser.Controller/LiveTv/RecordingInfo.cs delete mode 100644 MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs delete mode 100644 MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 8eb27888ab..c8b432ecb2 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -95,12 +95,5 @@ namespace MediaBrowser.Controller.Channels /// The cancellation token. /// The item media sources. IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); - - /// - /// Whether the item supports media probe. - /// - /// The item. - /// Whether media probe should be enabled. - bool EnableMediaProbe(BaseItem item); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index ce34954e3f..52fb156481 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -140,14 +140,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task CloseLiveStream(string id, CancellationToken cancellationToken); - /// - /// Records the live stream. - /// - /// The identifier. - /// The cancellation token. - /// Task. - Task RecordLiveStream(string id, CancellationToken cancellationToken); - /// /// Resets the tuner. /// @@ -180,9 +172,4 @@ namespace MediaBrowser.Controller.LiveTv { Task GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List 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 b983091588..3689a2adf6 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -35,13 +35,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task<IEnumerable<ChannelInfo>>. Task> GetChannels(bool enableCache, CancellationToken cancellationToken); - /// - /// Gets the tuner infos. - /// - /// The cancellation token. - /// Task<List<LiveTvTunerInfo>>. - Task> GetTunerInfos(CancellationToken cancellationToken); - /// /// Gets the channel stream. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs deleted file mode 100644 index eb3babc180..0000000000 --- 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(); - IsVisible = true; - } - - /// - /// Gets or sets the status. - /// - /// The status. - public LiveTvServiceStatus Status { get; set; } - - /// - /// Gets or sets the status message. - /// - /// The status message. - public string StatusMessage { get; set; } - - /// - /// Gets or sets the version. - /// - /// The version. - public string Version { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has update available. - /// - /// true if this instance has update available; otherwise, false. - public bool HasUpdateAvailable { get; set; } - - /// - /// Gets or sets the tuners. - /// - /// The tuners. - public List Tuners { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is visible. - /// - /// true if this instance is visible; otherwise, false. - public bool IsVisible { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs deleted file mode 100644 index aa5eb59d16..0000000000 --- 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(); - } - - /// - /// Gets or sets the type of the source. - /// - /// The type of the source. - public string SourceType { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public LiveTvTunerStatus Status { get; set; } - - /// - /// Gets or sets the channel identifier. - /// - /// The channel identifier. - public string ChannelId { get; set; } - - /// - /// Gets or sets the recording identifier. - /// - /// The recording identifier. - public string RecordingId { get; set; } - - /// - /// Gets or sets the name of the program. - /// - /// The name of the program. - public string ProgramName { get; set; } - - /// - /// Gets or sets the clients. - /// - /// The clients. - public List Clients { get; set; } - - /// - /// Gets or sets a value indicating whether this instance can reset. - /// - /// true if this instance can reset; otherwise, false. - public bool CanReset { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs deleted file mode 100644 index 1dcf7a58fe..0000000000 --- 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(); - } - - /// - /// Gets or sets the id of the recording. - /// - public string Id { get; set; } - - /// - /// Gets or sets the series timer identifier. - /// - /// The series timer identifier. - public string SeriesTimerId { get; set; } - - /// - /// Gets or sets the timer identifier. - /// - /// The timer identifier. - public string TimerId { get; set; } - - /// - /// Gets or sets the channelId of the recording. - /// - public string ChannelId { get; set; } - - /// - /// Gets or sets the type of the channel. - /// - /// The type of the channel. - public ChannelType ChannelType { get; set; } - - /// - /// Gets or sets the name of the recording. - /// - public string Name { get; set; } - - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - - /// - /// Gets or sets the overview. - /// - /// The overview. - public string Overview { get; set; } - - /// - /// Gets or sets the start date of the recording, in UTC. - /// - public DateTime StartDate { get; set; } - - /// - /// Gets or sets the end date of the recording, in UTC. - /// - public DateTime EndDate { get; set; } - - /// - /// Gets or sets the program identifier. - /// - /// The program identifier. - public string ProgramId { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public RecordingStatus Status { get; set; } - - /// - /// Gets or sets the genre of the program. - /// - public List Genres { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is repeat. - /// - /// true if this instance is repeat; otherwise, false. - public bool IsRepeat { get; set; } - - /// - /// Gets or sets the episode title. - /// - /// The episode title. - public string EpisodeTitle { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is hd. - /// - /// true if this instance is hd; otherwise, false. - public bool? IsHD { get; set; } - - /// - /// Gets or sets the audio. - /// - /// The audio. - public ProgramAudio? Audio { get; set; } - - /// - /// Gets or sets the original air date. - /// - /// The original air date. - public DateTime? OriginalAirDate { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is movie. - /// - /// true if this instance is movie; otherwise, false. - public bool IsMovie { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is sports. - /// - /// true if this instance is sports; otherwise, false. - public bool IsSports { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is series. - /// - /// true if this instance is series; otherwise, false. - public bool IsSeries { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is live. - /// - /// true if this instance is live; otherwise, false. - public bool IsLive { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is news. - /// - /// true if this instance is news; otherwise, false. - public bool IsNews { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is kids. - /// - /// true if this instance is kids; otherwise, false. - public bool IsKids { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is premiere. - /// - /// true if this instance is premiere; otherwise, false. - public bool IsPremiere { get; set; } - - /// - /// Gets or sets the official rating. - /// - /// The official rating. - public string OfficialRating { get; set; } - - /// - /// Gets or sets the community rating. - /// - /// The community rating. - public float? CommunityRating { get; set; } - - /// - /// Gets or sets the image path if it can be accessed directly from the file system. - /// - /// The image path. - public string ImagePath { get; set; } - - /// - /// Gets or sets the image url if it can be downloaded. - /// - /// The image URL. - public string ImageUrl { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has image. - /// - /// null if [has image] contains no value, true if [has image]; otherwise, false. - public bool? HasImage { get; set; } - - /// - /// Gets or sets the show identifier. - /// - /// The show identifier. - public string ShowId { get; set; } - - /// - /// Gets or sets the date last updated. - /// - /// The date last updated. - public DateTime DateLastUpdated { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs deleted file mode 100644 index 0b943c9396..0000000000 --- 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.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index f900da5567..034a6bf8bf 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); - Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken); - Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs b/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs deleted file mode 100644 index 80a6461957..0000000000 --- a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.LiveTv -{ - public enum LiveTvTunerStatus - { - Available = 0, - Disabled = 1, - RecordingTv = 2, - LiveTv = 3 - } -} diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs index f5ce75ff4d..51abb503eb 100644 --- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs +++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs @@ -113,15 +113,6 @@ namespace Jellyfin.LiveTv.Channels return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item); } - /// - public bool EnableMediaProbe(BaseItem item) - { - var internalChannel = _libraryManager.GetItemById(item.ChannelId); - var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); - - return channel is ISupportsMediaProbe; - } - /// public Task DeleteItem(BaseItem item) { @@ -562,18 +553,6 @@ namespace Jellyfin.LiveTv.Channels return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures()); } - /// - /// Checks whether the provided Guid supports external transfer. - /// - /// The Guid. - /// Whether or not the provided Guid supports external transfer. - public bool SupportsExternalTransfer(Guid channelId) - { - var channelProvider = GetChannelProvider(channelId); - - return channelProvider.GetChannelFeatures().SupportsContentDownloading; - } - /// /// Gets the provided channel's supported features. /// @@ -1215,19 +1194,6 @@ namespace Jellyfin.LiveTv.Channels return result; } - internal IChannel GetChannelProvider(Guid internalChannelId) - { - var result = GetAllChannels() - .FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name))); - - if (result is null) - { - throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId); - } - - return result; - } - /// public void Dispose() { diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 625451fa37..9eb3aa2fd6 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -1023,11 +1023,6 @@ namespace Jellyfin.LiveTv.EmbyTV return Task.CompletedTask; } - public Task RecordLiveStream(string id, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - public Task ResetTuner(string id, CancellationToken cancellationToken) { return Task.CompletedTask; diff --git a/src/Jellyfin.LiveTv/StreamHelper.cs b/src/Jellyfin.LiveTv/StreamHelper.cs index ab4b6e9b15..e9644e95e7 100644 --- a/src/Jellyfin.LiveTv/StreamHelper.cs +++ b/src/Jellyfin.LiveTv/StreamHelper.cs @@ -81,36 +81,6 @@ namespace Jellyfin.LiveTv } } - public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) - { - byte[] buffer = ArrayPool.Shared.Rent(IODefaults.CopyToBufferSize); - try - { - int bytesRead; - - while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) - { - var bytesToWrite = Math.Min(bytesRead, copyLength); - - if (bytesToWrite > 0) - { - await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false); - } - - copyLength -= bytesToWrite; - - if (copyLength <= 0) - { - break; - } - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(bufferSize); diff --git a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index a56af65a6f..fef84dd000 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -16,7 +15,6 @@ using System.Threading.Tasks; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; -using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -164,152 +162,6 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun } } - private async Task> GetTunerInfosHttp(TunerHostInfo info, CancellationToken cancellationToken) - { - var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken) - .ConfigureAwait(false); - var tuners = new List(); - var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using (stream.ConfigureAwait(false)) - { - using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); - await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false)) - { - string stripedLine = StripXML(line); - if (stripedLine.Contains("Channel", StringComparison.Ordinal)) - { - LiveTvTunerStatus status; - var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = stripedLine.Substring(0, index - 1); - var currentChannel = stripedLine.Substring(index + 7); - if (string.Equals(currentChannel, "none", StringComparison.Ordinal)) - { - status = LiveTvTunerStatus.LiveTv; - } - else - { - status = LiveTvTunerStatus.Available; - } - - tuners.Add(new LiveTvTunerInfo - { - Name = name, - SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber, - ProgramName = currentChannel, - Status = status - }); - } - } - } - - return tuners; - } - - private static string StripXML(string source) - { - if (string.IsNullOrEmpty(source)) - { - return string.Empty; - } - - char[] buffer = new char[source.Length]; - int bufferIndex = 0; - bool inside = false; - - for (int i = 0; i < source.Length; i++) - { - char let = source[i]; - if (let == '<') - { - inside = true; - continue; - } - - if (let == '>') - { - inside = false; - continue; - } - - if (!inside) - { - buffer[bufferIndex++] = let; - } - } - - return new string(buffer, 0, bufferIndex); - } - - private async Task> GetTunerInfosUdp(TunerHostInfo info, CancellationToken cancellationToken) - { - var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - - var tuners = new List(model.TunerCount); - - var uri = new Uri(GetApiUrl(info)); - - using (var manager = new HdHomerunManager()) - { - // Legacy HdHomeruns are IPv4 only - var ipInfo = IPAddress.Parse(uri.Host); - - for (int i = 0; i < model.TunerCount; i++) - { - var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1); - var currentChannel = "none"; // TODO: Get current channel and map back to Station Id - var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); - var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; - tuners.Add(new LiveTvTunerInfo - { - Name = name, - SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber, - ProgramName = currentChannel, - Status = status - }); - } - } - - return tuners; - } - - public async Task> GetTunerInfos(CancellationToken cancellationToken) - { - var list = new List(); - - foreach (var host in Config.GetLiveTvConfiguration().TunerHosts - .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))) - { - try - { - list.AddRange(await GetTunerInfos(host, cancellationToken).ConfigureAwait(false)); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting tuner info"); - } - } - - return list; - } - - public async Task> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken) - { - // TODO Need faster way to determine UDP vs HTTP - var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false); - - var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo; - - if (hdHomerunChannelInfo is null || hdHomerunChannelInfo.IsLegacyTuner) - { - return await GetTunerInfosUdp(info, cancellationToken).ConfigureAwait(false); - } - - return await GetTunerInfosHttp(info, cancellationToken).ConfigureAwait(false); - } - private static string GetApiUrl(TunerHostInfo info) { var url = info.Url; @@ -575,40 +427,24 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun _streamHelper); } - var enableHttpStream = true; - if (enableHttpStream) - { - mediaSource.Protocol = MediaProtocol.Http; - - var httpUrl = channel.Path; - - // If raw was used, the tuner doesn't support params - if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) - { - httpUrl += "?transcode=" + profile; - } + mediaSource.Protocol = MediaProtocol.Http; - mediaSource.Path = httpUrl; + var httpUrl = channel.Path; - return new SharedHttpStream( - mediaSource, - tunerHost, - streamId, - FileSystem, - _httpClientFactory, - Logger, - Config, - _appHost, - _streamHelper); + // If raw was used, the tuner doesn't support params + if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) + { + httpUrl += "?transcode=" + profile; } - return new HdHomerunUdpStream( + mediaSource.Path = httpUrl; + + return new SharedHttpStream( mediaSource, tunerHost, streamId, - new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), - modelInfo.TunerCount, FileSystem, + _httpClientFactory, Logger, Config, _appHost, diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs index 7235e65b64..3666d342ed 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs @@ -80,22 +80,6 @@ namespace Jellyfin.LiveTv.TunerHosts .ConfigureAwait(false); } - public Task> GetTunerInfos(CancellationToken cancellationToken) - { - var list = GetTunerHosts() - .Select(i => new LiveTvTunerInfo() - { - Name = Name, - SourceType = Type, - Status = LiveTvTunerStatus.Available, - Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture), - Url = i.Url - }) - .ToList(); - - return Task.FromResult(list); - } - protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = tunerHost.TunerCount; -- cgit v1.2.3 From e7b8d45bbb0f2b832245dae7ac0d401c56cb10a4 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Wed, 17 Jan 2024 08:51:39 -0700 Subject: Use helper function to compare guid (#10825) --- .../Data/SqliteItemRepository.cs | 32 +++++++++++----------- .../EntryPoints/LibraryChangedNotifier.cs | 3 +- .../Library/LibraryManager.cs | 30 ++++++++++---------- .../Library/MediaSourceManager.cs | 5 ++-- .../Library/MusicManager.cs | 3 +- .../Library/SearchEngine.cs | 4 +-- .../Library/UserViewManager.cs | 5 ++-- .../Playlists/PlaylistManager.cs | 3 +- .../Session/SessionManager.cs | 22 +++++++-------- Emby.Server.Implementations/SyncPlay/Group.cs | 3 +- Emby.Server.Implementations/TV/TVSeriesManager.cs | 7 +++-- .../Updates/InstallationManager.cs | 3 +- .../DefaultAuthorizationHandler.cs | 3 +- .../FirstTimeSetupPolicy/FirstTimeSetupHandler.cs | 3 +- Jellyfin.Api/Controllers/ArtistsController.cs | 7 +++-- Jellyfin.Api/Controllers/ChannelsController.cs | 5 ++-- Jellyfin.Api/Controllers/FilterController.cs | 5 ++-- Jellyfin.Api/Controllers/GenresController.cs | 5 ++-- Jellyfin.Api/Controllers/InstantMixController.cs | 15 +++++----- Jellyfin.Api/Controllers/ItemsController.cs | 5 ++-- Jellyfin.Api/Controllers/LibraryController.cs | 26 +++++++++--------- Jellyfin.Api/Controllers/LiveTvController.cs | 25 +++++++++-------- Jellyfin.Api/Controllers/MoviesController.cs | 3 +- Jellyfin.Api/Controllers/MusicGenresController.cs | 5 ++-- Jellyfin.Api/Controllers/PersonsController.cs | 5 ++-- Jellyfin.Api/Controllers/PlaylistsController.cs | 3 +- Jellyfin.Api/Controllers/SearchController.cs | 2 +- Jellyfin.Api/Controllers/SessionController.cs | 7 +++-- Jellyfin.Api/Controllers/StudiosController.cs | 5 ++-- Jellyfin.Api/Controllers/SuggestionsController.cs | 3 +- Jellyfin.Api/Controllers/TvShowsController.cs | 10 +++---- Jellyfin.Api/Controllers/UserController.cs | 3 +- Jellyfin.Api/Controllers/UserLibraryController.cs | 19 +++++++------ Jellyfin.Api/Controllers/VideosController.cs | 7 +++-- Jellyfin.Api/Controllers/YearsController.cs | 6 ++-- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 3 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 3 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- .../Users/UserManager.cs | 3 +- .../Entities/AggregateFolder.cs | 3 +- .../Entities/Audio/MusicArtist.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 16 +++++------ MediaBrowser.Controller/Entities/Folder.cs | 9 +++--- MediaBrowser.Controller/Entities/TV/Episode.cs | 11 ++++---- MediaBrowser.Controller/Entities/TV/Season.cs | 5 ++-- MediaBrowser.Controller/Entities/UserView.cs | 8 +++--- .../Entities/UserViewBuilder.cs | 2 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- .../Transcoding/TranscodeManager.cs | 3 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 3 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 4 +-- src/Jellyfin.Extensions/GuidExtensions.cs | 26 ++++++++++++++++++ .../Json/Converters/JsonNullableGuidConverter.cs | 2 +- src/Jellyfin.LiveTv/Channels/ChannelManager.cs | 8 +++--- src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 6 ++-- src/Jellyfin.LiveTv/LiveTvDtoService.cs | 5 ++-- src/Jellyfin.LiveTv/LiveTvManager.cs | 7 +++-- .../AuthHelper.cs | 3 +- 58 files changed, 249 insertions(+), 184 deletions(-) create mode 100644 src/Jellyfin.Extensions/GuidExtensions.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d0772654ce..a6336f1451 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -699,7 +699,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.IsEmpty() ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); if (item is IHasProgramAttributes hasProgramAttributes) { @@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); var parentId = item.ParentId; - if (parentId.Equals(default)) + if (parentId.IsEmpty()) { saveItemStatement.TryBindNull("@ParentId"); } @@ -925,7 +925,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBind("@SeasonName", episode.SeasonName); - var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId; + var nullableSeasonId = episode.SeasonId.IsEmpty() ? (Guid?)null : episode.SeasonId; saveItemStatement.TryBind("@SeasonId", nullableSeasonId); } @@ -937,7 +937,7 @@ namespace Emby.Server.Implementations.Data if (item is IHasSeries hasSeries) { - var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId; + var nullableSeriesId = hasSeries.SeriesId.IsEmpty() ? (Guid?)null : hasSeries.SeriesId; saveItemStatement.TryBind("@SeriesId", nullableSeriesId); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); @@ -1010,7 +1010,7 @@ namespace Emby.Server.Implementations.Data } Guid ownerId = item.OwnerId; - if (ownerId.Equals(default)) + if (ownerId.IsEmpty()) { saveItemStatement.TryBindNull("@OwnerId"); } @@ -1266,7 +1266,7 @@ namespace Emby.Server.Implementations.Data /// is . public BaseItem RetrieveItem(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(id)); } @@ -1970,7 +1970,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } @@ -3230,7 +3230,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add($"ChannelId in ({inClause})"); } - if (!query.ParentId.Equals(default)) + if (!query.ParentId.IsEmpty()) { whereClauses.Add("ParentId=@ParentId"); statement?.TryBind("@ParentId", query.ParentId); @@ -4452,7 +4452,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type public void DeleteItem(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } @@ -4583,13 +4583,13 @@ AND Type = @InternalPersonType)"); statement?.TryBind("@UserId", query.User.InternalId); } - if (!query.ItemId.Equals(default)) + if (!query.ItemId.IsEmpty()) { whereClauses.Add("ItemId=@ItemId"); statement?.TryBind("@ItemId", query.ItemId); } - if (!query.AppearsInItemId.Equals(default)) + if (!query.AppearsInItemId.IsEmpty()) { whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); statement?.TryBind("@AppearsInItemId", query.AppearsInItemId); @@ -4640,7 +4640,7 @@ AND Type = @InternalPersonType)"); private void UpdateAncestors(Guid itemId, List ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement) { - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentNullException(nameof(itemId)); } @@ -5156,7 +5156,7 @@ AND Type = @InternalPersonType)"); private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db) { - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentNullException(nameof(itemId)); } @@ -5228,7 +5228,7 @@ AND Type = @InternalPersonType)"); public void UpdatePeople(Guid itemId, List people) { - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentNullException(nameof(itemId)); } @@ -5378,7 +5378,7 @@ AND Type = @InternalPersonType)"); { CheckDisposed(); - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } @@ -5758,7 +5758,7 @@ AND Type = @InternalPersonType)"); CancellationToken cancellationToken) { CheckDisposed(); - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty.", nameof(id)); } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index a83d7a4105..83e7b230df 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -241,7 +242,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint { var userIds = _sessionManager.Sessions .Select(i => i.UserId) - .Where(i => !i.Equals(default)) + .Where(i => !i.IsEmpty()) .Distinct() .ToArray(); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a79ffd9cb1..8ae913dad8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -732,7 +732,7 @@ namespace Emby.Server.Implementations.Library Path = path }; - if (folder.Id.Equals(default)) + if (folder.Id.IsEmpty()) { if (string.IsNullOrEmpty(folder.Path)) { @@ -1219,7 +1219,7 @@ namespace Emby.Server.Implementations.Library /// is null. public BaseItem GetItemById(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(id)); } @@ -1241,7 +1241,7 @@ namespace Emby.Server.Implementations.Library public List GetItemList(InternalItemsQuery query, bool allowExternalContent) { - if (query.Recursive && !query.ParentId.Equals(default)) + if (query.Recursive && !query.ParentId.IsEmpty()) { var parent = GetItemById(query.ParentId); if (parent is not null) @@ -1272,7 +1272,7 @@ namespace Emby.Server.Implementations.Library public int GetCount(InternalItemsQuery query) { - if (query.Recursive && !query.ParentId.Equals(default)) + if (query.Recursive && !query.ParentId.IsEmpty()) { var parent = GetItemById(query.ParentId); if (parent is not null) @@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations.Library public QueryResult GetItemsResult(InternalItemsQuery query) { - if (query.Recursive && !query.ParentId.Equals(default)) + if (query.Recursive && !query.ParentId.IsEmpty()) { var parent = GetItemById(query.ParentId); if (parent is not null) @@ -1486,7 +1486,7 @@ namespace Emby.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) { if (query.AncestorIds.Length == 0 && - query.ParentId.Equals(default) && + query.ParentId.IsEmpty() && query.ChannelIds.Count == 0 && query.TopParentIds.Length == 0 && string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && @@ -1520,7 +1520,7 @@ namespace Emby.Server.Implementations.Library } // Translate view into folders - if (!view.DisplayParentId.Equals(default)) + if (!view.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(view.DisplayParentId); if (displayParent is not null) @@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations.Library return Array.Empty(); } - if (!view.ParentId.Equals(default)) + if (!view.ParentId.IsEmpty()) { var displayParent = GetItemById(view.ParentId); if (displayParent is not null) @@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.Library return null; } - while (!item.ParentId.Equals(default)) + while (!item.ParentId.IsEmpty()) { var parent = item.GetParent(); if (parent is null || parent is AggregateFolder) @@ -2215,7 +2215,7 @@ namespace Emby.Server.Implementations.Library CollectionType? viewType, string sortName) { - var parentIdString = parentId.Equals(default) + var parentIdString = parentId.IsEmpty() ? null : parentId.ToString("N", CultureInfo.InvariantCulture); var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); @@ -2251,7 +2251,7 @@ namespace Emby.Server.Implementations.Library var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && !item.DisplayParentId.Equals(default)) + if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2315,7 +2315,7 @@ namespace Emby.Server.Implementations.Library var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && !item.DisplayParentId.Equals(default)) + if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2345,7 +2345,7 @@ namespace Emby.Server.Implementations.Library { ArgumentException.ThrowIfNullOrEmpty(name); - var parentIdString = parentId.Equals(default) + var parentIdString = parentId.IsEmpty() ? null : parentId.ToString("N", CultureInfo.InvariantCulture); var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); @@ -2391,7 +2391,7 @@ namespace Emby.Server.Implementations.Library var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && !item.DisplayParentId.Equals(default)) + if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2419,7 +2419,7 @@ namespace Emby.Server.Implementations.Library return GetItemById(parentId.Value); } - if (userId.HasValue && !userId.Equals(default)) + if (!userId.IsNullOrEmpty()) { return GetUserRootFolder(); } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 68eccf311d..c38f1af912 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -524,10 +525,10 @@ namespace Emby.Server.Implementations.Library _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource); var clone = JsonSerializer.Deserialize(json, _jsonOptions); - if (!request.UserId.Equals(default)) + if (!request.UserId.IsEmpty()) { var user = _userManager.GetUserById(request.UserId); - var item = request.ItemId.Equals(default) + var item = request.ItemId.IsEmpty() ? null : _libraryManager.GetItemById(request.ItemId); SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index b2439a87e1..078f4ad219 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library { return Guid.Empty; } - }).Where(i => !i.Equals(default)).ToArray(); + }).Where(i => !i.IsEmpty()).ToArray(); return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index b916b91708..020cb517d1 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library public QueryResult GetSearchHints(SearchQuery query) { User user = null; - if (!query.UserId.Equals(default)) + if (!query.UserId.IsEmpty()) { user = _userManager.GetUserById(query.UserId); } @@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Library if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist) { - if (!searchQuery.ParentId.Equals(default)) + if (!searchQuery.ParentId.IsEmpty()) { searchQuery.AncestorIds = new[] { searchQuery.ParentId }; searchQuery.ParentId = Guid.Empty; diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 1d662ed8dd..83a66c8e47 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -151,7 +152,7 @@ namespace Emby.Server.Implementations.Library var index = Array.IndexOf(orders, i.Id); if (index == -1 && i is UserView view - && !view.DisplayParentId.Equals(default)) + && !view.DisplayParentId.IsEmpty()) { index = Array.IndexOf(orders, view.DisplayParentId); } @@ -253,7 +254,7 @@ namespace Emby.Server.Implementations.Library var parents = new List(); - if (!parentId.Equals(default)) + if (!parentId.IsEmpty()) { var parentItem = _libraryManager.GetItemById(parentId); if (parentItem is Channel) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index d2e2fd7d56..aea8d65322 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.Playlists public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection itemIds, Guid userId) { - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index f457a78b3b..bbb3938dcf 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -337,7 +337,7 @@ namespace Emby.Server.Implementations.Session info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } - if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null) + if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null) { var current = session.NowPlayingItem; @@ -529,7 +529,7 @@ namespace Emby.Server.Implementations.Session { var users = new List(); - if (session.UserId.Equals(default)) + if (session.UserId.IsEmpty()) { return users; } @@ -690,7 +690,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = info.ItemId.Equals(default) + var libraryItem = info.ItemId.IsEmpty() ? null : GetNowPlayingItem(session, info.ItemId); @@ -784,7 +784,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = info.ItemId.Equals(default) + var libraryItem = info.ItemId.IsEmpty() ? null : GetNowPlayingItem(session, info.ItemId); @@ -923,7 +923,7 @@ namespace Emby.Server.Implementations.Session session.StopAutomaticProgress(); - var libraryItem = info.ItemId.Equals(default) + var libraryItem = info.ItemId.IsEmpty() ? null : GetNowPlayingItem(session, info.ItemId); @@ -933,7 +933,7 @@ namespace Emby.Server.Implementations.Session info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } - if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null) + if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null) { var current = session.NowPlayingItem; @@ -1154,7 +1154,7 @@ namespace Emby.Server.Implementations.Session var session = GetSessionToRemoteControl(sessionId); - var user = session.UserId.Equals(default) ? null : _userManager.GetUserById(session.UserId); + var user = session.UserId.IsEmpty() ? null : _userManager.GetUserById(session.UserId); List items; @@ -1223,7 +1223,7 @@ namespace Emby.Server.Implementations.Session { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (!controllingSession.UserId.Equals(default)) + if (!controllingSession.UserId.IsEmpty()) { command.ControllingUserId = controllingSession.UserId; } @@ -1342,7 +1342,7 @@ namespace Emby.Server.Implementations.Session { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (!controllingSession.UserId.Equals(default)) + if (!controllingSession.UserId.IsEmpty()) { command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture); } @@ -1463,7 +1463,7 @@ namespace Emby.Server.Implementations.Session ArgumentException.ThrowIfNullOrEmpty(request.AppVersion); User user = null; - if (!request.UserId.Equals(default)) + if (!request.UserId.IsEmpty()) { user = _userManager.GetUserById(request.UserId); } @@ -1766,7 +1766,7 @@ namespace Emby.Server.Implementations.Session { ArgumentNullException.ThrowIfNull(info); - var user = info.UserId.Equals(default) + var user = info.UserId.IsEmpty() ? null : _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index da8f949326..a7821c0e0e 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; @@ -553,7 +554,7 @@ namespace Emby.Server.Implementations.SyncPlay if (playingItemRemoved) { var itemId = PlayQueue.GetPlayingItemId(); - if (!itemId.Equals(default)) + if (!itemId.IsEmpty()) { var item = _libraryManager.GetItemById(itemId); RunTimeTicks = item.RunTimeTicks ?? 0; diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index ef890aeb4f..34c9e86f26 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.TV } string? presentationUniqueKey = null; - if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default)) + if (!query.SeriesId.IsNullOrEmpty()) { if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series) { @@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.TV string? presentationUniqueKey = null; int? limit = null; - if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default)) + if (!request.SeriesId.IsNullOrEmpty()) { if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series) { @@ -146,7 +147,7 @@ namespace Emby.Server.Implementations.TV // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) - var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default); + var alwaysEnableFirstEpisode = !request.SeriesId.IsNullOrEmpty(); var anyFound = false; return allNextUp diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 15c4cfdf02..ce3d6cab88 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.Updates availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); } - if (!id.Equals(default)) + if (!id.IsEmpty()) { availablePackages = availablePackages.Where(x => x.Id.Equals(id)); } diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs index cf3cb69052..7d0fe55898 100644 --- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs @@ -2,6 +2,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; @@ -41,7 +42,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy var isApiKey = context.User.GetIsApiKey(); var userId = context.User.GetUserId(); // This likely only happens during the wizard, so skip the default checks and let any other handlers do it - if (!isApiKey && userId.Equals(default)) + if (!isApiKey && userId.IsEmpty()) { return Task.CompletedTask; } diff --git a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs index 688a13bc0b..965b7e7e60 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; @@ -46,7 +47,7 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy } var userId = contextUser.GetUserId(); - if (userId.Equals(default)) + if (userId.IsEmpty()) { context.Fail(); return Task.CompletedTask; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index e7d3e694ab..8b931f1621 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -126,7 +127,7 @@ public class ArtistsController : BaseJellyfinApiController User? user = null; BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { user = _userManager.GetUserById(userId.Value); } @@ -330,7 +331,7 @@ public class ArtistsController : BaseJellyfinApiController User? user = null; BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { user = _userManager.GetUserById(userId.Value); } @@ -469,7 +470,7 @@ public class ArtistsController : BaseJellyfinApiController var item = _libraryManager.GetArtist(name, dtoOptions); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index fdc16ee23f..f83c71b578 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -126,7 +127,7 @@ public class ChannelsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -201,7 +202,7 @@ public class ChannelsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index baeb8b81a0..d6e043e6a1 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -3,6 +3,7 @@ using System.Linq; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -53,7 +54,7 @@ public class FilterController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -146,7 +147,7 @@ public class FilterController : BaseJellyfinApiController [FromQuery] bool? recursive) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 6cb1993e46..54d48aec21 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -95,7 +96,7 @@ public class GenresController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -172,7 +173,7 @@ public class GenresController : BaseJellyfinApiController item ??= new Genre(); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 4dc2a4253d..e7ff1f9868 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -76,7 +77,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -113,7 +114,7 @@ public class InstantMixController : BaseJellyfinApiController { var album = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -150,7 +151,7 @@ public class InstantMixController : BaseJellyfinApiController { var playlist = (Playlist)_libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -186,7 +187,7 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -223,7 +224,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -260,7 +261,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -334,7 +335,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index a1fc8e11b2..d10fba920c 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -245,7 +246,7 @@ public class ItemsController : BaseJellyfinApiController var isApiKey = User.GetIsApiKey(); // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method userId = RequestHelpers.GetUserId(User, userId); - var user = !isApiKey && !userId.Value.Equals(default) + var user = !isApiKey && !userId.IsNullOrEmpty() ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException() : null; @@ -840,7 +841,7 @@ public class ItemsController : BaseJellyfinApiController var ancestorIds = Array.Empty(); var excludeFolderIds = user.GetPreferenceValues(PreferenceKind.LatestItemExcludes); - if (parentIdGuid.Equals(default) && excludeFolderIds.Length > 0) + if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index de057bbab9..a0bbc961f0 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -146,12 +146,12 @@ public class LibraryController : BaseJellyfinApiController [FromQuery] bool inheritFromParent = false) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); @@ -213,12 +213,12 @@ public class LibraryController : BaseJellyfinApiController [FromQuery] bool inheritFromParent = false) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); @@ -339,7 +339,7 @@ public class LibraryController : BaseJellyfinApiController { var isApiKey = User.GetIsApiKey(); var userId = User.GetUserId(); - var user = !isApiKey && !userId.Equals(default) + var user = !isApiKey && !userId.IsEmpty() ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() : null; if (!isApiKey && user is null) @@ -382,7 +382,7 @@ public class LibraryController : BaseJellyfinApiController { var isApiKey = User.GetIsApiKey(); var userId = User.GetUserId(); - var user = !isApiKey && !userId.Equals(default) + var user = !isApiKey && !userId.IsEmpty() ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() : null; @@ -428,7 +428,7 @@ public class LibraryController : BaseJellyfinApiController [FromQuery] bool? isFavorite) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -471,7 +471,7 @@ public class LibraryController : BaseJellyfinApiController var baseItemDtos = new List(); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -702,8 +702,8 @@ public class LibraryController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); @@ -718,7 +718,7 @@ public class LibraryController : BaseJellyfinApiController return new QueryResult(); } - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 27eb88b60f..1b2f5750f6 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -15,6 +15,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -182,7 +183,7 @@ public class LiveTvController : BaseJellyfinApiController dtoOptions, CancellationToken.None); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -214,10 +215,10 @@ public class LiveTvController : BaseJellyfinApiController public ActionResult GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = channelId.Equals(default) + var item = channelId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(channelId); @@ -387,7 +388,7 @@ public class LiveTvController : BaseJellyfinApiController public async Task>> GetRecordingFolders([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false); @@ -410,10 +411,10 @@ public class LiveTvController : BaseJellyfinApiController public ActionResult GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); + var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); var dtoOptions = new DtoOptions() .AddClientFields(User); @@ -567,7 +568,7 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool enableTotalRecordCount = true) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -594,7 +595,7 @@ public class LiveTvController : BaseJellyfinApiController GenreIds = genreIds }; - if (librarySeriesId.HasValue && !librarySeriesId.Equals(default)) + if (!librarySeriesId.IsNullOrEmpty()) { query.IsSeries = true; @@ -623,7 +624,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvAccess)] public async Task>> GetPrograms([FromBody] GetProgramsDto body) { - var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId); + var user = body.UserId.IsEmpty() ? null : _userManager.GetUserById(body.UserId); var query = new InternalItemsQuery(user) { @@ -648,7 +649,7 @@ public class LiveTvController : BaseJellyfinApiController GenreIds = body.GenreIds }; - if (!body.LibrarySeriesId.Equals(default)) + if (!body.LibrarySeriesId.IsEmpty()) { query.IsSeries = true; @@ -707,7 +708,7 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool enableTotalRecordCount = true) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -746,7 +747,7 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index e1145481fa..471bcd096e 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -7,6 +7,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -69,7 +70,7 @@ public class MoviesController : BaseJellyfinApiController [FromQuery] int itemLimit = 8) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 69b9042646..5411baa3e7 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -95,7 +96,7 @@ public class MusicGenresController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -164,7 +165,7 @@ public class MusicGenresController : BaseJellyfinApiController return NotFound(); } - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index b4c6f490a0..6ca3086015 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -83,7 +84,7 @@ public class PersonsController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -129,7 +130,7 @@ public class PersonsController : BaseJellyfinApiController return NotFound(); } - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); return _dtoService.GetBaseItemDto(item, dtoOptions, user); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index c4c89ccde0..921cc6031f 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -9,6 +9,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; @@ -188,7 +189,7 @@ public class PlaylistsController : BaseJellyfinApiController return NotFound(); } - var user = userId.Equals(default) + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 5b45941657..413b7b8340 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -209,7 +209,7 @@ public class SearchController : BaseJellyfinApiController break; } - if (!item.ChannelId.Equals(default)) + if (!item.ChannelId.IsEmpty()) { var channel = _libraryManager.GetItemById(item.ChannelId); result.ChannelName = channel?.Name; diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 083515a949..52b58b8f10 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.SessionDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; @@ -71,7 +72,7 @@ public class SessionController : BaseJellyfinApiController result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); } - if (controllableByUserId.HasValue && !controllableByUserId.Equals(default)) + if (!controllableByUserId.IsNullOrEmpty()) { result = result.Where(i => i.SupportsRemoteControl); @@ -83,12 +84,12 @@ public class SessionController : BaseJellyfinApiController if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { - result = result.Where(i => i.UserId.Equals(default) || i.ContainsUser(controllableByUserId.Value)); + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableByUserId.Value)); } if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) { - result = result.Where(i => !i.UserId.Equals(default)); + result = result.Where(i => !i.UserId.IsEmpty()); } result = result.Where(i => diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index f434f60f51..708fc7436f 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -91,7 +92,7 @@ public class StudiosController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -144,7 +145,7 @@ public class StudiosController : BaseJellyfinApiController var dtoOptions = new DtoOptions().AddClientFields(User); var item = _libraryManager.GetStudio(name); - if (!userId.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 675757fc51..2aa6d25a79 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -62,7 +63,7 @@ public class SuggestionsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] bool enableTotalRecordCount = false) { - var user = userId.Equals(default) + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 55a30d4692..3d84b61bf4 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -111,7 +111,7 @@ public class TvShowsController : BaseJellyfinApiController }, options); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -150,7 +150,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] bool? enableUserData) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -222,7 +222,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] ItemSortBy? sortBy) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -284,7 +284,7 @@ public class TvShowsController : BaseJellyfinApiController } // This must be the last filter - if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default)) + if (!adjacentTo.IsNullOrEmpty()) { episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList(); } @@ -339,7 +339,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] bool? enableUserData) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index f9f27f1480..ea10ee24f9 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.UserDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -532,7 +533,7 @@ public class UserController : BaseJellyfinApiController public ActionResult GetCurrentUser() { var userId = User.GetUserId(); - if (userId.Equals(default)) + if (userId.IsEmpty()) { return BadRequest(); } diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 2c4fe91862..264e0a3dbd 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -84,7 +85,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -145,7 +146,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -185,7 +186,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -221,7 +222,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -257,7 +258,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -294,7 +295,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -330,7 +331,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -375,7 +376,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -558,7 +559,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index c231c147fc..e6c3198698 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -11,6 +11,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -96,12 +97,12 @@ public class VideosController : BaseJellyfinApiController public ActionResult> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index ca46c38c59..e4aa0ea42d 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -90,7 +90,7 @@ public class YearsController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); @@ -110,7 +110,7 @@ public class YearsController : BaseJellyfinApiController { var folder = (Folder)parentItem; - if (userId.Equals(default)) + if (userId.IsNullOrEmpty()) { items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList(); } @@ -182,7 +182,7 @@ public class YearsController : BaseJellyfinApiController var dtoOptions = new DtoOptions() .AddClientFields(User); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); return _dtoService.GetBaseItemDto(item, dtoOptions, user); diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 321987ca74..6a24ad32ab 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -86,7 +87,7 @@ public class MediaInfoHelper string? mediaSourceId = null, string? liveStreamId = null) { - var user = userId is null || userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var item = _libraryManager.GetItemById(id); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index be3d4dfb67..429e972132 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -7,6 +7,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -67,7 +68,7 @@ public static class RequestHelpers var authenticatedUserId = claimsPrincipal.GetUserId(); // UserId not provided, fall back to authenticated user id. - if (userId is null || userId.Value.Equals(default)) + if (userId.IsNullOrEmpty()) { return authenticatedUserId; } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 78943f7b58..7a3842a9f7 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -82,7 +82,7 @@ public static class StreamingHelpers }; var userId = httpContext.User.GetUserId(); - if (!userId.Equals(default)) + if (!userId.IsEmpty()) { state.User = userManager.GetUserById(userId); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index cc464a7a3d..c4a2bfdb87 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -11,6 +11,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Data.Events.Users; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -117,7 +118,7 @@ namespace Jellyfin.Server.Implementations.Users /// public User? GetUserById(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(id)); } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index d789033f16..b225f22df0 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 /// The id is empty. 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 18d948a62f..11cdf84445 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 { [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 fdbceac961..ddcc994a0b 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 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 e707eedbf6..74eb089de3 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 bf31508c1d..37e2414142 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 0a040a3c28..c29cefc15e 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 eb026deb4f..c93488a85e 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -70,11 +70,11 @@ namespace MediaBrowser.Controller.Entities /// public override IEnumerable 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 a3525c8623..4af000557e 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 be2eb4d288..5adadec390 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.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 483d0a1d82..d79e4441aa 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -9,6 +9,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -400,7 +401,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bf18d46dcd..da683a17e6 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; @@ -1536,7 +1537,7 @@ namespace MediaBrowser.Model.Dlna private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource) { - if (options.ItemId.Equals(default)) + if (options.ItemId.IsEmpty()) { ArgumentException.ThrowIfNullOrEmpty(options.DeviceId); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 4ba8844182..b530b9de3f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -706,7 +706,7 @@ namespace MediaBrowser.Providers.Manager { BaseItem? referenceItem = null; - if (!searchInfo.ItemId.Equals(default)) + if (!searchInfo.ItemId.IsEmpty()) { referenceItem = _libraryManager.GetItemById(searchInfo.ItemId); } @@ -944,7 +944,7 @@ namespace MediaBrowser.Providers.Manager public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority) { ArgumentNullException.ThrowIfNull(itemId); - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(itemId)); } diff --git a/src/Jellyfin.Extensions/GuidExtensions.cs b/src/Jellyfin.Extensions/GuidExtensions.cs new file mode 100644 index 0000000000..95c591a82e --- /dev/null +++ b/src/Jellyfin.Extensions/GuidExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Jellyfin.Extensions; + +/// +/// Guid specific extensions. +/// +public static class GuidExtensions +{ + /// + /// Determine whether the guid is default. + /// + /// The guid. + /// Whether the guid is the default value. + public static bool IsEmpty(this Guid guid) + => guid.Equals(default); + + /// + /// Determine whether the guid is null or default. + /// + /// The guid. + /// Whether the guid is null or the default valueF. + public static bool IsNullOrEmpty([NotNullWhen(false)] this Guid? guid) + => guid is null || guid.Value.IsEmpty(); +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs index 656e3c3dab..0a50b5c3b9 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Extensions.Json.Converters { // null got handled higher up the call stack var val = value!.Value; - if (val.Equals(default)) + if (val.IsEmpty()) { writer.WriteNullValue(); } diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs index 51abb503eb..bc968f8ee1 100644 --- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs +++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs @@ -150,7 +150,7 @@ namespace Jellyfin.LiveTv.Channels /// public async Task> GetChannelsInternalAsync(ChannelQuery query) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); @@ -263,7 +263,7 @@ namespace Jellyfin.LiveTv.Channels /// public async Task> GetChannelsAsync(ChannelQuery query) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); @@ -695,7 +695,7 @@ namespace Jellyfin.LiveTv.Channels // Find the corresponding channel provider plugin var channelProvider = GetChannelProvider(channel); - var parentItem = query.ParentId.Equals(default) + var parentItem = query.ParentId.IsEmpty() ? channel : _libraryManager.GetItemById(query.ParentId); @@ -708,7 +708,7 @@ namespace Jellyfin.LiveTv.Channels cancellationToken) .ConfigureAwait(false); - if (query.ParentId.Equals(default)) + if (query.ParentId.IsEmpty()) { query.Parent = channel; } diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 9eb3aa2fd6..e7e927b2d0 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -1992,7 +1992,7 @@ namespace Jellyfin.LiveTv.EmbyTV await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false); } - var people = item.Id.Equals(default) ? new List() : _libraryManager.GetPeople(item); + var people = item.Id.IsEmpty() ? new List() : _libraryManager.GetPeople(item); var directors = people .Where(i => i.IsType(PersonKind.Director)) @@ -2317,7 +2317,7 @@ namespace Jellyfin.LiveTv.EmbyTV { string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId; - if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.Equals(default)) + if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.IsEmpty()) { if (!tempChannelCache.TryGetValue(parent.ChannelId, out LiveTvChannel channel)) { @@ -2376,7 +2376,7 @@ namespace Jellyfin.LiveTv.EmbyTV { string channelId = null; - if (!programInfo.ChannelId.Equals(default)) + if (!programInfo.ChannelId.IsEmpty()) { if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out LiveTvChannel channel)) { diff --git a/src/Jellyfin.LiveTv/LiveTvDtoService.cs b/src/Jellyfin.LiveTv/LiveTvDtoService.cs index 7c7c26eb4a..55b056d3d8 100644 --- a/src/Jellyfin.LiveTv/LiveTvDtoService.cs +++ b/src/Jellyfin.LiveTv/LiveTvDtoService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; @@ -456,7 +457,7 @@ namespace Jellyfin.LiveTv info.Id = timer.ExternalId; } - if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId)) + if (!dto.ChannelId.IsEmpty() && string.IsNullOrEmpty(info.ChannelId)) { var channel = _libraryManager.GetItemById(dto.ChannelId); @@ -522,7 +523,7 @@ namespace Jellyfin.LiveTv info.Id = timer.ExternalId; } - if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId)) + if (!dto.ChannelId.IsEmpty() && string.IsNullOrEmpty(info.ChannelId)) { var channel = _libraryManager.GetItemById(dto.ChannelId); diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 71822f3762..bada4249aa 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -150,7 +151,7 @@ namespace Jellyfin.LiveTv public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); @@ -1245,7 +1246,7 @@ namespace Jellyfin.LiveTv { cancellationToken.ThrowIfCancellationRequested(); - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { // Somehow some invalid data got into the db. It probably predates the boundary checking continue; @@ -1504,7 +1505,7 @@ namespace Jellyfin.LiveTv public async Task> GetRecordingsAsync(RecordingQuery query, DtoOptions options) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 4e8aec9f19..2f21495044 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.StartupDtos; using Jellyfin.Api.Models.UserDtos; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Model.Dto; using Xunit; @@ -56,7 +57,7 @@ namespace Jellyfin.Server.Integration.Tests public static async Task GetRootFolderDtoAsync(HttpClient client, Guid userId = default) { - if (userId.Equals(default)) + if (userId.IsEmpty()) { var userDto = await GetUserDtoAsync(client); userId = userDto.Id; -- cgit v1.2.3