aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs41
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs10
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs3
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs16
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs1
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs52
7 files changed, 80 insertions, 46 deletions
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 838f48949d..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)
@@ -1607,8 +1611,9 @@ public class DynamicHlsController : BaseJellyfinApiController
if (state.VideoStream is not null && state.IsOutputVideo)
{
- // fMP4 needs this flag to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT
- hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont";
+ // fMP4 needs frag_discont to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT
+ // HLS does not use SIDX, and skipping it avoids FFmpeg rewriting open-GOP boundary packet PTS
+ hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont+skip_sidx";
}
segmentFormat = "fmp4" + outputFmp4HeaderArg;
@@ -1646,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/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index ac7c091f85..aa942e7642 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -84,7 +84,7 @@ public class MediaInfoController : BaseJellyfinApiController
return NotFound();
}
- return await _mediaInfoHelper.GetPlaybackInfo(item, user).ConfigureAwait(false);
+ return await _mediaInfoHelper.GetPlaybackInfo(item, user, Request).ConfigureAwait(false);
}
/// <summary>
@@ -177,6 +177,7 @@ public class MediaInfoController : BaseJellyfinApiController
var info = await _mediaInfoHelper.GetPlaybackInfo(
item,
user,
+ Request,
mediaSourceId,
liveStreamId)
.ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 9ffccaa9e9..51d4081ecf 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -4,6 +4,7 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
@@ -103,6 +104,7 @@ public class PersonsController : BaseJellyfinApiController
personTypes,
excludePersonTypes)
{
+ AccessFilter = BuildAccessFilter(user),
NameContains = searchTerm,
NameStartsWith = nameStartsWith,
NameLessThan = nameLessThan,
@@ -123,6 +125,20 @@ public class PersonsController : BaseJellyfinApiController
.ToArray());
}
+ // People are not owned by a library, so nothing in the Peoples table says which of them a user is
+ // allowed to see; that only follows from the items they are credited on.
+ private InternalItemsQuery? BuildAccessFilter(User? user)
+ {
+ if (user is null || !user.HasContentRestrictions())
+ {
+ return null;
+ }
+
+ var accessFilter = new InternalItemsQuery(user) { IncludeOwnedItems = true };
+ _libraryManager.ConfigureUserAccess(accessFilter, user);
+ return accessFilter;
+ }
+
/// <summary>
/// Get person by name.
/// </summary>
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index e53d15acfd..cdbd1ee7aa 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -133,6 +133,7 @@ public class UniversalAudioController : BaseJellyfinApiController
var info = await _mediaInfoHelper.GetPlaybackInfo(
item,
user,
+ Request,
mediaSourceId)
.ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index a718035528..da03032249 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -34,12 +34,15 @@ 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;
private readonly IDtoService _dtoService;
private readonly IUserViewManager _userViewManager;
private readonly IFileSystem _fileSystem;
+ private readonly IProviderManager _providerManager;
/// <summary>
/// Initializes a new instance of the <see cref="UserLibraryController"/> class.
@@ -50,13 +53,15 @@ public class UserLibraryController : BaseJellyfinApiController
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="userViewManager">Instance of the <see cref="IUserViewManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
public UserLibraryController(
IUserManager userManager,
IUserDataManager userDataRepository,
ILibraryManager libraryManager,
IDtoService dtoService,
IUserViewManager userViewManager,
- IFileSystem fileSystem)
+ IFileSystem fileSystem,
+ IProviderManager providerManager)
{
_userManager = userManager;
_userDataRepository = userDataRepository;
@@ -64,6 +69,7 @@ public class UserLibraryController : BaseJellyfinApiController
_dtoService = dtoService;
_userViewManager = userViewManager;
_fileSystem = fileSystem;
+ _providerManager = providerManager;
}
/// <summary>
@@ -94,7 +100,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
+ await RefreshOnDemandIfNeeded(item).ConfigureAwait(false);
var dtoOptions = new DtoOptions();
@@ -639,24 +645,36 @@ public class UserLibraryController : BaseJellyfinApiController
limit,
groupItems);
- private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
+ private async Task RefreshOnDemandIfNeeded(BaseItem item)
{
- if (item is Person)
+ if (item is not Person)
{
- var hasMetadata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
- var performFullRefresh = !hasMetadata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
+ return;
+ }
- if (performFullRefresh)
- {
- var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
- ImageRefreshMode = MetadataRefreshMode.FullRefresh,
- ForceSave = true
- };
-
- await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
- }
+ var hasMetadata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
+ if (hasMetadata || (DateTime.UtcNow - item.DateLastRefreshed).TotalDays < 3)
+ {
+ return;
+ }
+
+ 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);
}
}