diff options
Diffstat (limited to 'Jellyfin.Api/Controllers')
| -rw-r--r-- | Jellyfin.Api/Controllers/DynamicHlsController.cs | 36 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ItemLookupController.cs | 3 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ItemUpdateController.cs | 10 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/UserLibraryController.cs | 37 |
4 files changed, 47 insertions, 39 deletions
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 4aa728b5bf..034a9dea55 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -20,7 +20,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Streaming; -using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; @@ -1456,22 +1455,16 @@ public class DynamicHlsController : BaseJellyfinApiController var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); - TranscodingJob? job; - - if (System.IO.File.Exists(segmentPath)) - { - job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); - } - + // Keep segment selection and transcoding replacement under the same playlist lock. + // An out-of-order request must not replace a job while another request is using its output. using (await _transcodeManager.LockAsync(playlistPath, cancellationToken).ConfigureAwait(false)) { + TranscodingJob? job; var startTranscoding = false; if (System.IO.File.Exists(segmentPath)) { job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - _logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); + _logger.LogDebug("returning {0} [it exists]", segmentPath); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } @@ -1505,6 +1498,9 @@ public class DynamicHlsController : BaseJellyfinApiController // If the playlist doesn't already exist, startup ffmpeg try { + var currentJob = _transcodeManager.GetTranscodingJob(playlistPath, TranscodingJobType); + await WaitForActiveTranscodingRequests(currentJob, cancellationToken).ConfigureAwait(false); + await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false) .ConfigureAwait(false); @@ -1540,11 +1536,19 @@ public class DynamicHlsController : BaseJellyfinApiController await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false); } } + + _logger.LogDebug("returning {0} [general case]", segmentPath); + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } + } - _logger.LogDebug("returning {0} [general case]", segmentPath); - job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); + internal static async Task WaitForActiveTranscodingRequests(TranscodingJob? job, CancellationToken cancellationToken) + { + while (job?.ActiveRequestCount > 0) + { + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } } private static double[] GetSegmentLengths(StreamState state) @@ -1647,9 +1651,9 @@ public class DynamicHlsController : BaseJellyfinApiController segmentFormat, startNumber.ToString(CultureInfo.InvariantCulture), baseUrlParam, - EncodingUtils.NormalizePath(outputTsArg), + outputTsArg.EscapeProcessArgument(), hlsArguments, - EncodingUtils.NormalizePath(outputPath)).Trim(); + outputPath.EscapeProcessArgument()).Trim(); } /// <summary> diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index d009f80a96..39ba5ab186 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Authorization; @@ -263,7 +264,7 @@ public class ItemLookupController : BaseJellyfinApiController searchResult.ProviderIds); // Since the refresh process won't erase provider Ids, we need to set this explicitly now. - item.ProviderIds = searchResult.ProviderIds; + item.SetProviderIds(searchResult.ProviderIds); await _providerManager.RefreshFullItem( item, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 36c82cf461..65fffc4181 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -428,15 +428,7 @@ public class ItemUpdateController : BaseJellyfinApiController if (request.ProviderIds is not null) { - foreach (var pair in request.ProviderIds.ToList()) - { - if (string.IsNullOrEmpty(pair.Value)) - { - request.ProviderIds.Remove(pair.Key); - } - } - - item.ProviderIds = request.ProviderIds; + item.SetProviderIds(request.ProviderIds); } if (item is Video video) diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index ea134a4619..da03032249 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -34,6 +34,8 @@ namespace Jellyfin.Api.Controllers; [Tags("Library")] public class UserLibraryController : BaseJellyfinApiController { + private static readonly TimeSpan RefreshOnDemandTimeout = TimeSpan.FromSeconds(3); + private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; private readonly ILibraryManager _libraryManager; @@ -79,7 +81,7 @@ public class UserLibraryController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the item.</returns> [HttpGet("Items/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<BaseItemDto> GetItem( + public async Task<ActionResult<BaseItemDto>> GetItem( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId) { @@ -98,7 +100,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - QueueRefreshOnDemandIfNeeded(item); + await RefreshOnDemandIfNeeded(item).ConfigureAwait(false); var dtoOptions = new DtoOptions(); @@ -116,7 +118,7 @@ public class UserLibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult<BaseItemDto> GetItemLegacy( + public Task<ActionResult<BaseItemDto>> GetItemLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) => GetItem(userId, itemId); @@ -643,7 +645,7 @@ public class UserLibraryController : BaseJellyfinApiController limit, groupItems); - private void QueueRefreshOnDemandIfNeeded(BaseItem item) + private async Task RefreshOnDemandIfNeeded(BaseItem item) { if (item is not Person) { @@ -656,15 +658,24 @@ public class UserLibraryController : BaseJellyfinApiController return; } - _providerManager.QueueRefresh( - item.Id, - new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - ForceSave = true - }, - RefreshPriority.High); + var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ForceSave = true + }; + + using var timeout = CancellationTokenSource.CreateLinkedTokenSource(HttpContext.RequestAborted); + timeout.CancelAfter(RefreshOnDemandTimeout); + + try + { + await item.RefreshMetadata(options, timeout.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) when (!HttpContext.RequestAborted.IsCancellationRequested) + { + _providerManager.QueueRefresh(item.Id, options, RefreshPriority.High); + } } /// <summary> |
