From 20fd05b05081ad387e94128b4f26d907808c8f0c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 17 Feb 2023 19:27:36 +0100 Subject: Consistently write IP in upercase --- Jellyfin.Api/Controllers/MediaInfoController.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 2 +- Jellyfin.Api/Controllers/UniversalAudioController.cs | 2 +- Jellyfin.Api/Controllers/UserController.cs | 18 +++++++++--------- 4 files changed, 12 insertions(+), 12 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index ea10dd771f..1bef35c274 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -183,7 +183,7 @@ public class MediaInfoController : BaseJellyfinApiController enableTranscoding.Value, allowVideoStreamCopy.Value, allowAudioStreamCopy.Value, - Request.HttpContext.GetNormalizedRemoteIp()); + Request.HttpContext.GetNormalizedRemoteIP()); } _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate); diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 4ab705f40a..91901518fa 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -179,7 +179,7 @@ public class SystemController : BaseJellyfinApiController return new EndPointInfo { IsLocal = HttpContext.IsLocal(), - IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp()) + IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) }; } diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 3455215979..04f2109eaa 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -143,7 +143,7 @@ public class UniversalAudioController : BaseJellyfinApiController true, true, true, - Request.HttpContext.GetNormalizedRemoteIp()); + Request.HttpContext.GetNormalizedRemoteIP()); } _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate); diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index b0973b8a14..ec54255780 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -129,7 +129,7 @@ public class UserController : BaseJellyfinApiController return NotFound("User not found"); } - var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp().ToString()); + var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIP().ToString()); return result; } @@ -211,7 +211,7 @@ public class UserController : BaseJellyfinApiController DeviceId = auth.DeviceId, DeviceName = auth.Device, Password = request.Pw, - RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(), + RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(), Username = request.Username }).ConfigureAwait(false); @@ -220,7 +220,7 @@ public class UserController : BaseJellyfinApiController catch (SecurityException e) { // rethrow adding IP address to message - throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIp()}] {e.Message}", e); + throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e); } } @@ -242,7 +242,7 @@ public class UserController : BaseJellyfinApiController catch (SecurityException e) { // rethrow adding IP address to message - throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIp()}] {e.Message}", e); + throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e); } } @@ -288,7 +288,7 @@ public class UserController : BaseJellyfinApiController user.Username, request.CurrentPw ?? string.Empty, request.CurrentPw ?? string.Empty, - HttpContext.GetNormalizedRemoteIp().ToString(), + HttpContext.GetNormalizedRemoteIP().ToString(), false).ConfigureAwait(false); if (success is null) @@ -489,7 +489,7 @@ public class UserController : BaseJellyfinApiController await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false); } - var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp().ToString()); + var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString()); return result; } @@ -504,7 +504,7 @@ public class UserController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public async Task> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest) { - var ip = HttpContext.GetNormalizedRemoteIp(); + var ip = HttpContext.GetNormalizedRemoteIP(); var isLocal = HttpContext.IsLocal() || _networkManager.IsInLocalNetwork(ip); @@ -585,7 +585,7 @@ public class UserController : BaseJellyfinApiController if (filterByNetwork) { - if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp())) + if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())) { users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess)); } @@ -593,7 +593,7 @@ public class UserController : BaseJellyfinApiController var result = users .OrderBy(u => u.Username) - .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp().ToString())); + .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString())); return result; } -- cgit v1.2.3 From 9564248b10829ed21c62b19aa84e50806d35fd59 Mon Sep 17 00:00:00 2001 From: MBR-0001 <55142207+MBR-0001@users.noreply.github.com> Date: Sat, 29 Jul 2023 14:52:35 +0200 Subject: Add ability to upload (and save) SDH subtitles (#10036) --- Jellyfin.Api/Controllers/SubtitleController.cs | 5 +++-- Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs | 6 ++++++ MediaBrowser.Controller/Subtitles/SubtitleResponse.cs | 2 ++ MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index b3e9d62972..7d02550b68 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -90,7 +90,7 @@ public class SubtitleController : BaseJellyfinApiController [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteSubtitle( + public async Task DeleteSubtitle( [FromRoute, Required] Guid itemId, [FromRoute, Required] int index) { @@ -101,7 +101,7 @@ public class SubtitleController : BaseJellyfinApiController return NotFound(); } - _subtitleManager.DeleteSubtitles(item, index); + await _subtitleManager.DeleteSubtitles(item, index).ConfigureAwait(false); return NoContent(); } @@ -416,6 +416,7 @@ public class SubtitleController : BaseJellyfinApiController Format = body.Format, Language = body.Language, IsForced = body.IsForced, + IsHearingImpaired = body.IsHearingImpaired, Stream = memoryStream }).ConfigureAwait(false); _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); diff --git a/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs index 3c903ea6b5..2c45e704bc 100644 --- a/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs +++ b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs @@ -25,6 +25,12 @@ public class UploadSubtitleDto [Required] public bool IsForced { get; set; } + /// + /// Gets or sets a value indicating whether the subtitle is for hearing impaired. + /// + [Required] + public bool IsHearingImpaired { get; set; } + /// /// Gets or sets the subtitle data. /// diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs index 85b3e6fbd7..51c29c7a24 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs @@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.Subtitles public bool IsForced { get; set; } + public bool IsHearingImpaired { get; set; } + public Stream Stream { get; set; } } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 0c01c50317..87fd2a3cda 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -200,6 +200,11 @@ namespace MediaBrowser.Providers.Subtitles saveFileName += ".forced"; } + if (response.IsHearingImpaired) + { + saveFileName += ".sdh"; + } + saveFileName += "." + response.Format.ToLowerInvariant(); if (saveInMediaFolder) -- cgit v1.2.3 From dd75f35a1a453541e4ca8cfe9422d3e39e9d668b Mon Sep 17 00:00:00 2001 From: Jan Müller Date: Mon, 31 Jul 2023 21:49:51 +0200 Subject: Fix the is-local check when resetting the password This fixes the check whether a warning should be logged when resetting the password from outside the local network. Fixes: #10059 --- Jellyfin.Api/Controllers/UserController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 4f61af35e0..1be40111dd 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -494,7 +494,7 @@ public class UserController : BaseJellyfinApiController var isLocal = HttpContext.IsLocal() || _networkManager.IsInLocalNetwork(ip); - if (isLocal) + if (!isLocal) { _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip); } -- cgit v1.2.3 From 79cff704ff4e00895a8d2b97ecbbea3e2f5f56fc Mon Sep 17 00:00:00 2001 From: Jan Müller Date: Tue, 1 Aug 2023 17:28:49 +0200 Subject: Allow flac inside mp4 for all HLS audio streams The -strict -2 setting was only added if the encoder was set to 'copy'. If 'flac' is explicitly requested, we also need to set it, so that ffmpeg doesn't abort the conversion. Fixes: #10066 --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index ce684e457c..94093f1671 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1704,18 +1704,29 @@ public class DynamicHlsController : BaseJellyfinApiController var audioCodec = _encodingHelper.GetAudioEncoder(state); + // dts, flac, opus and truehd are experimental in mp4 muxer + var strictArgs = string.Empty; + var actualOutputAudioCodec = state.ActualOutputAudioCodec; + if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) + { + strictArgs = " -strict -2"; + } + if (!state.IsOutputVideo) { if (EncodingHelper.IsCopyCodec(audioCodec)) { var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); - return "-acodec copy -strict -2" + bitStreamArgs; + return "-acodec copy" + bitStreamArgs + strictArgs; } var audioTranscodeParams = string.Empty; - audioTranscodeParams += "-acodec " + audioCodec; + audioTranscodeParams += "-acodec " + audioCodec + strictArgs; var audioBitrate = state.OutputAudioBitrate; var audioChannels = state.OutputAudioChannels; @@ -1747,17 +1758,6 @@ public class DynamicHlsController : BaseJellyfinApiController return audioTranscodeParams; } - // dts, flac, opus and truehd are experimental in mp4 muxer - var strictArgs = string.Empty; - var actualOutputAudioCodec = state.ActualOutputAudioCodec; - if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) - || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) - || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) - { - strictArgs = " -strict -2"; - } - if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); -- cgit v1.2.3 From c74d3e62d0989eaaa0f19f383fd153746348a234 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 31 Aug 2023 07:18:18 +0800 Subject: Fix the issue that audio bsf only takes effect for remuxing (#10172) Signed-off-by: nyanmisaka --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index ce684e457c..5ba0119389 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1703,19 +1703,18 @@ public class DynamicHlsController : BaseJellyfinApiController } var audioCodec = _encodingHelper.GetAudioEncoder(state); + var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); if (!state.IsOutputVideo) { if (EncodingHelper.IsCopyCodec(audioCodec)) { - var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); - return "-acodec copy -strict -2" + bitStreamArgs; } var audioTranscodeParams = string.Empty; - audioTranscodeParams += "-acodec " + audioCodec; + audioTranscodeParams += "-acodec " + audioCodec + bitStreamArgs; var audioBitrate = state.OutputAudioBitrate; var audioChannels = state.OutputAudioChannels; @@ -1761,7 +1760,6 @@ public class DynamicHlsController : BaseJellyfinApiController if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); - var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs; if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) @@ -1772,7 +1770,7 @@ public class DynamicHlsController : BaseJellyfinApiController return copyArgs; } - var args = "-codec:a:0 " + audioCodec + strictArgs; + var args = "-codec:a:0 " + audioCodec + bitStreamArgs + strictArgs; var channels = state.OutputAudioChannels; -- cgit v1.2.3 From b2dcc7c90e42160adab28e3dac31e2bc4fd2c193 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 2 Sep 2023 00:05:00 +0800 Subject: Fix AV1 playback in LiveTV AV1 in fMP4 requires global_header data for parsing. Only disable global_header in TS since it has no global_header. Signed-off-by: nyanmisaka --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 5ba0119389..065a4ce5c6 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1651,7 +1651,7 @@ public class DynamicHlsController : BaseJellyfinApiController _encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer), threads, mapArgs, - GetVideoArguments(state, startNumber, isEventPlaylist), + GetVideoArguments(state, startNumber, isEventPlaylist, segmentContainer), GetAudioArguments(state), maxMuxingQueueSize, state.SegmentLength.ToString(CultureInfo.InvariantCulture), @@ -1814,8 +1814,9 @@ public class DynamicHlsController : BaseJellyfinApiController /// The . /// The first number in the hls sequence. /// Whether the playlist is EVENT or VOD. + /// The segment container. /// The command line arguments for video transcoding. - private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist) + private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist, string segmentContainer) { if (state.VideoStream is null) { @@ -1907,7 +1908,7 @@ public class DynamicHlsController : BaseJellyfinApiController } // TODO why was this not enabled for VOD? - if (isEventPlaylist) + if (isEventPlaylist && string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase)) { args += " -flags -global_header"; } -- cgit v1.2.3 From 9c64f94458b955da8dfb7761d781ce2f45a8dfca Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 5 Sep 2023 23:30:06 -0400 Subject: Add option to include resumable items in next up requests --- Emby.Server.Implementations/TV/TVSeriesManager.cs | 20 ++++++++++---------- Jellyfin.Api/Controllers/TvShowsController.cs | 5 ++++- MediaBrowser.Model/Querying/NextUpQuery.cs | 6 ++++++ 3 files changed, 20 insertions(+), 11 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index f0e173f0b1..ef890aeb4f 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -135,13 +135,13 @@ namespace Emby.Server.Implementations.TV private IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList seriesKeys, DtoOptions dtoOptions) { - var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false)); + var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, request.EnableResumable, false)); if (request.EnableRewatching) { - allNextUp = allNextUp.Concat( - seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, true))) - .OrderByDescending(i => i.LastWatchedDate); + allNextUp = allNextUp + .Concat(seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false, true))) + .OrderByDescending(i => i.LastWatchedDate); } // If viewing all next up for all series, remove first episodes @@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.TV /// Gets the next up. /// /// Task{Episode}. - private (DateTime LastWatchedDate, Func GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching) + private (DateTime LastWatchedDate, Func GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool includeResumable, bool includePlayed) { var lastQuery = new InternalItemsQuery(user) { @@ -200,8 +200,8 @@ namespace Emby.Server.Implementations.TV } }; - // If rewatching is enabled, sort first by date played and then by season and episode numbers - lastQuery.OrderBy = rewatching + // If including played results, sort first by date played and then by season and episode numbers + lastQuery.OrderBy = includePlayed ? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) } : new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) }; @@ -216,7 +216,7 @@ namespace Emby.Server.Implementations.TV IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending) }, Limit = 1, - IsPlayed = rewatching, + IsPlayed = includePlayed, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, DtoOptions = dtoOptions @@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.TV SeriesPresentationUniqueKey = seriesKey, ParentIndexNumber = 0, IncludeItemTypes = new[] { BaseItemKind.Episode }, - IsPlayed = rewatching, + IsPlayed = includePlayed, IsVirtualItem = false, DtoOptions = dtoOptions }) @@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.TV nextEpisode = sortedConsideredEpisodes.FirstOrDefault(); } - if (nextEpisode is not null) + if (nextEpisode is not null && !includeResumable) { var userData = _userDataManager.GetUserData(user, nextEpisode); diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 7d23281f2c..bdbbd1e0db 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -68,7 +68,8 @@ public class TvShowsController : BaseJellyfinApiController /// Optional. Starting date of shows to show in Next Up section. /// Whether to enable the total records count. Defaults to true. /// Whether to disable sending the first episode in a series as next up. - /// Whether to include watched episode in next up results. + /// Whether to include resumable episodes in next up results. + /// Whether to include watched episodes in next up results. /// A with the next up episodes. [HttpGet("NextUp")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -86,6 +87,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] DateTime? nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool disableFirstEpisode = false, + [FromQuery] bool enableResumable = true, [FromQuery] bool enableRewatching = false) { userId = RequestHelpers.GetUserId(User, userId); @@ -104,6 +106,7 @@ public class TvShowsController : BaseJellyfinApiController EnableTotalRecordCount = enableTotalRecordCount, DisableFirstEpisode = disableFirstEpisode, NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue, + EnableResumable = enableResumable, EnableRewatching = enableRewatching }, options); diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 0fb996df97..35353e6fa6 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Querying EnableTotalRecordCount = true; DisableFirstEpisode = false; NextUpDateCutoff = DateTime.MinValue; + EnableResumable = false; EnableRewatching = false; } @@ -83,6 +84,11 @@ namespace MediaBrowser.Model.Querying /// public DateTime NextUpDateCutoff { get; set; } + /// + /// Gets or sets a value indicating whether to include resumable episodes as next up. + /// + public bool EnableResumable { get; set; } + /// /// Gets or sets a value indicating whether getting rewatching next up list. /// -- cgit v1.2.3 From 493de3297a415061f8d6a69ff9f62261c3159a2a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 Sep 2023 21:10:49 -0400 Subject: Use IHostLifetime to handle restarting and shutting down --- Emby.Server.Implementations/ApplicationHost.cs | 124 ++------------------- .../Plugins/PluginManager.cs | 22 ++-- .../Session/SessionManager.cs | 117 +++++++++---------- Jellyfin.Api/Controllers/SystemController.cs | 14 ++- Jellyfin.Server/CoreAppHost.cs | 6 - Jellyfin.Server/Program.cs | 70 ++---------- MediaBrowser.Common/IApplicationHost.cs | 21 ++-- MediaBrowser.Common/Plugins/IPluginManager.cs | 5 - MediaBrowser.Controller/Session/ISessionManager.cs | 14 --- .../JellyfinApplicationFactory.cs | 3 +- 10 files changed, 98 insertions(+), 298 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8b13ccadab..86721ace61 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -12,7 +12,6 @@ using System.Linq; using System.Net; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; using Emby.Dlna; using Emby.Dlna.Main; @@ -112,7 +111,7 @@ namespace Emby.Server.Implementations /// /// Class CompositionRoot. /// - public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable + public abstract class ApplicationHost : IServerApplicationHost, IDisposable { /// /// The disposable parts. @@ -127,7 +126,6 @@ namespace Emby.Server.Implementations private readonly IPluginManager _pluginManager; private List _creatingInstances; - private ISessionManager _sessionManager; /// /// Gets or sets all concrete types. @@ -172,6 +170,8 @@ namespace Emby.Server.Implementations ConfigurationManager.Configuration, ApplicationPaths.PluginsPath, ApplicationVersion); + + _disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue); } /// @@ -202,7 +202,10 @@ namespace Emby.Server.Implementations public bool HasPendingRestart { get; private set; } /// - public bool IsShuttingDown { get; private set; } + public bool IsShuttingDown { get; set; } + + /// + public bool ShouldRestart { get; set; } /// /// Gets the logger. @@ -406,11 +409,9 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// - /// The cancellation token. /// . - public async Task RunStartupTasksAsync(CancellationToken cancellationToken) + public async Task RunStartupTasksAsync() { - cancellationToken.ThrowIfCancellationRequested(); Logger.LogInformation("Running startup tasks"); Resolve().AddTasks(GetExports(false)); @@ -424,8 +425,6 @@ namespace Emby.Server.Implementations var entryPoints = GetExports(); - cancellationToken.ThrowIfCancellationRequested(); - var stopWatch = new Stopwatch(); stopWatch.Start(); @@ -435,8 +434,6 @@ namespace Emby.Server.Implementations Logger.LogInformation("Core startup complete"); CoreStartupHasCompleted = true; - cancellationToken.ThrowIfCancellationRequested(); - stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); @@ -633,8 +630,6 @@ namespace Emby.Server.Implementations var localizationManager = (LocalizationManager)Resolve(); await localizationManager.LoadAll().ConfigureAwait(false); - _sessionManager = Resolve(); - SetStaticProperties(); FindParts(); @@ -855,38 +850,6 @@ namespace Emby.Server.Implementations } } - /// - /// Restarts this instance. - /// - public void Restart() - { - if (IsShuttingDown) - { - return; - } - - IsShuttingDown = true; - _pluginManager.UnloadAssemblies(); - - Task.Run(async () => - { - try - { - await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error sending server restart notification"); - } - - Logger.LogInformation("Calling RestartInternal"); - - RestartInternal(); - }); - } - - protected abstract void RestartInternal(); - /// /// Gets the composable part assemblies. /// @@ -1065,30 +1028,6 @@ namespace Emby.Server.Implementations }.ToString().TrimEnd('/'); } - /// - public async Task Shutdown() - { - if (IsShuttingDown) - { - return; - } - - IsShuttingDown = true; - - try - { - await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error sending server shutdown notification"); - } - - ShutdownInternal(); - } - - protected abstract void ShutdownInternal(); - public IEnumerable GetApiPluginAssemblies() { var assemblies = _allConcreteTypes @@ -1152,52 +1091,5 @@ namespace Emby.Server.Implementations _disposed = true; } - - public async ValueTask DisposeAsync() - { - await DisposeAsyncCore().ConfigureAwait(false); - Dispose(false); - GC.SuppressFinalize(this); - } - - /// - /// Used to perform asynchronous cleanup of managed resources or for cascading calls to . - /// - /// A ValueTask. - protected virtual async ValueTask DisposeAsyncCore() - { - var type = GetType(); - - Logger.LogInformation("Disposing {Type}", type.Name); - - foreach (var (part, _) in _disposableParts) - { - var partType = part.GetType(); - if (partType == type) - { - continue; - } - - Logger.LogInformation("Disposing {Type}", partType.Name); - - try - { - part.Dispose(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error disposing {Type}", partType.Name); - } - } - - if (_sessionManager is not null) - { - // used for closing websockets - foreach (var session in _sessionManager.Sessions) - { - await session.DisposeAsync().ConfigureAwait(false); - } - } - } } } diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 1303012e1a..d7189ef0ca 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.IO; using System.Linq; @@ -11,7 +10,6 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common; @@ -30,7 +28,7 @@ namespace Emby.Server.Implementations.Plugins /// /// Defines the . /// - public class PluginManager : IPluginManager + public sealed class PluginManager : IPluginManager, IDisposable { private const string MetafileName = "meta.json"; @@ -191,15 +189,6 @@ namespace Emby.Server.Implementations.Plugins } } - /// - public void UnloadAssemblies() - { - foreach (var assemblyLoadContext in _assemblyLoadContexts) - { - assemblyLoadContext.Unload(); - } - } - /// /// Creates all the plugin instances. /// @@ -441,6 +430,15 @@ namespace Emby.Server.Implementations.Plugins return SaveManifest(manifest, path); } + /// + public void Dispose() + { + foreach (var assemblyLoadContext in _assemblyLoadContexts) + { + assemblyLoadContext.Unload(); + } + } + /// /// Reconciles the manifest against any properties that exist locally in a pre-packaged meta.json found at the path. /// If no file is found, no reconciliation occurs. diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b4a622ccf4..902d46a906 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -36,6 +36,7 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; @@ -44,7 +45,7 @@ namespace Emby.Server.Implementations.Session /// /// Class SessionManager. /// - public class SessionManager : ISessionManager, IDisposable + public sealed class SessionManager : ISessionManager, IAsyncDisposable { private readonly IUserDataManager _userDataManager; private readonly ILogger _logger; @@ -57,11 +58,9 @@ namespace Emby.Server.Implementations.Session private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerApplicationHost _appHost; private readonly IDeviceManager _deviceManager; - - /// - /// The active connections. - /// - private readonly ConcurrentDictionary _activeConnections = new(StringComparer.OrdinalIgnoreCase); + private readonly CancellationTokenRegistration _shutdownCallback; + private readonly ConcurrentDictionary _activeConnections + = new(StringComparer.OrdinalIgnoreCase); private Timer _idleTimer; @@ -79,7 +78,8 @@ namespace Emby.Server.Implementations.Session IImageProcessor imageProcessor, IServerApplicationHost appHost, IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager) + IMediaSourceManager mediaSourceManager, + IHostApplicationLifetime hostApplicationLifetime) { _logger = logger; _eventManager = eventManager; @@ -92,6 +92,7 @@ namespace Emby.Server.Implementations.Session _appHost = appHost; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; + _shutdownCallback = hostApplicationLifetime.ApplicationStopping.Register(OnApplicationStopping); _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } @@ -151,36 +152,6 @@ namespace Emby.Server.Implementations.Session } } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _idleTimer?.Dispose(); - } - - _idleTimer = null; - - _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; - - _disposed = true; - } - private void CheckDisposed() { if (_disposed) @@ -1330,32 +1301,6 @@ namespace Emby.Server.Implementations.Session return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken); } - /// - /// Sends the server shutdown notification. - /// - /// The cancellation token. - /// Task. - public Task SendServerShutdownNotification(CancellationToken cancellationToken) - { - CheckDisposed(); - - return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); - } - - /// - /// Sends the server restart notification. - /// - /// The cancellation token. - /// Task. - public Task SendServerRestartNotification(CancellationToken cancellationToken) - { - CheckDisposed(); - - _logger.LogDebug("Beginning SendServerRestartNotification"); - - return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken); - } - /// /// Adds the additional user. /// @@ -1833,5 +1778,51 @@ namespace Emby.Server.Implementations.Session return SendMessageToSessions(sessions, name, data, cancellationToken); } + + /// + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + foreach (var session in _activeConnections.Values) + { + await session.DisposeAsync().ConfigureAwait(false); + } + + if (_idleTimer is not null) + { + await _idleTimer.DisposeAsync().ConfigureAwait(false); + _idleTimer = null; + } + + await _shutdownCallback.DisposeAsync().ConfigureAwait(false); + + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; + _disposed = true; + } + + private async void OnApplicationStopping() + { + _logger.LogInformation("Sending shutdown notifications"); + try + { + var messageType = _appHost.ShouldRestart ? SessionMessageType.ServerRestarting : SessionMessageType.ServerShuttingDown; + + await SendMessageToSessions(Sessions, messageType, string.Empty, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending server shutdown notifications"); + } + + // Close open websockets to allow Kestrel to shut down cleanly + foreach (var session in _activeConnections.Values) + { + await session.DisposeAsync().ConfigureAwait(false); + } + } } } diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index a29790961e..4cc0f0ced4 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers; @@ -32,6 +33,7 @@ public class SystemController : BaseJellyfinApiController private readonly IFileSystem _fileSystem; private readonly INetworkManager _network; private readonly ILogger _logger; + private readonly IHostApplicationLifetime _hostApplicationLifetime; /// /// Initializes a new instance of the class. @@ -41,18 +43,21 @@ public class SystemController : BaseJellyfinApiController /// Instance of interface. /// Instance of interface. /// Instance of interface. + /// Instance of interface. public SystemController( IServerConfigurationManager serverConfigurationManager, IServerApplicationHost appHost, IFileSystem fileSystem, INetworkManager network, - ILogger logger) + ILogger logger, + IHostApplicationLifetime hostApplicationLifetime) { _appPaths = serverConfigurationManager.ApplicationPaths; _appHost = appHost; _fileSystem = fileSystem; _network = network; _logger = logger; + _hostApplicationLifetime = hostApplicationLifetime; } /// @@ -110,7 +115,9 @@ public class SystemController : BaseJellyfinApiController Task.Run(async () => { await Task.Delay(100).ConfigureAwait(false); - _appHost.Restart(); + _appHost.ShouldRestart = true; + _appHost.IsShuttingDown = true; + _hostApplicationLifetime.StopApplication(); }); return NoContent(); } @@ -130,7 +137,8 @@ public class SystemController : BaseJellyfinApiController Task.Run(async () => { await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); + _appHost.IsShuttingDown = true; + _hostApplicationLifetime.StopApplication(); }); return NoContent(); } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 0c6315c667..4c116745b8 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -102,9 +102,6 @@ namespace Jellyfin.Server base.RegisterServices(serviceCollection); } - /// - protected override void RestartInternal() => Program.Restart(); - /// protected override IEnumerable GetAssembliesWithPartsInternal() { @@ -114,8 +111,5 @@ namespace Jellyfin.Server // Jellyfin.Server.Implementations yield return typeof(JellyfinDbContext).Assembly; } - - /// - protected override void ShutdownInternal() => Program.Shutdown(); } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 3a3dd97bd3..f9259d0d92 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; @@ -42,7 +41,6 @@ namespace Jellyfin.Server public const string LoggingConfigFileSystem = "logging.json"; private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); - private static CancellationTokenSource _tokenSource = new(); private static long _startTimestamp; private static ILogger _logger = NullLogger.Instance; private static bool _restartOnShutdown; @@ -65,27 +63,6 @@ namespace Jellyfin.Server .MapResult(StartApp, ErrorParsingArguments); } - /// - /// Shuts down the application. - /// - internal static void Shutdown() - { - if (!_tokenSource.IsCancellationRequested) - { - _tokenSource.Cancel(); - } - } - - /// - /// Restarts the application. - /// - internal static void Restart() - { - _restartOnShutdown = true; - - Shutdown(); - } - private static async Task StartApp(StartupOptions options) { _startTimestamp = Stopwatch.GetTimestamp(); @@ -110,33 +87,6 @@ namespace Jellyfin.Server AppDomain.CurrentDomain.UnhandledException += (_, e) => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception"); - // Intercept Ctrl+C and Ctrl+Break - Console.CancelKeyPress += (_, e) => - { - if (_tokenSource.IsCancellationRequested) - { - return; // Already shutting down - } - - e.Cancel = true; - _logger.LogInformation("Ctrl+C, shutting down"); - Environment.ExitCode = 128 + 2; - Shutdown(); - }; - - // Register a SIGTERM handler - AppDomain.CurrentDomain.ProcessExit += (_, _) => - { - if (_tokenSource.IsCancellationRequested) - { - return; // Already shutting down - } - - _logger.LogInformation("Received a SIGTERM signal, shutting down"); - Environment.ExitCode = 128 + 15; - Shutdown(); - }; - _logger.LogInformation( "Jellyfin version: {Version}", Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3)); @@ -166,12 +116,10 @@ namespace Jellyfin.Server do { - _restartOnShutdown = false; await StartServer(appPaths, options, startupConfig).ConfigureAwait(false); if (_restartOnShutdown) { - _tokenSource = new CancellationTokenSource(); _startTimestamp = Stopwatch.GetTimestamp(); } } while (_restartOnShutdown); @@ -179,7 +127,7 @@ namespace Jellyfin.Server private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration startupConfig) { - var appHost = new CoreAppHost( + using var appHost = new CoreAppHost( appPaths, _loggerFactory, options, @@ -189,6 +137,7 @@ namespace Jellyfin.Server try { host = Host.CreateDefaultBuilder() + .UseConsoleLifetime() .ConfigureServices(services => appHost.Init(services)) .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger)) .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig)) @@ -203,7 +152,7 @@ namespace Jellyfin.Server try { - await host.StartAsync(_tokenSource.Token).ConfigureAwait(false); + await host.StartAsync().ConfigureAwait(false); if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket()) { @@ -212,22 +161,18 @@ namespace Jellyfin.Server StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger); } } - catch (Exception ex) when (ex is not TaskCanceledException) + catch (Exception) { _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again"); throw; } - await appHost.RunStartupTasksAsync(_tokenSource.Token).ConfigureAwait(false); + await appHost.RunStartupTasksAsync().ConfigureAwait(false); _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp)); - // Block main thread until shutdown - await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Don't throw on cancellation + await host.WaitForShutdownAsync().ConfigureAwait(false); + _restartOnShutdown = appHost.ShouldRestart; } catch (Exception ex) { @@ -250,7 +195,6 @@ namespace Jellyfin.Server } } - await appHost.DisposeAsync().ConfigureAwait(false); host?.Dispose(); } } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 96ee701b38..c1ea6e87cc 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common @@ -42,10 +41,15 @@ namespace MediaBrowser.Common bool HasPendingRestart { get; } /// - /// Gets a value indicating whether this instance is currently shutting down. + /// Gets or sets a value indicating whether this instance is currently shutting down. /// /// true if this instance is shutting down; otherwise, false. - bool IsShuttingDown { get; } + bool IsShuttingDown { get; set; } + + /// + /// Gets or sets a value indicating whether the application should restart. + /// + bool ShouldRestart { get; set; } /// /// Gets the application version. @@ -87,11 +91,6 @@ namespace MediaBrowser.Common /// void NotifyPendingRestart(); - /// - /// Restarts this instance. - /// - void Restart(); - /// /// Gets the exports. /// @@ -123,12 +122,6 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); - /// - /// Shuts down. - /// - /// A task. - Task Shutdown(); - /// /// Initializes this instance. /// diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 1d73de3c95..0ff9719e98 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -29,11 +29,6 @@ namespace MediaBrowser.Common.Plugins /// An IEnumerable{Assembly}. IEnumerable LoadAssemblies(); - /// - /// Unloads all of the assemblies. - /// - void UnloadAssemblies(); - /// /// Registers the plugin's services with the DI. /// Note: DI is not yet instantiated yet. diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 0c4719a0e5..53df7133b5 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -232,20 +232,6 @@ namespace MediaBrowser.Controller.Session /// Task. Task SendRestartRequiredNotification(CancellationToken cancellationToken); - /// - /// Sends the server shutdown notification. - /// - /// The cancellation token. - /// Task. - Task SendServerShutdownNotification(CancellationToken cancellationToken); - - /// - /// Sends the server restart notification. - /// - /// The cancellation token. - /// Task. - Task SendServerRestartNotification(CancellationToken cancellationToken); - /// /// Adds the additional user. /// diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 55bc43455f..1c87d11f18 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Concurrent; using System.Globalization; using System.IO; -using System.Threading; using Emby.Server.Implementations; using Jellyfin.Server.Extensions; using Jellyfin.Server.Helpers; @@ -105,7 +104,7 @@ namespace Jellyfin.Server.Integration.Tests var appHost = (TestAppHost)testServer.Services.GetRequiredService(); appHost.ServiceProvider = testServer.Services; appHost.InitializeServices().GetAwaiter().GetResult(); - appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult(); + appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); return testServer; } -- cgit v1.2.3 From f746db9a54caef36bb3b32cf22812a78d97c865d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 2 Oct 2023 15:55:26 -0400 Subject: Re-add shutdown/restart methods --- Emby.Server.Implementations/ApplicationHost.cs | 19 +++++++++++++++++++ Jellyfin.Api/Controllers/SystemController.cs | 23 +++-------------------- MediaBrowser.Common/IApplicationHost.cs | 18 ++++++++++++++---- 3 files changed, 36 insertions(+), 24 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 86721ace61..e2d2719e5d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -101,6 +101,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; @@ -850,6 +851,24 @@ namespace Emby.Server.Implementations } } + /// + public void Restart() + { + ShouldRestart = true; + Shutdown(); + } + + /// + public void Shutdown() + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + IsShuttingDown = true; + Resolve().StopApplication(); + }); + } + /// /// Gets the composable part assemblies. /// diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 4cc0f0ced4..42ac4a9b4b 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Mime; -using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Common.Configuration; @@ -18,7 +17,6 @@ using MediaBrowser.Model.System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers; @@ -33,7 +31,6 @@ public class SystemController : BaseJellyfinApiController private readonly IFileSystem _fileSystem; private readonly INetworkManager _network; private readonly ILogger _logger; - private readonly IHostApplicationLifetime _hostApplicationLifetime; /// /// Initializes a new instance of the class. @@ -43,21 +40,18 @@ public class SystemController : BaseJellyfinApiController /// Instance of interface. /// Instance of interface. /// Instance of interface. - /// Instance of interface. public SystemController( IServerConfigurationManager serverConfigurationManager, IServerApplicationHost appHost, IFileSystem fileSystem, INetworkManager network, - ILogger logger, - IHostApplicationLifetime hostApplicationLifetime) + ILogger logger) { _appPaths = serverConfigurationManager.ApplicationPaths; _appHost = appHost; _fileSystem = fileSystem; _network = network; _logger = logger; - _hostApplicationLifetime = hostApplicationLifetime; } /// @@ -112,13 +106,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult RestartApplication() { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - _appHost.ShouldRestart = true; - _appHost.IsShuttingDown = true; - _hostApplicationLifetime.StopApplication(); - }); + _appHost.Restart(); return NoContent(); } @@ -134,12 +122,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult ShutdownApplication() { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - _appHost.IsShuttingDown = true; - _hostApplicationLifetime.StopApplication(); - }); + _appHost.Shutdown(); return NoContent(); } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index c1ea6e87cc..5985d3dd82 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -41,15 +41,15 @@ namespace MediaBrowser.Common bool HasPendingRestart { get; } /// - /// Gets or sets a value indicating whether this instance is currently shutting down. + /// Gets a value indicating whether this instance is currently shutting down. /// /// true if this instance is shutting down; otherwise, false. - bool IsShuttingDown { get; set; } + bool IsShuttingDown { get; } /// - /// Gets or sets a value indicating whether the application should restart. + /// Gets a value indicating whether the application should restart. /// - bool ShouldRestart { get; set; } + bool ShouldRestart { get; } /// /// Gets the application version. @@ -91,6 +91,11 @@ namespace MediaBrowser.Common /// void NotifyPendingRestart(); + /// + /// Restarts this instance. + /// + void Restart(); + /// /// Gets the exports. /// @@ -122,6 +127,11 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); + /// + /// Shuts down. + /// + void Shutdown(); + /// /// Initializes this instance. /// -- cgit v1.2.3 From 1ca9f8b04ba93f2c9d33f278beb4e591d84a2f6a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 3 Oct 2023 09:26:20 -0400 Subject: Remove unused fields and parameters --- Jellyfin.Api/Controllers/LiveTvController.cs | 7 +------ Jellyfin.Server.Implementations/Users/UserManager.cs | 10 ++-------- 2 files changed, 3 insertions(+), 14 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 267ba4afb4..649397d68d 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -23,7 +23,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -48,7 +47,6 @@ public class LiveTvController : BaseJellyfinApiController private readonly IMediaSourceManager _mediaSourceManager; private readonly IConfigurationManager _configurationManager; private readonly TranscodingJobHelper _transcodingJobHelper; - private readonly ISessionManager _sessionManager; /// /// Initializes a new instance of the class. @@ -61,7 +59,6 @@ public class LiveTvController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the class. - /// Instance of the interface. public LiveTvController( ILiveTvManager liveTvManager, IUserManager userManager, @@ -70,8 +67,7 @@ public class LiveTvController : BaseJellyfinApiController IDtoService dtoService, IMediaSourceManager mediaSourceManager, IConfigurationManager configurationManager, - TranscodingJobHelper transcodingJobHelper, - ISessionManager sessionManager) + TranscodingJobHelper transcodingJobHelper) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -81,7 +77,6 @@ public class LiveTvController : BaseJellyfinApiController _mediaSourceManager = mediaSourceManager; _configurationManager = configurationManager; _transcodingJobHelper = transcodingJobHelper; - _sessionManager = sessionManager; } /// diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 5010751ddb..94ac4798ca 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -20,7 +20,6 @@ using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Users; using Microsoft.EntityFrameworkCore; @@ -35,7 +34,6 @@ namespace Jellyfin.Server.Implementations.Users { private readonly IDbContextFactory _dbProvider; private readonly IEventManager _eventManager; - private readonly ICryptoProvider _cryptoProvider; private readonly INetworkManager _networkManager; private readonly IApplicationHost _appHost; private readonly IImageProcessor _imageProcessor; @@ -53,7 +51,6 @@ namespace Jellyfin.Server.Implementations.Users /// /// The database provider. /// The event manager. - /// The cryptography provider. /// The network manager. /// The application host. /// The image processor. @@ -61,7 +58,6 @@ namespace Jellyfin.Server.Implementations.Users public UserManager( IDbContextFactory dbProvider, IEventManager eventManager, - ICryptoProvider cryptoProvider, INetworkManager networkManager, IApplicationHost appHost, IImageProcessor imageProcessor, @@ -69,7 +65,6 @@ namespace Jellyfin.Server.Implementations.Users { _dbProvider = dbProvider; _eventManager = eventManager; - _cryptoProvider = cryptoProvider; _networkManager = networkManager; _appHost = appHost; _imageProcessor = imageProcessor; @@ -384,7 +379,7 @@ namespace Jellyfin.Server.Implementations.Users } var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); - var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) + var authResult = await AuthenticateLocalUser(username, password, user) .ConfigureAwait(false); var authenticationProvider = authResult.AuthenticationProvider; var success = authResult.Success; @@ -787,8 +782,7 @@ namespace Jellyfin.Server.Implementations.Users private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> AuthenticateLocalUser( string username, string password, - User? user, - string remoteEndPoint) + User? user) { bool success = false; IAuthenticationProvider? authenticationProvider = null; -- cgit v1.2.3 From 6f7413812fb4654d1b1ca065a177a1bb19408386 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 4 Oct 2023 14:34:53 -0400 Subject: Add SystemManager service --- Emby.Server.Implementations/ApplicationHost.cs | 80 +--------------- Emby.Server.Implementations/SystemManager.cs | 108 ++++++++++++++++++++++ Jellyfin.Api/Controllers/SystemController.cs | 47 +++++----- MediaBrowser.Common/IApplicationHost.cs | 24 +---- MediaBrowser.Controller/IServerApplicationHost.cs | 12 --- MediaBrowser.Controller/ISystemManager.cs | 34 +++++++ 6 files changed, 173 insertions(+), 132 deletions(-) create mode 100644 Emby.Server.Implementations/SystemManager.cs create mode 100644 MediaBrowser.Controller/ISystemManager.cs (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8518b13521..3253ea0267 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -101,7 +101,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; @@ -133,7 +132,7 @@ namespace Emby.Server.Implementations /// All concrete types. private Type[] _allConcreteTypes; - private bool _disposed = false; + private bool _disposed; /// /// Initializes a new instance of the class. @@ -184,26 +183,16 @@ namespace Emby.Server.Implementations public bool CoreStartupHasCompleted { get; private set; } - public virtual bool CanLaunchWebBrowser => Environment.UserInteractive - && !_startupOptions.IsService - && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()); - /// /// Gets the singleton instance. /// public INetworkManager NetManager { get; private set; } - /// - /// Gets a value indicating whether this instance has changes that require the entire application to restart. - /// - /// true if this instance has pending application restart; otherwise, false. - public bool HasPendingRestart { get; private set; } - /// - public bool IsShuttingDown { get; private set; } + public bool HasPendingRestart { get; private set; } /// - public bool ShouldRestart { get; private set; } + public bool ShouldRestart { get; set; } /// /// Gets the logger. @@ -507,6 +496,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddScoped(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(NetManager); @@ -850,24 +841,6 @@ namespace Emby.Server.Implementations } } - /// - public void Restart() - { - ShouldRestart = true; - Shutdown(); - } - - /// - public void Shutdown() - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - IsShuttingDown = true; - Resolve().StopApplication(); - }); - } - /// /// Gets the composable part assemblies. /// @@ -923,49 +896,6 @@ namespace Emby.Server.Implementations protected abstract IEnumerable GetAssembliesWithPartsInternal(); - /// - /// Gets the system status. - /// - /// Where this request originated. - /// SystemInfo. - public SystemInfo GetSystemInfo(HttpRequest request) - { - return new SystemInfo - { - HasPendingRestart = HasPendingRestart, - IsShuttingDown = IsShuttingDown, - Version = ApplicationVersionString, - WebSocketPortNumber = HttpPort, - CompletedInstallations = Resolve().CompletedInstallations.ToArray(), - Id = SystemId, - ProgramDataPath = ApplicationPaths.ProgramDataPath, - WebPath = ApplicationPaths.WebPath, - LogPath = ApplicationPaths.LogDirectoryPath, - ItemsByNamePath = ApplicationPaths.InternalMetadataPath, - InternalMetadataPath = ApplicationPaths.InternalMetadataPath, - CachePath = ApplicationPaths.CachePath, - CanLaunchWebBrowser = CanLaunchWebBrowser, - TranscodingTempPath = ConfigurationManager.GetTranscodePath(), - ServerName = FriendlyName, - LocalAddress = GetSmartApiUrl(request), - SupportsLibraryMonitor = true, - PackageName = _startupOptions.PackageName - }; - } - - public PublicSystemInfo GetPublicSystemInfo(HttpRequest request) - { - return new PublicSystemInfo - { - Version = ApplicationVersionString, - ProductName = ApplicationProductName, - Id = SystemId, - ServerName = FriendlyName, - LocalAddress = GetSmartApiUrl(request), - StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted - }; - } - /// public string GetSmartApiUrl(IPAddress remoteAddr) { diff --git a/Emby.Server.Implementations/SystemManager.cs b/Emby.Server.Implementations/SystemManager.cs new file mode 100644 index 0000000000..5e9c424e9a --- /dev/null +++ b/Emby.Server.Implementations/SystemManager.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; + +namespace Emby.Server.Implementations; + +/// +public class SystemManager : ISystemManager +{ + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly IServerApplicationHost _applicationHost; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IServerConfigurationManager _configurationManager; + private readonly IStartupOptions _startupOptions; + private readonly IInstallationManager _installationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + public SystemManager( + IHostApplicationLifetime applicationLifetime, + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IServerConfigurationManager configurationManager, + IStartupOptions startupOptions, + IInstallationManager installationManager) + { + _applicationLifetime = applicationLifetime; + _applicationHost = applicationHost; + _applicationPaths = applicationPaths; + _configurationManager = configurationManager; + _startupOptions = startupOptions; + _installationManager = installationManager; + } + + private bool CanLaunchWebBrowser => Environment.UserInteractive + && !_startupOptions.IsService + && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()); + + /// + public SystemInfo GetSystemInfo(HttpRequest request) + { + return new SystemInfo + { + HasPendingRestart = _applicationHost.HasPendingRestart, + IsShuttingDown = _applicationLifetime.ApplicationStopping.IsCancellationRequested, + Version = _applicationHost.ApplicationVersionString, + WebSocketPortNumber = _applicationHost.HttpPort, + CompletedInstallations = _installationManager.CompletedInstallations.ToArray(), + Id = _applicationHost.SystemId, + ProgramDataPath = _applicationPaths.ProgramDataPath, + WebPath = _applicationPaths.WebPath, + LogPath = _applicationPaths.LogDirectoryPath, + ItemsByNamePath = _applicationPaths.InternalMetadataPath, + InternalMetadataPath = _applicationPaths.InternalMetadataPath, + CachePath = _applicationPaths.CachePath, + CanLaunchWebBrowser = CanLaunchWebBrowser, + TranscodingTempPath = _configurationManager.GetTranscodePath(), + ServerName = _applicationHost.FriendlyName, + LocalAddress = _applicationHost.GetSmartApiUrl(request), + SupportsLibraryMonitor = true, + PackageName = _startupOptions.PackageName + }; + } + + /// + public PublicSystemInfo GetPublicSystemInfo(HttpRequest request) + { + return new PublicSystemInfo + { + Version = _applicationHost.ApplicationVersionString, + ProductName = _applicationHost.Name, + Id = _applicationHost.SystemId, + ServerName = _applicationHost.FriendlyName, + LocalAddress = _applicationHost.GetSmartApiUrl(request), + StartupWizardCompleted = _configurationManager.CommonConfiguration.IsStartupWizardCompleted + }; + } + + /// + public void Restart() => ShutdownInternal(true); + + /// + public void Shutdown() => ShutdownInternal(false); + + private void ShutdownInternal(bool restart) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + _applicationHost.ShouldRestart = restart; + _applicationLifetime.StopApplication(); + }); + } +} diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 42ac4a9b4b..11095a97f0 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -10,7 +10,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; @@ -26,32 +25,36 @@ namespace Jellyfin.Api.Controllers; /// public class SystemController : BaseJellyfinApiController { + private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly INetworkManager _network; - private readonly ILogger _logger; + private readonly INetworkManager _networkManager; + private readonly ISystemManager _systemManager; /// /// Initializes a new instance of the class. /// - /// Instance of interface. + /// Instance of interface. + /// Instance of interface. /// Instance of interface. /// Instance of interface. - /// Instance of interface. - /// Instance of interface. + /// Instance of interface. + /// Instance of interface. public SystemController( - IServerConfigurationManager serverConfigurationManager, + ILogger logger, IServerApplicationHost appHost, + IServerApplicationPaths appPaths, IFileSystem fileSystem, - INetworkManager network, - ILogger logger) + INetworkManager networkManager, + ISystemManager systemManager) { - _appPaths = serverConfigurationManager.ApplicationPaths; + _logger = logger; _appHost = appHost; + _appPaths = appPaths; _fileSystem = fileSystem; - _network = network; - _logger = logger; + _networkManager = networkManager; + _systemManager = systemManager; } /// @@ -65,9 +68,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult GetSystemInfo() - { - return _appHost.GetSystemInfo(Request); - } + => _systemManager.GetSystemInfo(Request); /// /// Gets public information about the server. @@ -77,9 +78,7 @@ public class SystemController : BaseJellyfinApiController [HttpGet("Info/Public")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetPublicSystemInfo() - { - return _appHost.GetPublicSystemInfo(Request); - } + => _systemManager.GetPublicSystemInfo(Request); /// /// Pings the system. @@ -90,9 +89,7 @@ public class SystemController : BaseJellyfinApiController [HttpPost("Ping", Name = "PostPingSystem")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult PingSystem() - { - return _appHost.Name; - } + => _appHost.Name; /// /// Restarts the application. @@ -106,7 +103,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult RestartApplication() { - _appHost.Restart(); + _systemManager.Restart(); return NoContent(); } @@ -122,7 +119,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult ShutdownApplication() { - _appHost.Shutdown(); + _systemManager.Shutdown(); return NoContent(); } @@ -180,7 +177,7 @@ public class SystemController : BaseJellyfinApiController return new EndPointInfo { IsLocal = HttpContext.IsLocal(), - IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) + IsInNetwork = _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) }; } @@ -218,7 +215,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetWakeOnLanInfo() { - var result = _network.GetMacAddresses() + var result = _networkManager.GetMacAddresses() .Select(i => new WakeOnLanInfo(i)); return Ok(result); } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 5985d3dd82..23795c6be8 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -35,21 +35,15 @@ namespace MediaBrowser.Common string SystemId { get; } /// - /// Gets a value indicating whether this instance has pending kernel reload. + /// Gets a value indicating whether this instance has pending changes requiring a restart. /// - /// true if this instance has pending kernel reload; otherwise, false. + /// true if this instance has a pending restart; otherwise, false. bool HasPendingRestart { get; } /// - /// Gets a value indicating whether this instance is currently shutting down. + /// Gets or sets a value indicating whether the application should restart. /// - /// true if this instance is shutting down; otherwise, false. - bool IsShuttingDown { get; } - - /// - /// Gets a value indicating whether the application should restart. - /// - bool ShouldRestart { get; } + bool ShouldRestart { get; set; } /// /// Gets the application version. @@ -91,11 +85,6 @@ namespace MediaBrowser.Common /// void NotifyPendingRestart(); - /// - /// Restarts this instance. - /// - void Restart(); - /// /// Gets the exports. /// @@ -127,11 +116,6 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); - /// - /// Shuts down. - /// - void Shutdown(); - /// /// Initializes this instance. /// diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 45ac5c3a85..e9c4d9e19a 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -4,7 +4,6 @@ using System.Net; using MediaBrowser.Common; -using MediaBrowser.Model.System; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller @@ -16,8 +15,6 @@ namespace MediaBrowser.Controller { bool CoreStartupHasCompleted { get; } - bool CanLaunchWebBrowser { get; } - /// /// Gets the HTTP server port. /// @@ -41,15 +38,6 @@ namespace MediaBrowser.Controller /// The name of the friendly. string FriendlyName { get; } - /// - /// Gets the system info. - /// - /// The HTTP request. - /// SystemInfo. - SystemInfo GetSystemInfo(HttpRequest request); - - PublicSystemInfo GetPublicSystemInfo(HttpRequest request); - /// /// Gets a URL specific for the request. /// diff --git a/MediaBrowser.Controller/ISystemManager.cs b/MediaBrowser.Controller/ISystemManager.cs new file mode 100644 index 0000000000..ef3034d2f5 --- /dev/null +++ b/MediaBrowser.Controller/ISystemManager.cs @@ -0,0 +1,34 @@ +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Controller; + +/// +/// A service for managing the application instance. +/// +public interface ISystemManager +{ + /// + /// Gets the system info. + /// + /// The HTTP request. + /// The . + SystemInfo GetSystemInfo(HttpRequest request); + + /// + /// Gets the public system info. + /// + /// The HTTP request. + /// The . + PublicSystemInfo GetPublicSystemInfo(HttpRequest request); + + /// + /// Starts the application restart process. + /// + void Restart(); + + /// + /// Starts the application shutdown process. + /// + void Shutdown(); +} -- cgit v1.2.3 From efc4c305a912eb92904289fa4a176db120047fba Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 5 Oct 2023 23:29:31 +0200 Subject: Use CryptoStream to convert stream from base64 Should be way more efficient --- Jellyfin.Api/Controllers/ImageController.cs | 43 ++++++++++++-------------- Jellyfin.Api/Controllers/SubtitleController.cs | 8 ++--- MediaBrowser.Providers/Manager/ImageSaver.cs | 6 +++- 3 files changed, 28 insertions(+), 29 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 3c5f18af55..7b10ea170f 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Mime; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; @@ -78,6 +79,9 @@ public class ImageController : BaseJellyfinApiController _appPaths = appPaths; } + private static Stream GetFromBase64Stream(Stream inputStream) + => new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read); + /// /// Sets the user image. /// @@ -116,8 +120,8 @@ public class ImageController : BaseJellyfinApiController return BadRequest("Incorrect ContentType."); } - var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); - await using (memoryStream.ConfigureAwait(false)) + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) { // Handle image/png; charset=utf-8 var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); @@ -130,7 +134,7 @@ public class ImageController : BaseJellyfinApiController user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension)); await _providerManager - .SaveImage(memoryStream, mimeType, user.ProfileImage.Path) + .SaveImage(stream, mimeType, user.ProfileImage.Path) .ConfigureAwait(false); await _userManager.UpdateUserAsync(user).ConfigureAwait(false); @@ -176,8 +180,8 @@ public class ImageController : BaseJellyfinApiController return BadRequest("Incorrect ContentType."); } - var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); - await using (memoryStream.ConfigureAwait(false)) + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) { // Handle image/png; charset=utf-8 var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); @@ -190,7 +194,7 @@ public class ImageController : BaseJellyfinApiController user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension)); await _providerManager - .SaveImage(memoryStream, mimeType, user.ProfileImage.Path) + .SaveImage(stream, mimeType, user.ProfileImage.Path) .ConfigureAwait(false); await _userManager.UpdateUserAsync(user).ConfigureAwait(false); @@ -372,12 +376,12 @@ public class ImageController : BaseJellyfinApiController return BadRequest("Incorrect ContentType."); } - var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); - await using (memoryStream.ConfigureAwait(false)) + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) { // Handle image/png; charset=utf-8 var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); - await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(item, stream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); return NoContent(); @@ -416,12 +420,12 @@ public class ImageController : BaseJellyfinApiController return BadRequest("Incorrect ContentType."); } - var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); - await using (memoryStream.ConfigureAwait(false)) + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) { // Handle image/png; charset=utf-8 var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); - await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(item, stream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); return NoContent(); @@ -1792,8 +1796,8 @@ public class ImageController : BaseJellyfinApiController return BadRequest("Incorrect ContentType."); } - var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); - await using (memoryStream.ConfigureAwait(false)) + var stream = GetFromBase64Stream(Request.Body); + await using (stream.ConfigureAwait(false)) { var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension); var brandingOptions = _serverConfigurationManager.GetConfiguration("branding"); @@ -1803,7 +1807,7 @@ public class ImageController : BaseJellyfinApiController var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await using (fs.ConfigureAwait(false)) { - await memoryStream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false); + await stream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false); } return NoContent(); @@ -1833,15 +1837,6 @@ public class ImageController : BaseJellyfinApiController return NoContent(); } - private static async Task GetMemoryStream(Stream inputStream) - { - using var reader = new StreamReader(inputStream); - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - - var bytes = Convert.FromBase64String(text); - return new MemoryStream(bytes, 0, bytes.Length, false, true); - } - private ImageInfo? GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex) { int? width = null; diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 7d02550b68..fb89e96108 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Mime; +using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -405,9 +406,8 @@ public class SubtitleController : BaseJellyfinApiController [FromBody, Required] UploadSubtitleDto body) { var video = (Video)_libraryManager.GetItemById(itemId); - var data = Convert.FromBase64String(body.Data); - var memoryStream = new MemoryStream(data, 0, data.Length, false, true); - await using (memoryStream.ConfigureAwait(false)) + var stream = new CryptoStream(Request.Body, new FromBase64Transform(), CryptoStreamMode.Read); + await using (stream.ConfigureAwait(false)) { await _subtitleManager.UploadSubtitle( video, @@ -417,7 +417,7 @@ public class SubtitleController : BaseJellyfinApiController Language = body.Language, IsForced = body.IsForced, IsHearingImpaired = body.IsHearingImpaired, - Stream = memoryStream + Stream = stream }).ConfigureAwait(false); _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index e7c2cd2558..d827168314 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -263,7 +263,11 @@ namespace MediaBrowser.Providers.Manager var fileStreamOptions = AsyncFile.WriteOptions; fileStreamOptions.Mode = FileMode.Create; - fileStreamOptions.PreallocationSize = source.Length; + if (source.CanSeek) + { + fileStreamOptions.PreallocationSize = source.Length; + } + var fs = new FileStream(path, fileStreamOptions); await using (fs.ConfigureAwait(false)) { -- cgit v1.2.3 From b176beb88e22a36cc056439ac2a4df4fbe68f2c1 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 6 Oct 2023 00:40:09 +0200 Subject: Reduce string allocations Some simple changes to reduce the number of allocated strings --- Emby.Dlna/DlnaManager.cs | 2 +- Emby.Naming/ExternalFiles/ExternalPathParser.cs | 2 +- Emby.Naming/Video/StubResolver.cs | 7 ++--- Emby.Photos/PhotoProvider.cs | 2 +- .../IO/ManagedFileSystem.cs | 6 ++-- .../Library/LibraryManager.cs | 4 +-- .../Library/Resolvers/Audio/AudioResolver.cs | 6 ++-- .../Library/Resolvers/BaseVideoResolver.cs | 2 +- .../Library/Resolvers/Books/BookResolver.cs | 9 +++--- .../MediaEncoder/EncodingManager.cs | 2 +- .../Playlists/PlaylistManager.cs | 16 ++++------ .../Updates/InstallationManager.cs | 3 +- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 +-- Jellyfin.Api/Controllers/HlsSegmentController.cs | 9 +++--- Jellyfin.Api/Helpers/StreamingHelpers.cs | 10 +++--- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 18 +++++------ .../Attachments/AttachmentExtractor.cs | 36 +++------------------- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 ++-- src/Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 +- src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 12 ++++---- 21 files changed, 63 insertions(+), 97 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 99b3e6e7ef..d67cb67b54 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -228,7 +228,7 @@ namespace Emby.Dlna try { return _fileSystem.GetFilePaths(path) - .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase)) + .Where(i => Path.GetExtension(i.AsSpan()).Equals(".xml", StringComparison.OrdinalIgnoreCase)) .Select(i => ParseProfileFile(i, type)) .Where(i => i is not null) .ToList()!; // We just filtered out all the nulls diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs index 9531296711..4080ba10d3 100644 --- a/Emby.Naming/ExternalFiles/ExternalPathParser.cs +++ b/Emby.Naming/ExternalFiles/ExternalPathParser.cs @@ -43,7 +43,7 @@ namespace Emby.Naming.ExternalFiles return null; } - var extension = Path.GetExtension(path); + var extension = Path.GetExtension(path.AsSpan()); if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) && !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))) { diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index f7ba606e3e..4b9df19b08 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -26,19 +26,18 @@ namespace Emby.Naming.Video return false; } - var extension = Path.GetExtension(path); + var extension = Path.GetExtension(path.AsSpan()); if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return false; } - path = Path.GetFileNameWithoutExtension(path); - var token = Path.GetExtension(path).TrimStart('.'); + var token = Path.GetExtension(Path.GetFileNameWithoutExtension(path.AsSpan())).TrimStart('.'); foreach (var rule in options.StubTypes) { - if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase)) + if (token.Equals(rule.Token, StringComparison.OrdinalIgnoreCase)) { stubType = rule.StubType; return true; diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index f54066c57f..27329a7f2f 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -61,7 +61,7 @@ namespace Emby.Photos item.SetImagePath(ImageType.Primary, item.Path); // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs - if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase)) + if (_includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase)) { try { diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 18b00ce0b2..4178936ce2 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -103,15 +103,17 @@ namespace Emby.Server.Implementations.IO return filePath; } + var filePathSpan = filePath.AsSpan(); + // relative path if (firstChar == '\\') { - filePath = filePath.Substring(1); + filePathSpan = filePathSpan.Slice(1); } try { - return Path.GetFullPath(Path.Combine(folderPath, filePath)); + return Path.GetFullPath(Path.Join(folderPath, filePathSpan)); } catch (ArgumentException) { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index b0a4a4151a..845e7e3512 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1162,7 +1162,7 @@ namespace Emby.Server.Implementations.Library Name = Path.GetFileName(dir), Locations = _fileSystem.GetFilePaths(dir, false) - .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) + .Where(i => Path.GetExtension(i.AsSpan()).Equals(ShortcutFileExtension, StringComparison.OrdinalIgnoreCase)) .Select(i => { try @@ -3135,7 +3135,7 @@ namespace Emby.Server.Implementations.Library } var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true) - .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) + .Where(i => Path.GetExtension(i.AsSpan()).Equals(ShortcutFileExtension, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(f => _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(f)).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(shortcut)) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index a74f824752..862f144e68 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -94,9 +94,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (AudioFileParser.IsAudioFile(args.Path, _namingOptions)) { - var extension = Path.GetExtension(args.Path); + var extension = Path.GetExtension(args.Path.AsSpan()); - if (string.Equals(extension, ".cue", StringComparison.OrdinalIgnoreCase)) + if (extension.Equals(".cue", StringComparison.OrdinalIgnoreCase)) { // if audio file exists of same name, return null return null; @@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (item is not null) { - item.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); + item.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase); item.IsInMixedFolder = true; } diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 381796d0e3..779cfd5be4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -263,7 +263,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - return directoryService.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase)); + return directoryService.GetFilePaths(fullPath).Any(i => Path.GetExtension(i.AsSpan()).Equals(".vob", StringComparison.OrdinalIgnoreCase)); } /// diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 042422c6f4..73861ff599 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -32,9 +32,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books return GetBook(args); } - var extension = Path.GetExtension(args.Path); + var extension = Path.GetExtension(args.Path.AsSpan()); - if (extension is not null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + if (_validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's a book return new Book @@ -51,12 +51,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books { var bookFiles = args.FileSystemChildren.Where(f => { - var fileExtension = Path.GetExtension(f.FullName) - ?? string.Empty; + var fileExtension = Path.GetExtension(f.FullName.AsSpan()); return _validExtensions.Contains( fileExtension, - StringComparer.OrdinalIgnoreCase); + StringComparison.OrdinalIgnoreCase); }).ToList(); // Don't return a Book if there is more (or less) than one document in the directory diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 7732e32d0a..896f47923f 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -222,7 +222,7 @@ namespace Emby.Server.Implementations.MediaEncoder { var deadImages = images .Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase) - .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) + .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i.AsSpan()), StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (var image in deadImages) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 0cb450cdf3..649c499247 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -327,9 +327,9 @@ namespace Emby.Server.Implementations.Playlists // this is probably best done as a metadata provider // saving a file over itself will require some work to prevent this from happening when not needed var playlistPath = item.Path; - var extension = Path.GetExtension(playlistPath); + var extension = Path.GetExtension(playlistPath.AsSpan()); - if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase)) + if (extension.Equals(".wpl", StringComparison.OrdinalIgnoreCase)) { var playlist = new WplPlaylist(); foreach (var child in item.GetLinkedChildren()) @@ -362,8 +362,7 @@ namespace Emby.Server.Implementations.Playlists string text = new WplContent().ToText(playlist); File.WriteAllText(playlistPath, text); } - - if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(".zpl", StringComparison.OrdinalIgnoreCase)) { var playlist = new ZplPlaylist(); foreach (var child in item.GetLinkedChildren()) @@ -396,8 +395,7 @@ namespace Emby.Server.Implementations.Playlists string text = new ZplContent().ToText(playlist); File.WriteAllText(playlistPath, text); } - - if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(".m3u", StringComparison.OrdinalIgnoreCase)) { var playlist = new M3uPlaylist { @@ -428,8 +426,7 @@ namespace Emby.Server.Implementations.Playlists string text = new M3uContent().ToText(playlist); File.WriteAllText(playlistPath, text); } - - if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(".m3u8", StringComparison.OrdinalIgnoreCase)) { var playlist = new M3uPlaylist(); playlist.IsExtended = true; @@ -458,8 +455,7 @@ namespace Emby.Server.Implementations.Playlists string text = new M3uContent().ToText(playlist); File.WriteAllText(playlistPath, text); } - - if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(".pls", StringComparison.OrdinalIgnoreCase)) { var playlist = new PlsPlaylist(); foreach (var child in item.GetLinkedChildren()) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 6c198b6f99..77d385ba1c 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -504,8 +504,7 @@ namespace Emby.Server.Implementations.Updates private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken) { - var extension = Path.GetExtension(package.SourceUrl); - if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) + if (!Path.GetExtension(package.SourceUrl.AsSpan()).Equals(".zip", StringComparison.OrdinalIgnoreCase)) { _logger.LogError("Only zip packages are supported. {SourceUrl} is not a zip archive.", package.SourceUrl); return; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 065a4ce5c6..4026a38f8d 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -2041,9 +2041,9 @@ public class DynamicHlsController : BaseJellyfinApiController return null; } - var playlistFilename = Path.GetFileNameWithoutExtension(playlist); + var playlistFilename = Path.GetFileNameWithoutExtension(playlist.AsSpan()); - var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length); + var indexString = Path.GetFileNameWithoutExtension(file.Name.AsSpan()).Slice(playlistFilename.Length); return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture); } diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index d7cec865e1..6eedfd8c7f 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -59,7 +59,7 @@ public class HlsSegmentController : BaseJellyfinApiController public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string segmentId) { // TODO: Deprecate with new iOS app - var file = segmentId + Path.GetExtension(Request.Path); + var file = string.Concat(segmentId, Path.GetExtension(Request.Path.Value.AsSpan())); var transcodePath = _serverConfigurationManager.GetTranscodePath(); file = Path.GetFullPath(Path.Combine(transcodePath, file)); var fileDir = Path.GetDirectoryName(file); @@ -85,11 +85,12 @@ public class HlsSegmentController : BaseJellyfinApiController [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId) { - var file = playlistId + Path.GetExtension(Request.Path); + var file = string.Concat(playlistId, Path.GetExtension(Request.Path.Value.AsSpan())); var transcodePath = _serverConfigurationManager.GetTranscodePath(); file = Path.GetFullPath(Path.Combine(transcodePath, file)); var fileDir = Path.GetDirectoryName(file); - if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture) || Path.GetExtension(file) != ".m3u8") + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture) + || Path.GetExtension(file.AsSpan()).Equals(".m3u8", StringComparison.OrdinalIgnoreCase)) { return BadRequest("Invalid segment."); } @@ -138,7 +139,7 @@ public class HlsSegmentController : BaseJellyfinApiController [FromRoute, Required] string segmentId, [FromRoute, Required] string segmentContainer) { - var file = segmentId + Path.GetExtension(Request.Path); + var file = string.Concat(segmentId, Path.GetExtension(Request.Path.Value.AsSpan())); var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file)); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 782cd65685..d15fe4fab2 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -243,7 +243,7 @@ public static class StreamingHelpers ? GetOutputFileExtension(state, mediaSource) : ("." + state.OutputContainer); - state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); + state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); return state; } @@ -418,11 +418,11 @@ public static class StreamingHelpers /// System.String. private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource) { - var ext = Path.GetExtension(state.RequestedUrl); + var ext = Path.GetExtension(state.RequestedUrl.AsSpan()); - if (!string.IsNullOrEmpty(ext)) + if (ext.IsEmpty) { - return ext; + return null; } // Try to infer based on the desired video codec @@ -504,7 +504,7 @@ public static class StreamingHelpers /// The device id. /// The play session id. /// The complete file path, including the folder, for the transcoding file. - private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId) + private static string GetOutputFilePath(StreamState state, string? outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId) { var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}"; diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 73ebb396d0..c16a586d60 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -538,7 +538,7 @@ public class TranscodingJobHelper : IDisposable await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); } - if (state.SubtitleStream.IsExternal && string.Equals(Path.GetExtension(state.SubtitleStream.Path), ".mks", StringComparison.OrdinalIgnoreCase)) + 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)); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c311d3b8ab..3be7a4252b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -548,25 +548,25 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.Nullable{VideoCodecs}. public string InferVideoCodec(string url) { - var ext = Path.GetExtension(url); + var ext = Path.GetExtension(url.AsSpan()); - if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase)) { return "wmv"; } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase)) { // TODO: this may not always mean VP8, as the codec ages return "vp8"; } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgnoreCase)) { return "theora"; } - if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgnoreCase)) { return "h264"; } @@ -1080,10 +1080,10 @@ namespace MediaBrowser.Controller.MediaEncoding && state.SubtitleStream.IsExternal) { var subtitlePath = state.SubtitleStream.Path; - var subtitleExtension = Path.GetExtension(subtitlePath); + var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan()); - if (string.Equals(subtitleExtension, ".sub", StringComparison.OrdinalIgnoreCase) - || string.Equals(subtitleExtension, ".sup", StringComparison.OrdinalIgnoreCase)) + if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase) + || subtitleExtension.Equals(".sup", StringComparison.OrdinalIgnoreCase)) { var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); if (File.Exists(idxFile)) @@ -6033,7 +6033,7 @@ namespace MediaBrowser.Controller.MediaEncoding var format = string.Empty; var keyFrame = string.Empty; - if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) + if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase) && state.BaseRequest.Context == EncodingContext.Streaming) { // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 989e386a51..0ec0c84d41 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -23,7 +22,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Attachments { - public class AttachmentExtractor : IAttachmentExtractor, IDisposable + public sealed class AttachmentExtractor : IAttachmentExtractor { private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; @@ -34,8 +33,6 @@ namespace MediaBrowser.MediaEncoding.Attachments private readonly ConcurrentDictionary _semaphoreLocks = new ConcurrentDictionary(); - private bool _disposed = false; - public AttachmentExtractor( ILogger logger, IApplicationPaths appPaths, @@ -296,7 +293,7 @@ namespace MediaBrowser.MediaEncoding.Attachments ArgumentException.ThrowIfNullOrEmpty(outputPath); - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputPath))); var processArgs = string.Format( CultureInfo.InvariantCulture, @@ -391,33 +388,8 @@ namespace MediaBrowser.MediaEncoding.Attachments filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D", CultureInfo.InvariantCulture); } - var prefix = filename.Substring(0, 1); - return Path.Combine(_appPaths.DataPath, "attachments", prefix, filename); - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - } - - _disposed = true; + var prefix = filename.AsSpan(0, 1); + return Path.Join(_appPaths.DataPath, "attachments", prefix, filename); } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 346e97ae12..59c3505616 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -316,10 +316,8 @@ namespace MediaBrowser.MediaEncoding.Encoder { var files = _fileSystem.GetFilePaths(path, recursive); - var excludeExtensions = new[] { ".c" }; - - return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), filename, StringComparison.OrdinalIgnoreCase) - && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + return files.FirstOrDefault(i => Path.GetFileNameWithoutExtension(i.AsSpan()).Equals(filename, StringComparison.OrdinalIgnoreCase) + && !Path.GetExtension(i.AsSpan()).Equals(".c", StringComparison.OrdinalIgnoreCase)); } catch (Exception) { diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 126c0503e0..416f602179 100644 --- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -188,7 +188,7 @@ public class SkiaEncoder : IImageEncoder return path; } - var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path)); + var tempPath = Path.Combine(_appPaths.TempDirectory, string.Concat(Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid."); Directory.CreateDirectory(directory); File.Copy(path, tempPath, true); diff --git a/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 6dff7aa9b4..4aff26c16b 100644 --- a/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -38,25 +38,25 @@ public partial class StripCollageBuilder { ArgumentNullException.ThrowIfNull(outputPath); - var ext = Path.GetExtension(outputPath); + var ext = Path.GetExtension(outputPath.AsSpan()); - if (string.Equals(ext, ".jpg", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".jpeg", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".jpg", StringComparison.OrdinalIgnoreCase) + || ext.Equals(".jpeg", StringComparison.OrdinalIgnoreCase)) { return SKEncodedImageFormat.Jpeg; } - if (string.Equals(ext, ".webp", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".webp", StringComparison.OrdinalIgnoreCase)) { return SKEncodedImageFormat.Webp; } - if (string.Equals(ext, ".gif", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".gif", StringComparison.OrdinalIgnoreCase)) { return SKEncodedImageFormat.Gif; } - if (string.Equals(ext, ".bmp", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".bmp", StringComparison.OrdinalIgnoreCase)) { return SKEncodedImageFormat.Bmp; } -- cgit v1.2.3 From 47254d6a2236e079af3cc8c2e37c77d9d1479235 Mon Sep 17 00:00:00 2001 From: Stepan Goremykin Date: Sun, 8 Oct 2023 00:46:15 +0200 Subject: Remove conditional access when it is known to be not null --- Emby.Server.Implementations/Net/SocketFactory.cs | 6 +++--- Jellyfin.Api/Controllers/LibraryController.cs | 6 +++--- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 3091398b7b..8478fbc4ac 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Net } catch { - socket?.Dispose(); + socket.Dispose(); throw; } @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Net } catch { - socket?.Dispose(); + socket.Dispose(); throw; } @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Net } catch { - socket?.Dispose(); + socket.Dispose(); throw; } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 46c0a8d527..21941ff942 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -294,8 +294,8 @@ public class LibraryController : BaseJellyfinApiController return new AllThemeMediaResult { - ThemeSongsResult = themeSongs?.Value, - ThemeVideosResult = themeVideos?.Value, + ThemeSongsResult = themeSongs.Value, + ThemeVideosResult = themeVideos.Value, SoundtrackSongsResult = new ThemeMediaResult() }; } @@ -490,7 +490,7 @@ public class LibraryController : BaseJellyfinApiController baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user)); - parent = parent?.GetParent(); + parent = parent.GetParent(); } return baseItemDtos; diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 889e2494a0..666e787951 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1313,7 +1313,7 @@ namespace MediaBrowser.Model.Dlna var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream)); var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions); - if (audioStream?.IsExternal == true) + if (audioStream.IsExternal == true) { audioStreamFailureReasons |= TranscodeReason.AudioIsExternal; } -- cgit v1.2.3 From aa073748c00fe2b0508fde88d900143acec09e36 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Mon, 9 Oct 2023 23:12:41 +0800 Subject: Drop experimental status of flac-in-MP4 for FFmpeg 6+ Signed-off-by: nyanmisaka --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 760fefbf51..44afdb6b11 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -45,6 +45,8 @@ public class DynamicHlsController : BaseJellyfinApiController private const string DefaultEventEncoderPreset = "superfast"; private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls; + private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0); + private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; @@ -1705,13 +1707,14 @@ public class DynamicHlsController : BaseJellyfinApiController var audioCodec = _encodingHelper.GetAudioEncoder(state); var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); - // dts, flac, opus and truehd are experimental in mp4 muxer + // opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer var strictArgs = string.Empty; var actualOutputAudioCodec = state.ActualOutputAudioCodec; - if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) + if (string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) - || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) + || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase) + || (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion < _minFFmpegFlacInMp4)) { strictArgs = " -strict -2"; } -- cgit v1.2.3 From 74f61fbd79ef2e2ad4a986f5f886581b2827de07 Mon Sep 17 00:00:00 2001 From: lonebyte <61915324+lonebyte@users.noreply.github.com> Date: Tue, 10 Oct 2023 22:48:52 +0200 Subject: Fix HLS playback of m4a files with mjpeg stream (#10069) --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 7bf366e5d0..42c94c29d3 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1721,14 +1721,17 @@ public class DynamicHlsController : BaseJellyfinApiController if (!state.IsOutputVideo) { + var audioTranscodeParams = string.Empty; + + // -vn to drop any video streams + audioTranscodeParams += "-vn"; + if (EncodingHelper.IsCopyCodec(audioCodec)) { - return "-acodec copy" + bitStreamArgs + strictArgs; + return audioTranscodeParams + " -acodec copy" + bitStreamArgs + strictArgs; } - var audioTranscodeParams = string.Empty; - - audioTranscodeParams += "-acodec " + audioCodec + bitStreamArgs + strictArgs; + audioTranscodeParams += " -acodec " + audioCodec + bitStreamArgs + strictArgs; var audioBitrate = state.OutputAudioBitrate; var audioChannels = state.OutputAudioChannels; @@ -1756,7 +1759,6 @@ public class DynamicHlsController : BaseJellyfinApiController audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } - audioTranscodeParams += " -vn"; return audioTranscodeParams; } -- cgit v1.2.3 From effc3d488c2e0df04189b18b8e47905b1f641f24 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 11 Oct 2023 11:05:14 -0400 Subject: Use DI for ContentDirectoryService --- .../Extensions/DlnaServiceCollectionExtensions.cs | 2 ++ Emby.Dlna/Main/DlnaEntryPoint.cs | 23 +--------------------- Jellyfin.Api/Controllers/DlnaServerController.cs | 5 +++-- 3 files changed, 6 insertions(+), 24 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs index 3a47acf10a..c28a5618ce 100644 --- a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs +++ b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Net; using System.Net.Http; using System.Text; +using Emby.Dlna.ContentDirectory; using Emby.Dlna.Ssdp; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -48,5 +49,6 @@ public static class DlnaServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 83b0ef3164..b2d2f36986 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -23,7 +23,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.TV; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; @@ -76,9 +75,7 @@ namespace Emby.Dlna.Main IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, - INetworkManager networkManager, - IUserViewManager userViewManager, - ITVSeriesManager tvSeriesManager) + INetworkManager networkManager) { _config = config; _appHost = appHost; @@ -97,21 +94,6 @@ namespace Emby.Dlna.Main _networkManager = networkManager; _logger = loggerFactory.CreateLogger(); - ContentDirectory = new ContentDirectory.ContentDirectoryService( - dlnaManager, - userDataManager, - imageProcessor, - libraryManager, - config, - userManager, - loggerFactory.CreateLogger(), - httpClientFactory, - localizationManager, - mediaSourceManager, - userViewManager, - mediaEncoder, - tvSeriesManager); - ConnectionManager = new ConnectionManager.ConnectionManagerService( dlnaManager, config, @@ -140,8 +122,6 @@ namespace Emby.Dlna.Main /// public static bool Enabled { get; private set; } - public IContentDirectory ContentDirectory { get; private set; } - public IConnectionManager ConnectionManager { get; private set; } public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } @@ -453,7 +433,6 @@ namespace Emby.Dlna.Main _communicationsServer = null; } - ContentDirectory = null; ConnectionManager = null; MediaReceiverRegistrar = null; Current = null; diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 95b296fae9..178ba6d6e1 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -33,10 +33,11 @@ public class DlnaServerController : BaseJellyfinApiController /// Initializes a new instance of the class. /// /// Instance of the interface. - public DlnaServerController(IDlnaManager dlnaManager) + /// Instance of the interface. + public DlnaServerController(IDlnaManager dlnaManager, IContentDirectory contentDirectory) { _dlnaManager = dlnaManager; - _contentDirectory = DlnaEntryPoint.Current.ContentDirectory; + _contentDirectory = contentDirectory; _connectionManager = DlnaEntryPoint.Current.ConnectionManager; _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar; } -- cgit v1.2.3 From e0b089a37573a02898e179e738d44039df9432bf Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 11 Oct 2023 11:08:19 -0400 Subject: Use DI for ConnectionManagerService --- Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs | 2 ++ Emby.Dlna/Main/DlnaEntryPoint.cs | 9 --------- Jellyfin.Api/Controllers/DlnaServerController.cs | 8 ++++++-- 3 files changed, 8 insertions(+), 11 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs index c28a5618ce..f8ed00aeaf 100644 --- a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs +++ b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Net; using System.Net.Http; using System.Text; +using Emby.Dlna.ConnectionManager; using Emby.Dlna.ContentDirectory; using Emby.Dlna.Ssdp; using MediaBrowser.Common.Net; @@ -50,5 +51,6 @@ public static class DlnaServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index b2d2f36986..a22143ae7b 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -94,12 +94,6 @@ namespace Emby.Dlna.Main _networkManager = networkManager; _logger = loggerFactory.CreateLogger(); - ConnectionManager = new ConnectionManager.ConnectionManagerService( - dlnaManager, - config, - loggerFactory.CreateLogger(), - httpClientFactory); - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService( loggerFactory.CreateLogger(), httpClientFactory, @@ -122,8 +116,6 @@ namespace Emby.Dlna.Main /// public static bool Enabled { get; private set; } - public IConnectionManager ConnectionManager { get; private set; } - public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } public async Task RunAsync() @@ -433,7 +425,6 @@ namespace Emby.Dlna.Main _communicationsServer = null; } - ConnectionManager = null; MediaReceiverRegistrar = null; Current = null; diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 178ba6d6e1..2d52097c04 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -34,11 +34,15 @@ public class DlnaServerController : BaseJellyfinApiController /// /// Instance of the interface. /// Instance of the interface. - public DlnaServerController(IDlnaManager dlnaManager, IContentDirectory contentDirectory) + /// Instance of the interface. + public DlnaServerController( + IDlnaManager dlnaManager, + IContentDirectory contentDirectory, + IConnectionManager connectionManager) { _dlnaManager = dlnaManager; _contentDirectory = contentDirectory; - _connectionManager = DlnaEntryPoint.Current.ConnectionManager; + _connectionManager = connectionManager; _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar; } -- cgit v1.2.3 From 010cf2340aca8f21d90fd9c8c8653b9b8d7208b2 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 11 Oct 2023 11:12:33 -0400 Subject: Use DI for MediaReceiverRegistrarService --- Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs | 2 ++ Emby.Dlna/Main/DlnaEntryPoint.cs | 13 ------------- Jellyfin.Api/Controllers/DlnaServerController.cs | 7 ++++--- 3 files changed, 6 insertions(+), 16 deletions(-) (limited to 'Jellyfin.Api/Controllers') diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs index f8ed00aeaf..8361cc7e78 100644 --- a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs +++ b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Text; using Emby.Dlna.ConnectionManager; using Emby.Dlna.ContentDirectory; +using Emby.Dlna.MediaReceiverRegistrar; using Emby.Dlna.Ssdp; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -52,5 +53,6 @@ public static class DlnaServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index a22143ae7b..ea996a4424 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -94,12 +94,6 @@ namespace Emby.Dlna.Main _networkManager = networkManager; _logger = loggerFactory.CreateLogger(); - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService( - loggerFactory.CreateLogger(), - httpClientFactory, - config); - Current = this; - var netConfig = config.GetConfiguration(NetworkConfigurationStore.StoreKey); _disabled = appHost.ListenWithHttps && netConfig.RequireHttps; @@ -109,15 +103,11 @@ namespace Emby.Dlna.Main } } - public static DlnaEntryPoint Current { get; private set; } - /// /// Gets a value indicating whether the dlna server is enabled. /// public static bool Enabled { get; private set; } - public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } - public async Task RunAsync() { await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); @@ -425,9 +415,6 @@ namespace Emby.Dlna.Main _communicationsServer = null; } - MediaReceiverRegistrar = null; - Current = null; - _disposed = true; } } diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 2d52097c04..42576934b3 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -5,7 +5,6 @@ using System.IO; using System.Net.Mime; using System.Threading.Tasks; using Emby.Dlna; -using Emby.Dlna.Main; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Controller.Dlna; @@ -35,15 +34,17 @@ public class DlnaServerController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public DlnaServerController( IDlnaManager dlnaManager, IContentDirectory contentDirectory, - IConnectionManager connectionManager) + IConnectionManager connectionManager, + IMediaReceiverRegistrar mediaReceiverRegistrar) { _dlnaManager = dlnaManager; _contentDirectory = contentDirectory; _connectionManager = connectionManager; - _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar; + _mediaReceiverRegistrar = mediaReceiverRegistrar; } /// -- cgit v1.2.3