aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs14
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs57
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs7
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs11
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs7
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs1
-rw-r--r--Jellyfin.Api/Helpers/AudioHelper.cs2
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs1
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs9
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs28
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs3
18 files changed, 109 insertions, 55 deletions
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 4b2e5e7ea..154a56702 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -77,6 +77,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
+ /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Total record count.</param>
/// <response code="200">Artists returned.</response>
@@ -112,6 +114,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -150,7 +154,8 @@ namespace Jellyfin.Api.Controllers
MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
@@ -276,6 +281,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
+ /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Total record count.</param>
/// <response code="200">Album artists returned.</response>
@@ -311,6 +318,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -349,7 +358,8 @@ namespace Jellyfin.Api.Controllers
MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b4154b361..62283d038 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -28,7 +28,6 @@ using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
@@ -545,7 +544,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions)
{
- var cancellationTokenSource = new CancellationTokenSource();
+ using var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new VideoRequestDto
{
Id = itemId,
@@ -710,7 +709,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions)
{
- var cancellationTokenSource = new CancellationTokenSource();
+ using var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new StreamingRequestDto
{
Id = itemId,
@@ -1138,7 +1137,7 @@ namespace Jellyfin.Api.Controllers
var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
var hlsVersion = isHlsInFmp4 ? "7" : "3";
- var builder = new StringBuilder();
+ var builder = new StringBuilder(128);
builder.AppendLine("#EXTM3U")
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
@@ -1191,6 +1190,7 @@ namespace Jellyfin.Api.Controllers
throw new ArgumentException("StartTimeTicks is not allowed.");
}
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
@@ -1208,7 +1208,7 @@ namespace Jellyfin.Api.Controllers
_deviceManager,
_transcodingJobHelper,
TranscodingJobType,
- cancellationTokenSource.Token)
+ cancellationToken)
.ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
@@ -1227,7 +1227,7 @@ namespace Jellyfin.Api.Controllers
}
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
- await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+ await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
var released = false;
var startTranscoding = false;
@@ -1323,24 +1323,28 @@ namespace Jellyfin.Api.Controllers
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
- private double[] GetSegmentLengths(StreamState state)
- {
- var result = new List<double>();
+ private static double[] GetSegmentLengths(StreamState state)
+ => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength);
- var ticks = state.RunTimeTicks ?? 0;
-
- var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
+ internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength)
+ {
+ var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks;
+ var wholeSegments = runtimeTicks / segmentLengthTicks;
+ var remainingTicks = runtimeTicks % segmentLengthTicks;
- while (ticks > 0)
+ var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1);
+ var segments = new double[segmentsLen];
+ for (int i = 0; i < wholeSegments; i++)
{
- var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
-
- result.Add(TimeSpan.FromTicks(length).TotalSeconds);
+ segments[i] = segmentlength;
+ }
- ticks -= length;
+ if (remainingTicks != 0)
+ {
+ segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds;
}
- return result.ToArray();
+ return segments;
}
private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
@@ -1376,18 +1380,13 @@ namespace Jellyfin.Api.Controllers
}
else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
{
- var outputFmp4HeaderArg = string.Empty;
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- if (isWindows)
+ var outputFmp4HeaderArg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch
{
// on Windows, the path of fmp4 header file needs to be configured
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
- }
- else
- {
+ true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"",
// on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"";
- }
+ false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
+ };
segmentFormat = "fmp4" + outputFmp4HeaderArg;
}
@@ -1762,9 +1761,9 @@ namespace Jellyfin.Api.Controllers
private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
{
- var folder = Path.GetDirectoryName(playlist);
+ var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist));
- var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty;
+ var filePrefix = Path.GetFileNameWithoutExtension(playlist);
try
{
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 7bcf4674c..5aa457153 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
+ /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Genres returned.</response>
@@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers
NameStartsWithOrGreater = nameStartsWithOrGreater,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 74cf3b162..35c27dd0e 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -143,7 +143,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems(
- [FromQuery] Guid? userId,
+ [FromQuery] Guid userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
[FromQuery] bool? hasThemeVideo,
@@ -224,8 +224,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
- var user = userId.HasValue && !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId.Value)
+ var user = !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId)
: null;
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index d0a2358ae..010a3b19a 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -18,6 +18,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
@@ -300,9 +301,8 @@ namespace Jellyfin.Api.Controllers
private IEnumerable<string> GetActors(IEnumerable<BaseItem> items)
{
- var people = _libraryManager.GetPeople(new InternalPeopleQuery
+ var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty<string>(), new[] { PersonType.Director })
{
- ExcludePersonTypes = new[] { PersonType.Director },
MaxListOrder = 3
});
@@ -316,10 +316,9 @@ namespace Jellyfin.Api.Controllers
private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
{
- var people = _libraryManager.GetPeople(new InternalPeopleQuery
- {
- PersonTypes = new[] { PersonType.Director }
- });
+ var people = _libraryManager.GetPeople(new InternalPeopleQuery(
+ new[] { PersonType.Director },
+ Array.Empty<string>()));
var itemIds = items.Select(i => i.Id).ToList();
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 7f7058b5e..27eec2b9a 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
+ /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Music genres returned.</response>
@@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers
NameStartsWithOrGreater = nameStartsWithOrGreater,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 70a94e27c..b98307f87 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -94,10 +94,10 @@ namespace Jellyfin.Api.Controllers
}
var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite);
- var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery
+ var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery(
+ personTypes,
+ excludePersonTypes)
{
- PersonTypes = personTypes,
- ExcludePersonTypes = excludePersonTypes,
NameContains = searchTerm,
User = user,
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 1669a659d..b473574e0 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
- [HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")]
+ [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")]
public async Task<ActionResult> GetSubtitle(
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index dd3836551..5cb7468b2 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
- [FromQuery] Guid? userId,
+ [FromQuery] Guid userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
[FromQuery] bool? hasThemeVideo,
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 59400db2a..ffb726fab 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -65,6 +65,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
+ /// <param name="nextUpDateCutoff">Optional. Starting date of shows to show in Next Up section.</param>
/// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param>
/// <param name="disableFirstEpisode">Whether to disable sending the first episode in a series as next up.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
@@ -81,6 +82,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
+ [FromQuery] DateTime? nextUpDateCutoff,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool disableFirstEpisode = false)
{
@@ -97,7 +99,8 @@ namespace Jellyfin.Api.Controllers
StartIndex = startIndex,
UserId = userId ?? Guid.Empty,
EnableTotalRecordCount = enableTotalRecordCount,
- DisableFirstEpisode = disableFirstEpisode
+ DisableFirstEpisode = disableFirstEpisode,
+ NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue
},
options);
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index 800cfa81f..34a1d1842 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -265,6 +265,7 @@ namespace Jellyfin.Api.Controllers
EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true
};
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index e544d001e..dc64a0f1b 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -373,6 +373,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Dictionary<string, string> streamOptions)
{
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new VideoRequestDto
{
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
index cf35ee23a..264131905 100644
--- a/Jellyfin.Api/Helpers/AudioHelper.cs
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -97,6 +97,8 @@ namespace Jellyfin.Api.Helpers
}
bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;
+
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
using var state = await StreamingHelpers.GetStreamingState(
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index fcada0e77..dc5d6715b 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -106,6 +106,7 @@ namespace Jellyfin.Api.Helpers
bool enableAdaptiveBitrateStreaming)
{
var isHeadRequest = _httpContextAccessor.HttpContext?.Request.Method == WebRequestMethods.Http.Head;
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
return await GetMasterPlaylistInternal(
streamingRequest,
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 0879cbd18..c295af7eb 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -22,7 +22,6 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Helpers
@@ -270,7 +269,7 @@ namespace Jellyfin.Api.Helpers
{
_activeTranscodingJobs.Remove(job);
- if (!job.CancellationTokenSource!.IsCancellationRequested)
+ if (job.CancellationTokenSource?.IsCancellationRequested == false)
{
job.CancellationTokenSource.Cancel();
}
@@ -380,7 +379,9 @@ namespace Jellyfin.Api.Helpers
/// <param name="outputFilePath">The output file path.</param>
private void DeleteHlsPartialStreamFiles(string outputFilePath)
{
- var directory = Path.GetDirectoryName(outputFilePath);
+ var directory = Path.GetDirectoryName(outputFilePath)
+ ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
+
var name = Path.GetFileNameWithoutExtension(outputFilePath);
var filesToDelete = _fileSystem.GetFilePaths(directory)
@@ -750,7 +751,7 @@ namespace Jellyfin.Api.Helpers
_logger.LogError("FFmpeg exited with code {0}", process.ExitCode);
}
- process.Dispose();
+ job.Dispose();
}
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index c10c34b59..bd7da9b06 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.5" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" />
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
index 9edc19bb6..291e571dc 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
@@ -11,7 +11,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
/// <summary>
/// Class TranscodingJob.
/// </summary>
- public class TranscodingJobDto
+ public class TranscodingJobDto : IDisposable
{
/// <summary>
/// The process lock.
@@ -249,5 +249,31 @@ namespace Jellyfin.Api.Models.PlaybackDtos
}
}
}
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Dispose all resources.
+ /// </summary>
+ /// <param name="disposing">Whether to dispose all resources.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Process?.Dispose();
+ Process = null;
+ KillTimer?.Dispose();
+ KillTimer = null;
+ CancellationTokenSource?.Dispose();
+ CancellationTokenSource = null;
+ TranscodingThrottler?.Dispose();
+ TranscodingThrottler = null;
+ }
+ }
}
}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
index e33e552ed..7b32d76ba 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
@@ -145,7 +145,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
- var path = job.Path;
+ var path = job.Path ?? throw new ArgumentException("Path can't be null.");
+
var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)