aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs3
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs3
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/AudioController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/DevicesController.cs5
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs55
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs5
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs9
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs14
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs15
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs73
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs44
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs58
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs3
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs5
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs17
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs2
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs10
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs5
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs10
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs1
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs3
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs19
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs23
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs6
-rw-r--r--Jellyfin.Api/Helpers/AudioHelper.cs16
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs12
-rw-r--r--Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs24
-rw-r--r--Jellyfin.Api/Helpers/HlsHelpers.cs1
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs3
-rw-r--r--Jellyfin.Api/Helpers/ProgressiveFileStream.cs182
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs3
-rw-r--r--Jellyfin.Api/Helpers/StreamingHelpers.cs12
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs924
-rw-r--r--Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs2
-rw-r--r--Jellyfin.Api/Middleware/LanFilteringMiddleware.cs3
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs283
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs219
-rw-r--r--Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs18
-rw-r--r--Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs4
-rw-r--r--Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs4
-rw-r--r--Jellyfin.Api/Models/StreamingDtos/StreamState.cs185
-rw-r--r--Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs49
-rw-r--r--Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs23
50 files changed, 299 insertions, 2083 deletions
diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
index cf3cb6905..7d0fe5589 100644
--- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
@@ -2,6 +2,7 @@
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
@@ -41,7 +42,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
var isApiKey = context.User.GetIsApiKey();
var userId = context.User.GetUserId();
// This likely only happens during the wizard, so skip the default checks and let any other handlers do it
- if (!isApiKey && userId.Equals(default))
+ if (!isApiKey && userId.IsEmpty())
{
return Task.CompletedTask;
}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs
index 688a13bc0..965b7e7e6 100644
--- a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs
+++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
@@ -46,7 +47,7 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
}
var userId = contextUser.GetUserId();
- if (userId.Equals(default))
+ if (userId.IsEmpty())
{
context.Fail();
return Task.CompletedTask;
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index e7d3e694a..8b931f162 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -126,7 +127,7 @@ public class ArtistsController : BaseJellyfinApiController
User? user = null;
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
- if (!userId.Value.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
user = _userManager.GetUserById(userId.Value);
}
@@ -330,7 +331,7 @@ public class ArtistsController : BaseJellyfinApiController
User? user = null;
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
- if (!userId.Value.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
user = _userManager.GetUserById(userId.Value);
}
@@ -469,7 +470,7 @@ public class ArtistsController : BaseJellyfinApiController
var item = _libraryManager.GetArtist(name, dtoOptions);
- if (!userId.Value.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 5bc533086..cd09d2bfa 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -6,6 +6,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index fdc16ee23..f83c71b57 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -126,7 +127,7 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -201,7 +202,7 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index aa200a722..6d9ec343e 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -42,16 +42,15 @@ public class DevicesController : BaseJellyfinApiController
/// <summary>
/// Get Devices.
/// </summary>
- /// <param name="supportsSync">Gets or sets a value indicating whether [supports synchronize].</param>
/// <param name="userId">Gets or sets the user identifier.</param>
/// <response code="200">Devices retrieved.</response>
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
+ public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
- return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false);
+ return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 9e9c610cc..dda1e9d56 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -9,8 +9,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
-using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -19,6 +19,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
@@ -51,7 +52,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
private readonly ILogger<DynamicHlsController> _logger;
private readonly EncodingHelper _encodingHelper;
private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator;
@@ -67,7 +68,7 @@ public class DynamicHlsController : BaseJellyfinApiController
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
- /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
+ /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
@@ -79,7 +80,7 @@ public class DynamicHlsController : BaseJellyfinApiController
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
- TranscodingJobHelper transcodingJobHelper,
+ ITranscodeManager transcodeManager,
ILogger<DynamicHlsController> logger,
DynamicHlsHelper dynamicHlsHelper,
EncodingHelper encodingHelper,
@@ -91,7 +92,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
_logger = logger;
_dynamicHlsHelper = dynamicHlsHelper;
_encodingHelper = encodingHelper;
@@ -283,17 +284,17 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager,
_mediaEncoder,
_encodingHelper,
- _transcodingJobHelper,
+ _transcodeManager,
TranscodingJobType,
cancellationToken)
.ConfigureAwait(false);
- TranscodingJobDto? job = null;
+ TranscodingJob? job = null;
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
if (!System.IO.File.Exists(playlistPath))
{
- var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
+ var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath);
await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
@@ -302,11 +303,11 @@ public class DynamicHlsController : BaseJellyfinApiController
// If the playlist doesn't already exist, startup ffmpeg
try
{
- job = await _transcodingJobHelper.StartFfMpeg(
+ job = await _transcodeManager.StartFfMpeg(
state,
playlistPath,
GetCommandLineArguments(playlistPath, state, true, 0),
- Request,
+ Request.HttpContext.User.GetUserId(),
TranscodingJobType,
cancellationTokenSource)
.ConfigureAwait(false);
@@ -331,11 +332,11 @@ public class DynamicHlsController : BaseJellyfinApiController
}
}
- job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
if (job is not null)
{
- _transcodingJobHelper.OnTranscodeEndRequest(job);
+ _transcodeManager.OnTranscodeEndRequest(job);
}
var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state);
@@ -1383,7 +1384,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager,
_mediaEncoder,
_encodingHelper,
- _transcodingJobHelper,
+ _transcodeManager,
TranscodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
@@ -1421,7 +1422,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager,
_mediaEncoder,
_encodingHelper,
- _transcodingJobHelper,
+ _transcodeManager,
TranscodingJobType,
cancellationToken)
.ConfigureAwait(false);
@@ -1432,16 +1433,16 @@ public class DynamicHlsController : BaseJellyfinApiController
var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
- TranscodingJobDto? job;
+ TranscodingJob? job;
if (System.IO.File.Exists(segmentPath))
{
- job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
_logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
- var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
+ var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath);
await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
var released = false;
var startTranscoding = false;
@@ -1450,7 +1451,7 @@ public class DynamicHlsController : BaseJellyfinApiController
{
if (System.IO.File.Exists(segmentPath))
{
- job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
transcodingLock.Release();
released = true;
_logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
@@ -1488,7 +1489,7 @@ public class DynamicHlsController : BaseJellyfinApiController
// If the playlist doesn't already exist, startup ffmpeg
try
{
- await _transcodingJobHelper.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false)
+ await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false)
.ConfigureAwait(false);
if (currentTranscodingIndex.HasValue)
@@ -1499,11 +1500,11 @@ public class DynamicHlsController : BaseJellyfinApiController
streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks;
state.WaitForPath = segmentPath;
- job = await _transcodingJobHelper.StartFfMpeg(
+ job = await _transcodeManager.StartFfMpeg(
state,
playlistPath,
GetCommandLineArguments(playlistPath, state, false, segmentId),
- Request,
+ Request.HttpContext.User.GetUserId(),
TranscodingJobType,
cancellationTokenSource).ConfigureAwait(false);
}
@@ -1517,7 +1518,7 @@ public class DynamicHlsController : BaseJellyfinApiController
}
else
{
- job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
if (job?.TranscodingThrottler is not null)
{
await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false);
@@ -1534,7 +1535,7 @@ public class DynamicHlsController : BaseJellyfinApiController
}
_logger.LogDebug("returning {0} [general case]", segmentPath);
- job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
@@ -1922,7 +1923,7 @@ public class DynamicHlsController : BaseJellyfinApiController
string segmentPath,
string segmentExtension,
int segmentIndex,
- TranscodingJobDto? transcodingJob,
+ TranscodingJob? transcodingJob,
CancellationToken cancellationToken)
{
var segmentExists = System.IO.File.Exists(segmentPath);
@@ -1991,7 +1992,7 @@ public class DynamicHlsController : BaseJellyfinApiController
return GetSegmentResult(state, segmentPath, transcodingJob);
}
- private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJobDto? transcodingJob)
+ private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJob? transcodingJob)
{
var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks;
@@ -2001,7 +2002,7 @@ public class DynamicHlsController : BaseJellyfinApiController
if (transcodingJob is not null)
{
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
- _transcodingJobHelper.OnTranscodeEndRequest(transcodingJob);
+ _transcodeManager.OnTranscodeEndRequest(transcodingJob);
}
return Task.CompletedTask;
@@ -2012,7 +2013,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
{
- var job = _transcodingJobHelper.GetTranscodingJob(playlist, TranscodingJobType);
+ var job = _transcodeManager.GetTranscodingJob(playlist, TranscodingJobType);
if (job is null || job.HasExited)
{
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index baeb8b81a..d6e043e6a 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -3,6 +3,7 @@ using System.Linq;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -53,7 +54,7 @@ public class FilterController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -146,7 +147,7 @@ public class FilterController : BaseJellyfinApiController
[FromQuery] bool? recursive)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 062e1062d..54d48aec2 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -95,7 +96,7 @@ public class GenresController : BaseJellyfinApiController
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
- User? user = userId.Value.Equals(default)
+ User? user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -131,8 +132,8 @@ public class GenresController : BaseJellyfinApiController
QueryResult<(BaseItem, ItemCounts)> result;
if (parentItem is ICollectionFolder parentCollectionFolder
- && (parentCollectionFolder.CollectionType == CollectionType.Music
- || parentCollectionFolder.CollectionType == CollectionType.MusicVideos))
+ && (parentCollectionFolder.CollectionType == CollectionType.music
+ || parentCollectionFolder.CollectionType == CollectionType.musicvideos))
{
result = _libraryManager.GetMusicGenres(query);
}
@@ -172,7 +173,7 @@ public class GenresController : BaseJellyfinApiController
item ??= new Genre();
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 392d9955f..1927a332b 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -24,22 +24,22 @@ public class HlsSegmentController : BaseJellyfinApiController
{
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
/// <summary>
/// Initializes a new instance of the <see cref="HlsSegmentController"/> class.
/// </summary>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
- /// <param name="transcodingJobHelper">Initialized instance of the <see cref="TranscodingJobHelper"/>.</param>
+ /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
public HlsSegmentController(
IFileSystem fileSystem,
IServerConfigurationManager serverConfigurationManager,
- TranscodingJobHelper transcodingJobHelper)
+ ITranscodeManager transcodeManager)
{
_fileSystem = fileSystem;
_serverConfigurationManager = serverConfigurationManager;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
}
/// <summary>
@@ -112,7 +112,7 @@ public class HlsSegmentController : BaseJellyfinApiController
[FromQuery, Required] string deviceId,
[FromQuery, Required] string playSessionId)
{
- _transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true);
+ _transcodeManager.KillTranscodingJobs(deviceId, playSessionId, _ => true);
return NoContent();
}
@@ -174,13 +174,13 @@ public class HlsSegmentController : BaseJellyfinApiController
private ActionResult GetFileResult(string path, string playlistPath)
{
- var transcodingJob = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
+ var transcodingJob = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
Response.OnCompleted(() =>
{
if (transcodingJob is not null)
{
- _transcodingJobHelper.OnTranscodeEndRequest(transcodingJob);
+ _transcodeManager.OnTranscodeEndRequest(transcodingJob);
}
return Task.CompletedTask;
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 4dc2a4253..e7ff1f986 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -76,7 +77,7 @@ public class InstantMixController : BaseJellyfinApiController
{
var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -113,7 +114,7 @@ public class InstantMixController : BaseJellyfinApiController
{
var album = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -150,7 +151,7 @@ public class InstantMixController : BaseJellyfinApiController
{
var playlist = (Playlist)_libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -186,7 +187,7 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -223,7 +224,7 @@ public class InstantMixController : BaseJellyfinApiController
{
var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -260,7 +261,7 @@ public class InstantMixController : BaseJellyfinApiController
{
var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -334,7 +335,7 @@ public class InstantMixController : BaseJellyfinApiController
{
var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index 4e5ed60d5..9800248c6 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -171,7 +171,7 @@ public class ItemUpdateController : BaseJellyfinApiController
info.ContentTypeOptions = GetContentTypeOptions(true).ToArray();
info.ContentType = configuredContentType;
- if (inheritedContentType is null || inheritedContentType == CollectionType.TvShows)
+ if (inheritedContentType is null || inheritedContentType == CollectionType.tvshows)
{
info.ContentTypeOptions = info.ContentTypeOptions
.Where(i => string.IsNullOrWhiteSpace(i.Value)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 4e46e808a..d10fba920 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -34,6 +35,7 @@ public class ItemsController : BaseJellyfinApiController
private readonly IDtoService _dtoService;
private readonly ILogger<ItemsController> _logger;
private readonly ISessionManager _sessionManager;
+ private readonly IUserDataManager _userDataRepository;
/// <summary>
/// Initializes a new instance of the <see cref="ItemsController"/> class.
@@ -44,13 +46,15 @@ public class ItemsController : BaseJellyfinApiController
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
+ /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
public ItemsController(
IUserManager userManager,
ILibraryManager libraryManager,
ILocalizationManager localization,
IDtoService dtoService,
ILogger<ItemsController> logger,
- ISessionManager sessionManager)
+ ISessionManager sessionManager,
+ IUserDataManager userDataRepository)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -58,6 +62,7 @@ public class ItemsController : BaseJellyfinApiController
_dtoService = dtoService;
_logger = logger;
_sessionManager = sessionManager;
+ _userDataRepository = userDataRepository;
}
/// <summary>
@@ -241,7 +246,7 @@ public class ItemsController : BaseJellyfinApiController
var isApiKey = User.GetIsApiKey();
// if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
userId = RequestHelpers.GetUserId(User, userId);
- var user = !isApiKey && !userId.Value.Equals(default)
+ var user = !isApiKey && !userId.IsNullOrEmpty()
? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException()
: null;
@@ -275,7 +280,7 @@ public class ItemsController : BaseJellyfinApiController
collectionType = hasCollectionType.CollectionType;
}
- if (collectionType == CollectionType.Playlists)
+ if (collectionType == CollectionType.playlists)
{
recursive = true;
includeItemTypes = new[] { BaseItemKind.Playlist };
@@ -836,7 +841,7 @@ public class ItemsController : BaseJellyfinApiController
var ancestorIds = Array.Empty<Guid>();
var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes);
- if (parentIdGuid.Equals(default) && excludeFolderIds.Length > 0)
+ if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0)
{
ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.Where(i => i is Folder)
@@ -881,4 +886,64 @@ public class ItemsController : BaseJellyfinApiController
itemsResult.TotalRecordCount,
returnItems);
}
+
+ /// <summary>
+ /// Get Item User Data.
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <response code="200">return item user data.</response>
+ /// <response code="404">Item is not found.</response>
+ /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
+ [HttpGet("Users/{userId}/Items/{itemId}/UserData")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public ActionResult<UserItemDataDto> GetItemUserData(
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] Guid itemId)
+ {
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data.");
+ }
+
+ var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException();
+ var item = _libraryManager.GetItemById(itemId);
+
+ return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user);
+ }
+
+ /// <summary>
+ /// Update Item User Data.
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userDataDto">New user data object.</param>
+ /// <response code="200">return updated user item data.</response>
+ /// <response code="404">Item is not found.</response>
+ /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
+ [HttpPost("Users/{userId}/Items/{itemId}/UserData")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public ActionResult<UserItemDataDto> UpdateItemUserData(
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] Guid itemId,
+ [FromBody, Required] UpdateUserItemDataDto userDataDto)
+ {
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data.");
+ }
+
+ var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException();
+ var item = _libraryManager.GetItemById(itemId);
+ if (item == null)
+ {
+ return NotFound();
+ }
+
+ _userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData);
+
+ return _userDataRepository.GetUserDataDto(item, user);
+ }
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index af9a93719..a0bbc961f 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -146,12 +146,12 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] bool inheritFromParent = false)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
- var item = itemId.Equals(default)
- ? (userId.Value.Equals(default)
+ var item = itemId.IsEmpty()
+ ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -213,12 +213,12 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] bool inheritFromParent = false)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
- var item = itemId.Equals(default)
- ? (userId.Value.Equals(default)
+ var item = itemId.IsEmpty()
+ ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -339,7 +339,7 @@ public class LibraryController : BaseJellyfinApiController
{
var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId();
- var user = !isApiKey && !userId.Equals(default)
+ var user = !isApiKey && !userId.IsEmpty()
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
: null;
if (!isApiKey && user is null)
@@ -382,7 +382,7 @@ public class LibraryController : BaseJellyfinApiController
{
var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId();
- var user = !isApiKey && !userId.Equals(default)
+ var user = !isApiKey && !userId.IsEmpty()
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
: null;
@@ -428,7 +428,7 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] bool? isFavorite)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -471,7 +471,7 @@ public class LibraryController : BaseJellyfinApiController
var baseItemDtos = new List<BaseItemDto>();
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -702,8 +702,8 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
userId = RequestHelpers.GetUserId(User, userId);
- var item = itemId.Equals(default)
- ? (userId.Value.Equals(default)
+ var item = itemId.IsEmpty()
+ ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -718,7 +718,7 @@ public class LibraryController : BaseJellyfinApiController
return new QueryResult<BaseItemDto>();
}
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -927,15 +927,15 @@ public class LibraryController : BaseJellyfinApiController
{
return contentType switch
{
- CollectionType.BoxSets => new[] { "BoxSet" },
- CollectionType.Playlists => new[] { "Playlist" },
- CollectionType.Movies => new[] { "Movie" },
- CollectionType.TvShows => new[] { "Series", "Season", "Episode" },
- CollectionType.Books => new[] { "Book" },
- CollectionType.Music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" },
- CollectionType.HomeVideos => new[] { "Video", "Photo" },
- CollectionType.Photos => new[] { "Video", "Photo" },
- CollectionType.MusicVideos => new[] { "MusicVideo" },
+ CollectionType.boxsets => new[] { "BoxSet" },
+ CollectionType.playlists => new[] { "Playlist" },
+ CollectionType.movies => new[] { "Movie" },
+ CollectionType.tvshows => new[] { "Series", "Season", "Episode" },
+ CollectionType.books => new[] { "Book" },
+ CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" },
+ CollectionType.homevideos => new[] { "Video", "Photo" },
+ CollectionType.photos => new[] { "Video", "Photo" },
+ CollectionType.musicvideos => new[] { "MusicVideo" },
_ => new[] { "Series", "Season", "Episode", "Movie" }
};
}
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 425086895..1b2f5750f 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -10,12 +10,12 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
@@ -24,6 +24,8 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
@@ -41,43 +43,47 @@ namespace Jellyfin.Api.Controllers;
public class LiveTvController : BaseJellyfinApiController
{
private readonly ILiveTvManager _liveTvManager;
+ private readonly ITunerHostManager _tunerHostManager;
private readonly IUserManager _userManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _configurationManager;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
/// </summary>
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
+ /// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
- /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
+ /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
public LiveTvController(
ILiveTvManager liveTvManager,
+ ITunerHostManager tunerHostManager,
IUserManager userManager,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IDtoService dtoService,
IMediaSourceManager mediaSourceManager,
IConfigurationManager configurationManager,
- TranscodingJobHelper transcodingJobHelper)
+ ITranscodeManager transcodeManager)
{
_liveTvManager = liveTvManager;
+ _tunerHostManager = tunerHostManager;
_userManager = userManager;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_dtoService = dtoService;
_mediaSourceManager = mediaSourceManager;
_configurationManager = configurationManager;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
}
/// <summary>
@@ -177,7 +183,7 @@ public class LiveTvController : BaseJellyfinApiController
dtoOptions,
CancellationToken.None);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -209,10 +215,10 @@ public class LiveTvController : BaseJellyfinApiController
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
- var item = channelId.Equals(default)
+ var item = channelId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(channelId);
@@ -382,7 +388,7 @@ public class LiveTvController : BaseJellyfinApiController
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false);
@@ -405,10 +411,10 @@ public class LiveTvController : BaseJellyfinApiController
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
- var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
+ var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
var dtoOptions = new DtoOptions()
.AddClientFields(User);
@@ -562,7 +568,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool enableTotalRecordCount = true)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -589,7 +595,7 @@ public class LiveTvController : BaseJellyfinApiController
GenreIds = genreIds
};
- if (librarySeriesId.HasValue && !librarySeriesId.Equals(default))
+ if (!librarySeriesId.IsNullOrEmpty())
{
query.IsSeries = true;
@@ -618,7 +624,7 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
{
- var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
+ var user = body.UserId.IsEmpty() ? null : _userManager.GetUserById(body.UserId);
var query = new InternalItemsQuery(user)
{
@@ -643,7 +649,7 @@ public class LiveTvController : BaseJellyfinApiController
GenreIds = body.GenreIds
};
- if (!body.LibrarySeriesId.Equals(default))
+ if (!body.LibrarySeriesId.IsEmpty())
{
query.IsSeries = true;
@@ -702,7 +708,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool enableTotalRecordCount = true)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -741,7 +747,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -949,9 +955,7 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
- {
- return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
- }
+ => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
/// <summary>
/// Deletes a tuner host.
@@ -1128,10 +1132,8 @@ public class LiveTvController : BaseJellyfinApiController
[HttpGet("TunerHosts/Types")]
[Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
- {
- return _liveTvManager.GetTunerHostTypes();
- }
+ public IEnumerable<NameIdPair> GetTunerHostTypes()
+ => _tunerHostManager.GetTunerHostTypes();
/// <summary>
/// Discover tuners.
@@ -1143,10 +1145,8 @@ public class LiveTvController : BaseJellyfinApiController
[HttpGet("Tuners/Discover")]
[Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
- {
- return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
- }
+ public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
+ => _tunerHostManager.DiscoverTuners(newDevicesOnly);
/// <summary>
/// Gets a live tv recording stream.
@@ -1171,7 +1171,7 @@ public class LiveTvController : BaseJellyfinApiController
return NotFound();
}
- var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper);
+ var stream = new ProgressiveFileStream(path, null, _transcodeManager);
return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
}
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index e1145481f..471bcd096 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -7,6 +7,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
@@ -69,7 +70,7 @@ public class MoviesController : BaseJellyfinApiController
[FromQuery] int itemLimit = 8)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 69b904264..5411baa3e 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -95,7 +96,7 @@ public class MusicGenresController : BaseJellyfinApiController
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
- User? user = userId.Value.Equals(default)
+ User? user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -164,7 +165,7 @@ public class MusicGenresController : BaseJellyfinApiController
return NotFound();
}
- if (!userId.Value.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index b4c6f490a..6ca308601 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -83,7 +84,7 @@ public class PersonsController : BaseJellyfinApiController
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = userId.Value.Equals(default)
+ User? user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -129,7 +130,7 @@ public class PersonsController : BaseJellyfinApiController
return NotFound();
}
- if (!userId.Value.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index c4c89ccde..921cc6031 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -9,6 +9,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.PlaylistDtos;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
@@ -188,7 +189,7 @@ public class PlaylistsController : BaseJellyfinApiController
return NotFound();
}
- var user = userId.Equals(default)
+ var user = userId.IsEmpty()
? null
: _userManager.GetUserById(userId);
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 8ad553bcb..bde2f4d1a 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -8,6 +8,7 @@ using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -30,7 +31,7 @@ public class PlaystateController : BaseJellyfinApiController
private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager;
private readonly ILogger<PlaystateController> _logger;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
/// <summary>
/// Initializes a new instance of the <see cref="PlaystateController"/> class.
@@ -40,14 +41,14 @@ public class PlaystateController : BaseJellyfinApiController
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
- /// <param name="transcodingJobHelper">Th <see cref="TranscodingJobHelper"/> singleton.</param>
+ /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
public PlaystateController(
IUserManager userManager,
IUserDataManager userDataRepository,
ILibraryManager libraryManager,
ISessionManager sessionManager,
ILoggerFactory loggerFactory,
- TranscodingJobHelper transcodingJobHelper)
+ ITranscodeManager transcodeManager)
{
_userManager = userManager;
_userDataRepository = userDataRepository;
@@ -55,7 +56,7 @@ public class PlaystateController : BaseJellyfinApiController
_sessionManager = sessionManager;
_logger = loggerFactory.CreateLogger<PlaystateController>();
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
}
/// <summary>
@@ -188,7 +189,7 @@ public class PlaystateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId)
{
- _transcodingJobHelper.PingTranscodingJob(playSessionId, null);
+ _transcodeManager.PingTranscodingJob(playSessionId, null);
return NoContent();
}
@@ -205,7 +206,7 @@ public class PlaystateController : BaseJellyfinApiController
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
{
- await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
+ await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
}
playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
@@ -354,7 +355,7 @@ public class PlaystateController : BaseJellyfinApiController
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
{
- await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
+ await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
}
playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
@@ -388,7 +389,7 @@ public class PlaystateController : BaseJellyfinApiController
{
if (method == PlayMethod.Transcode)
{
- var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodingJobHelper.GetTranscodingJob(playSessionId);
+ var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodeManager.GetTranscodingJob(playSessionId);
if (job is null)
{
return PlayMethod.DirectPlay;
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 5b4594165..413b7b834 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -209,7 +209,7 @@ public class SearchController : BaseJellyfinApiController
break;
}
- if (!item.ChannelId.Equals(default))
+ if (!item.ChannelId.IsEmpty())
{
var channel = _libraryManager.GetItemById(item.ChannelId);
result.ChannelName = channel?.Name;
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index fdebb3d45..52b58b8f1 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.SessionDtos;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
@@ -71,7 +72,7 @@ public class SessionController : BaseJellyfinApiController
result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
}
- if (controllableByUserId.HasValue && !controllableByUserId.Equals(default))
+ if (!controllableByUserId.IsNullOrEmpty())
{
result = result.Where(i => i.SupportsRemoteControl);
@@ -83,12 +84,12 @@ public class SessionController : BaseJellyfinApiController
if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
{
- result = result.Where(i => i.UserId.Equals(default) || i.ContainsUser(controllableByUserId.Value));
+ result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableByUserId.Value));
}
if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
{
- result = result.Where(i => !i.UserId.Equals(default));
+ result = result.Where(i => !i.UserId.IsEmpty());
}
result = result.Where(i =>
@@ -385,7 +386,6 @@ public class SessionController : BaseJellyfinApiController
/// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</param>
/// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param>
/// <param name="supportsMediaControl">Determines whether media can be played remotely..</param>
- /// <param name="supportsSync">Determines whether sync is supported.</param>
/// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
/// <response code="204">Capabilities posted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
@@ -397,7 +397,6 @@ public class SessionController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
[FromQuery] bool supportsMediaControl = false,
- [FromQuery] bool supportsSync = false,
[FromQuery] bool supportsPersistentIdentifier = true)
{
if (string.IsNullOrWhiteSpace(id))
@@ -410,7 +409,6 @@ public class SessionController : BaseJellyfinApiController
PlayableMediaTypes = playableMediaTypes,
SupportedCommands = supportedCommands,
SupportsMediaControl = supportsMediaControl,
- SupportsSync = supportsSync,
SupportsPersistentIdentifier = supportsPersistentIdentifier
});
return NoContent();
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index f434f60f5..708fc7436 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -5,6 +5,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -91,7 +92,7 @@ public class StudiosController : BaseJellyfinApiController
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = userId.Value.Equals(default)
+ User? user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -144,7 +145,7 @@ public class StudiosController : BaseJellyfinApiController
var dtoOptions = new DtoOptions().AddClientFields(User);
var item = _libraryManager.GetStudio(name);
- if (!userId.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index 675757fc5..2aa6d25a7 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -62,7 +63,7 @@ public class SuggestionsController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] bool enableTotalRecordCount = false)
{
- var user = userId.Equals(default)
+ var user = userId.IsEmpty()
? null
: _userManager.GetUserById(userId);
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 55a30d469..3d84b61bf 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -111,7 +111,7 @@ public class TvShowsController : BaseJellyfinApiController
},
options);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -150,7 +150,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] bool? enableUserData)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -222,7 +222,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] ItemSortBy? sortBy)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
@@ -284,7 +284,7 @@ public class TvShowsController : BaseJellyfinApiController
}
// This must be the last filter
- if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default))
+ if (!adjacentTo.IsNullOrEmpty())
{
episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList();
}
@@ -339,7 +339,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] bool? enableUserData)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 7177a0440..0a416aedb 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -11,6 +11,7 @@ using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Authorization;
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index f9f27f148..ea10ee24f 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@@ -532,7 +533,7 @@ public class UserController : BaseJellyfinApiController
public ActionResult<UserDto> GetCurrentUser()
{
var userId = User.GetUserId();
- if (userId.Equals(default))
+ if (userId.IsEmpty())
{
return BadRequest();
}
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index 2c4fe9186..264e0a3db 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -84,7 +85,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -145,7 +146,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -185,7 +186,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -221,7 +222,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -257,7 +258,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -294,7 +295,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -330,7 +331,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -375,7 +376,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
@@ -558,7 +559,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
- var item = itemId.Equals(default)
+ var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index 0ffa3ab1a..035d04474 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -90,7 +90,6 @@ public class UserViewsController : BaseJellyfinApiController
fields.Add(ItemFields.PrimaryImageAspectRatio);
fields.Add(ItemFields.DisplayPreferencesId);
- fields.Remove(ItemFields.BasicSyncInfo);
dtoOptions.Fields = fields.ToArray();
var user = _userManager.GetUserById(userId);
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 5d9868eb9..e6c319869 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -11,7 +11,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Api.Models.StreamingDtos;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
@@ -20,6 +20,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -43,7 +44,7 @@ public class VideosController : BaseJellyfinApiController
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly EncodingHelper _encodingHelper;
@@ -58,7 +59,7 @@ public class VideosController : BaseJellyfinApiController
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
- /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
+ /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public VideosController(
@@ -68,7 +69,7 @@ public class VideosController : BaseJellyfinApiController
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
- TranscodingJobHelper transcodingJobHelper,
+ ITranscodeManager transcodeManager,
IHttpClientFactory httpClientFactory,
EncodingHelper encodingHelper)
{
@@ -78,7 +79,7 @@ public class VideosController : BaseJellyfinApiController
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
_httpClientFactory = httpClientFactory;
_encodingHelper = encodingHelper;
}
@@ -96,12 +97,12 @@ public class VideosController : BaseJellyfinApiController
public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
- var user = userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
- var item = itemId.Equals(default)
- ? (userId.Value.Equals(default)
+ var item = itemId.IsEmpty()
+ ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -427,7 +428,7 @@ public class VideosController : BaseJellyfinApiController
_serverConfigurationManager,
_mediaEncoder,
_encodingHelper,
- _transcodingJobHelper,
+ _transcodeManager,
_transcodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
@@ -466,7 +467,7 @@ public class VideosController : BaseJellyfinApiController
if (state.MediaSource.IsInfiniteStream)
{
- var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper);
+ var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager);
return File(liveStream, contentType);
}
@@ -482,7 +483,7 @@ public class VideosController : BaseJellyfinApiController
state,
isHeadRequest,
HttpContext,
- _transcodingJobHelper,
+ _transcodeManager,
ffmpegCommandLineArguments,
_transcodingJobType,
cancellationTokenSource).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index ca46c38c5..e4aa0ea42 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -90,7 +90,7 @@ public class YearsController : BaseJellyfinApiController
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = userId.Value.Equals(default)
+ User? user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
@@ -110,7 +110,7 @@ public class YearsController : BaseJellyfinApiController
{
var folder = (Folder)parentItem;
- if (userId.Equals(default))
+ if (userId.IsNullOrEmpty())
{
items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList();
}
@@ -182,7 +182,7 @@ public class YearsController : BaseJellyfinApiController
var dtoOptions = new DtoOptions()
.AddClientFields(User);
- if (!userId.Value.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
index 926ce99dd..c80a9d582 100644
--- a/Jellyfin.Api/Helpers/AudioHelper.cs
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -2,13 +2,13 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
@@ -26,7 +26,7 @@ public class AudioHelper
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EncodingHelper _encodingHelper;
@@ -39,7 +39,7 @@ public class AudioHelper
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
- /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
+ /// <param name="transcodeManager">Instance of <see cref="ITranscodeManager"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
@@ -49,7 +49,7 @@ public class AudioHelper
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
- TranscodingJobHelper transcodingJobHelper,
+ ITranscodeManager transcodeManager,
IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor,
EncodingHelper encodingHelper)
@@ -59,7 +59,7 @@ public class AudioHelper
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
_httpClientFactory = httpClientFactory;
_httpContextAccessor = httpContextAccessor;
_encodingHelper = encodingHelper;
@@ -94,7 +94,7 @@ public class AudioHelper
_serverConfigurationManager,
_mediaEncoder,
_encodingHelper,
- _transcodingJobHelper,
+ _transcodeManager,
transcodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
@@ -133,7 +133,7 @@ public class AudioHelper
if (state.MediaSource.IsInfiniteStream)
{
- var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper);
+ var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager);
return new FileStreamResult(stream, contentType);
}
@@ -149,7 +149,7 @@ public class AudioHelper
state,
isHeadRequest,
_httpContextAccessor.HttpContext,
- _transcodingJobHelper,
+ _transcodeManager,
ffmpegCommandLineArguments,
transcodingJobType,
cancellationTokenSource).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index 05f7d44bf..fa81fc284 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -8,7 +8,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -18,6 +17,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
@@ -39,7 +39,7 @@ public class DynamicHlsHelper
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
private readonly INetworkManager _networkManager;
private readonly ILogger<DynamicHlsHelper> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
@@ -54,7 +54,7 @@ public class DynamicHlsHelper
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
- /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
+ /// <param name="transcodeManager">Instance of <see cref="ITranscodeManager"/>.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
@@ -66,7 +66,7 @@ public class DynamicHlsHelper
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
- TranscodingJobHelper transcodingJobHelper,
+ ITranscodeManager transcodeManager,
INetworkManager networkManager,
ILogger<DynamicHlsHelper> logger,
IHttpContextAccessor httpContextAccessor,
@@ -78,7 +78,7 @@ public class DynamicHlsHelper
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
_networkManager = networkManager;
_logger = logger;
_httpContextAccessor = httpContextAccessor;
@@ -130,7 +130,7 @@ public class DynamicHlsHelper
_serverConfigurationManager,
_mediaEncoder,
_encodingHelper,
- _transcodingJobHelper,
+ _transcodeManager,
transcodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index 0f0a70c69..5385979d4 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -4,9 +4,9 @@ using System.Net.Http;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Models.PlaybackDtos;
-using Jellyfin.Api.Models.StreamingDtos;
+using Jellyfin.Api.Extensions;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
@@ -65,7 +65,7 @@ public static class FileStreamResponseHelpers
/// <param name="state">The current <see cref="StreamState"/>.</param>
/// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
/// <param name="httpContext">The current http context.</param>
- /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
+ /// <param name="transcodeManager">The <see cref="ITranscodeManager"/> singleton.</param>
/// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
/// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
@@ -74,7 +74,7 @@ public static class FileStreamResponseHelpers
StreamState state,
bool isHeadRequest,
HttpContext httpContext,
- TranscodingJobHelper transcodingJobHelper,
+ ITranscodeManager transcodeManager,
string ffmpegCommandLineArguments,
TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource)
@@ -93,22 +93,28 @@ public static class FileStreamResponseHelpers
return new OkResult();
}
- var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
+ var transcodingLock = transcodeManager.GetTranscodingLock(outputPath);
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
- TranscodingJobDto? job;
+ TranscodingJob? job;
if (!File.Exists(outputPath))
{
- job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, httpContext.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
+ job = await transcodeManager.StartFfMpeg(
+ state,
+ outputPath,
+ ffmpegCommandLineArguments,
+ httpContext.User.GetUserId(),
+ transcodingJobType,
+ cancellationTokenSource).ConfigureAwait(false);
}
else
{
- job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
+ job = transcodeManager.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
state.Dispose();
}
- var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper);
+ var stream = new ProgressiveFileStream(outputPath, job, transcodeManager);
return new FileStreamResult(stream, contentType);
}
finally
diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs
index e2d3bfb19..c8a36c562 100644
--- a/Jellyfin.Api/Helpers/HlsHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsHelpers.cs
@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index 321987ca7..6a24ad32a 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -86,7 +87,7 @@ public class MediaInfoHelper
string? mediaSourceId = null,
string? liveStreamId = null)
{
- var user = userId is null || userId.Value.Equals(default)
+ var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById(id);
diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
deleted file mode 100644
index d7b1c9f8b..000000000
--- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Api.Models.PlaybackDtos;
-using MediaBrowser.Model.IO;
-
-namespace Jellyfin.Api.Helpers;
-
-/// <summary>
-/// A progressive file stream for transferring transcoded files as they are written to.
-/// </summary>
-public class ProgressiveFileStream : Stream
-{
- private readonly Stream _stream;
- private readonly TranscodingJobDto? _job;
- private readonly TranscodingJobHelper? _transcodingJobHelper;
- private readonly int _timeoutMs;
- private bool _disposed;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class.
- /// </summary>
- /// <param name="filePath">The path to the transcoded file.</param>
- /// <param name="job">The transcoding job information.</param>
- /// <param name="transcodingJobHelper">The transcoding job helper.</param>
- /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
- public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
- {
- _job = job;
- _transcodingJobHelper = transcodingJobHelper;
- _timeoutMs = timeoutMs;
-
- _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class.
- /// </summary>
- /// <param name="stream">The stream to progressively copy.</param>
- /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
- public ProgressiveFileStream(Stream stream, int timeoutMs = 30000)
- {
- _job = null;
- _transcodingJobHelper = null;
- _timeoutMs = timeoutMs;
- _stream = stream;
- }
-
- /// <inheritdoc />
- public override bool CanRead => _stream.CanRead;
-
- /// <inheritdoc />
- public override bool CanSeek => false;
-
- /// <inheritdoc />
- public override bool CanWrite => false;
-
- /// <inheritdoc />
- public override long Length => throw new NotSupportedException();
-
- /// <inheritdoc />
- public override long Position
- {
- get => throw new NotSupportedException();
- set => throw new NotSupportedException();
- }
-
- /// <inheritdoc />
- public override void Flush()
- {
- // Not supported
- }
-
- /// <inheritdoc />
- public override int Read(byte[] buffer, int offset, int count)
- => Read(buffer.AsSpan(offset, count));
-
- /// <inheritdoc />
- public override int Read(Span<byte> buffer)
- {
- int totalBytesRead = 0;
- var stopwatch = Stopwatch.StartNew();
-
- while (true)
- {
- totalBytesRead += _stream.Read(buffer);
- if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds))
- {
- break;
- }
-
- Thread.Sleep(50);
- }
-
- UpdateBytesWritten(totalBytesRead);
-
- return totalBytesRead;
- }
-
- /// <inheritdoc />
- public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
-
- /// <inheritdoc />
- public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
- {
- int totalBytesRead = 0;
- var stopwatch = Stopwatch.StartNew();
-
- while (true)
- {
- totalBytesRead += await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
- if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds))
- {
- break;
- }
-
- await Task.Delay(50, cancellationToken).ConfigureAwait(false);
- }
-
- UpdateBytesWritten(totalBytesRead);
-
- return totalBytesRead;
- }
-
- /// <inheritdoc />
- public override long Seek(long offset, SeekOrigin origin)
- => throw new NotSupportedException();
-
- /// <inheritdoc />
- public override void SetLength(long value)
- => throw new NotSupportedException();
-
- /// <inheritdoc />
- public override void Write(byte[] buffer, int offset, int count)
- => throw new NotSupportedException();
-
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- try
- {
- if (disposing)
- {
- _stream.Dispose();
-
- if (_job is not null)
- {
- _transcodingJobHelper?.OnTranscodeEndRequest(_job);
- }
- }
- }
- finally
- {
- _disposed = true;
- base.Dispose(disposing);
- }
- }
-
- private void UpdateBytesWritten(int totalBytesRead)
- {
- if (_job is not null)
- {
- _job.BytesDownloaded += totalBytesRead;
- }
- }
-
- private bool StopReading(int bytesRead, long elapsed)
- {
- // It should stop reading when anything has been successfully read or if the job has exited
- // If the job is null, however, it's a live stream and will require user action to close,
- // but don't keep it open indefinitely if it isn't reading anything
- return bytesRead > 0 || (_job?.HasExited ?? elapsed >= _timeoutMs);
- }
-}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index be3d4dfb6..429e97213 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -7,6 +7,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -67,7 +68,7 @@ public static class RequestHelpers
var authenticatedUserId = claimsPrincipal.GetUserId();
// UserId not provided, fall back to authenticated user id.
- if (userId is null || userId.Value.Equals(default))
+ if (userId.IsNullOrEmpty())
{
return authenticatedUserId;
}
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 71c62b235..7a3842a9f 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
@@ -14,6 +13,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -38,7 +38,7 @@ public static class StreamingHelpers
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
- /// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param>
+ /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
@@ -51,7 +51,7 @@ public static class StreamingHelpers
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
EncodingHelper encodingHelper,
- TranscodingJobHelper transcodingJobHelper,
+ ITranscodeManager transcodeManager,
TranscodingJobType transcodingJobType,
CancellationToken cancellationToken)
{
@@ -74,7 +74,7 @@ public static class StreamingHelpers
streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
}
- var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
+ var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager)
{
Request = streamingRequest,
RequestedUrl = url,
@@ -82,7 +82,7 @@ public static class StreamingHelpers
};
var userId = httpContext.User.GetUserId();
- if (!userId.Equals(default))
+ if (!userId.IsEmpty())
{
state.User = userManager.GetUserById(userId);
}
@@ -115,7 +115,7 @@ public static class StreamingHelpers
if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
{
var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
- ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId)
+ ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId)
: null;
if (currentJob is not null)
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
deleted file mode 100644
index 77d3edbd6..000000000
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ /dev/null
@@ -1,924 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Models.PlaybackDtos;
-using Jellyfin.Api.Models.StreamingDtos;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Session;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Logging;
-
-namespace Jellyfin.Api.Helpers;
-
-/// <summary>
-/// Transcoding job helpers.
-/// </summary>
-public class TranscodingJobHelper : IDisposable
-{
- /// <summary>
- /// The active transcoding jobs.
- /// </summary>
- private static readonly List<TranscodingJobDto> _activeTranscodingJobs = new List<TranscodingJobDto>();
-
- /// <summary>
- /// The transcoding locks.
- /// </summary>
- private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>();
-
- private readonly IAttachmentExtractor _attachmentExtractor;
- private readonly IApplicationPaths _appPaths;
- private readonly EncodingHelper _encodingHelper;
- private readonly IFileSystem _fileSystem;
- private readonly ILogger<TranscodingJobHelper> _logger;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly ISessionManager _sessionManager;
- private readonly ILoggerFactory _loggerFactory;
- private readonly IUserManager _userManager;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class.
- /// </summary>
- /// <param name="attachmentExtractor">Instance of the <see cref="IAttachmentExtractor"/> interface.</param>
- /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
- /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobHelpers}"/> interface.</param>
- /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
- /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
- /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
- /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
- /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
- /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
- /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- public TranscodingJobHelper(
- IAttachmentExtractor attachmentExtractor,
- IApplicationPaths appPaths,
- ILogger<TranscodingJobHelper> logger,
- IMediaSourceManager mediaSourceManager,
- IFileSystem fileSystem,
- IMediaEncoder mediaEncoder,
- IServerConfigurationManager serverConfigurationManager,
- ISessionManager sessionManager,
- EncodingHelper encodingHelper,
- ILoggerFactory loggerFactory,
- IUserManager userManager)
- {
- _attachmentExtractor = attachmentExtractor;
- _appPaths = appPaths;
- _logger = logger;
- _mediaSourceManager = mediaSourceManager;
- _fileSystem = fileSystem;
- _mediaEncoder = mediaEncoder;
- _serverConfigurationManager = serverConfigurationManager;
- _sessionManager = sessionManager;
- _encodingHelper = encodingHelper;
- _loggerFactory = loggerFactory;
- _userManager = userManager;
-
- DeleteEncodedMediaCache();
-
- sessionManager.PlaybackProgress += OnPlaybackProgress;
- sessionManager.PlaybackStart += OnPlaybackProgress;
- }
-
- /// <summary>
- /// Get transcoding job.
- /// </summary>
- /// <param name="playSessionId">Playback session id.</param>
- /// <returns>The transcoding job.</returns>
- public TranscodingJobDto? GetTranscodingJob(string playSessionId)
- {
- lock (_activeTranscodingJobs)
- {
- return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
- }
- }
-
- /// <summary>
- /// Get transcoding job.
- /// </summary>
- /// <param name="path">Path to the transcoding file.</param>
- /// <param name="type">The <see cref="TranscodingJobType"/>.</param>
- /// <returns>The transcoding job.</returns>
- public TranscodingJobDto? GetTranscodingJob(string path, TranscodingJobType type)
- {
- lock (_activeTranscodingJobs)
- {
- return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
- }
- }
-
- /// <summary>
- /// Ping transcoding job.
- /// </summary>
- /// <param name="playSessionId">Play session id.</param>
- /// <param name="isUserPaused">Is user paused.</param>
- /// <exception cref="ArgumentNullException">Play session id is null.</exception>
- public void PingTranscodingJob(string playSessionId, bool? isUserPaused)
- {
- ArgumentException.ThrowIfNullOrEmpty(playSessionId);
-
- _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
-
- List<TranscodingJobDto> jobs;
-
- lock (_activeTranscodingJobs)
- {
- // This is really only needed for HLS.
- // Progressive streams can stop on their own reliably.
- jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
- }
-
- foreach (var job in jobs)
- {
- if (isUserPaused.HasValue)
- {
- _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
- job.IsUserPaused = isUserPaused.Value;
- }
-
- PingTimer(job, true);
- }
- }
-
- private void PingTimer(TranscodingJobDto job, bool isProgressCheckIn)
- {
- if (job.HasExited)
- {
- job.StopKillTimer();
- return;
- }
-
- var timerDuration = 10000;
-
- if (job.Type != TranscodingJobType.Progressive)
- {
- timerDuration = 60000;
- }
-
- job.PingTimeout = timerDuration;
- job.LastPingDate = DateTime.UtcNow;
-
- // Don't start the timer for playback checkins with progressive streaming
- if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
- {
- job.StartKillTimer(OnTranscodeKillTimerStopped);
- }
- else
- {
- job.ChangeKillTimerIfStarted();
- }
- }
-
- /// <summary>
- /// Called when [transcode kill timer stopped].
- /// </summary>
- /// <param name="state">The state.</param>
- private async void OnTranscodeKillTimerStopped(object? state)
- {
- var job = state as TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state));
- if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
- {
- var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
-
- if (timeSinceLastPing < job.PingTimeout)
- {
- job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout);
- return;
- }
- }
-
- _logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
-
- await KillTranscodingJob(job, true, path => true).ConfigureAwait(false);
- }
-
- /// <summary>
- /// Kills the single transcoding job.
- /// </summary>
- /// <param name="deviceId">The device id.</param>
- /// <param name="playSessionId">The play session identifier.</param>
- /// <param name="deleteFiles">The delete files.</param>
- /// <returns>Task.</returns>
- public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func<string, bool> deleteFiles)
- {
- return KillTranscodingJobs(
- j => string.IsNullOrWhiteSpace(playSessionId)
- ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
- : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase),
- deleteFiles);
- }
-
- /// <summary>
- /// Kills the transcoding jobs.
- /// </summary>
- /// <param name="killJob">The kill job.</param>
- /// <param name="deleteFiles">The delete files.</param>
- /// <returns>Task.</returns>
- private Task KillTranscodingJobs(Func<TranscodingJobDto, bool> killJob, Func<string, bool> deleteFiles)
- {
- var jobs = new List<TranscodingJobDto>();
-
- lock (_activeTranscodingJobs)
- {
- // This is really only needed for HLS.
- // Progressive streams can stop on their own reliably.
- jobs.AddRange(_activeTranscodingJobs.Where(killJob));
- }
-
- if (jobs.Count == 0)
- {
- return Task.CompletedTask;
- }
-
- IEnumerable<Task> GetKillJobs()
- {
- foreach (var job in jobs)
- {
- yield return KillTranscodingJob(job, false, deleteFiles);
- }
- }
-
- return Task.WhenAll(GetKillJobs());
- }
-
- /// <summary>
- /// Kills the transcoding job.
- /// </summary>
- /// <param name="job">The job.</param>
- /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
- /// <param name="delete">The delete.</param>
- private async Task KillTranscodingJob(TranscodingJobDto job, bool closeLiveStream, Func<string, bool> delete)
- {
- job.DisposeKillTimer();
-
- _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
-
- lock (_activeTranscodingJobs)
- {
- _activeTranscodingJobs.Remove(job);
-
- if (job.CancellationTokenSource?.IsCancellationRequested == false)
- {
-#pragma warning disable CA1849 // Can't await in lock block
- job.CancellationTokenSource.Cancel();
- }
- }
-
- lock (_transcodingLocks)
- {
- _transcodingLocks.Remove(job.Path!);
- }
-
- lock (job.ProcessLock!)
- {
- job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
-
- var process = job.Process;
-
- var hasExited = job.HasExited;
-
- if (!hasExited)
- {
- try
- {
- _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
-
- process!.StandardInput.WriteLine("q");
-
- // Need to wait because killing is asynchronous.
- if (!process.WaitForExit(5000))
- {
- _logger.LogInformation("Killing FFmpeg process for {Path}", job.Path);
- process.Kill();
- }
- }
- catch (InvalidOperationException)
- {
- }
- }
-#pragma warning restore CA1849
- }
-
- if (delete(job.Path!))
- {
- await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false);
- if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay)
- {
- var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat");
- if (File.Exists(concatFilePath))
- {
- _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath);
- File.Delete(concatFilePath);
- }
- }
- }
-
- if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
- {
- try
- {
- await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error closing live stream for {Path}", job.Path);
- }
- }
- }
-
- private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
- {
- if (retryCount >= 10)
- {
- return;
- }
-
- _logger.LogInformation("Deleting partial stream file(s) {Path}", path);
-
- await Task.Delay(delayMs).ConfigureAwait(false);
-
- try
- {
- if (jobType == TranscodingJobType.Progressive)
- {
- DeleteProgressivePartialStreamFiles(path);
- }
- else
- {
- DeleteHlsPartialStreamFiles(path);
- }
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
-
- await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
- }
- }
-
- /// <summary>
- /// Deletes the progressive partial stream files.
- /// </summary>
- /// <param name="outputFilePath">The output file path.</param>
- private void DeleteProgressivePartialStreamFiles(string outputFilePath)
- {
- if (File.Exists(outputFilePath))
- {
- _fileSystem.DeleteFile(outputFilePath);
- }
- }
-
- /// <summary>
- /// Deletes the HLS partial stream files.
- /// </summary>
- /// <param name="outputFilePath">The output file path.</param>
- private void DeleteHlsPartialStreamFiles(string outputFilePath)
- {
- var directory = Path.GetDirectoryName(outputFilePath)
- ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
-
- var name = Path.GetFileNameWithoutExtension(outputFilePath);
-
- var filesToDelete = _fileSystem.GetFilePaths(directory)
- .Where(f => f.Contains(name, StringComparison.OrdinalIgnoreCase));
-
- List<Exception>? exs = null;
- foreach (var file in filesToDelete)
- {
- try
- {
- _logger.LogDebug("Deleting HLS file {0}", file);
- _fileSystem.DeleteFile(file);
- }
- catch (IOException ex)
- {
- (exs ??= new List<Exception>(4)).Add(ex);
- _logger.LogError(ex, "Error deleting HLS file {Path}", file);
- }
- }
-
- if (exs is not null)
- {
- throw new AggregateException("Error deleting HLS files", exs);
- }
- }
-
- /// <summary>
- /// Report the transcoding progress to the session manager.
- /// </summary>
- /// <param name="job">The <see cref="TranscodingJobDto"/> of which the progress will be reported.</param>
- /// <param name="state">The <see cref="StreamState"/> of the current transcoding job.</param>
- /// <param name="transcodingPosition">The current transcoding position.</param>
- /// <param name="framerate">The framerate of the transcoding job.</param>
- /// <param name="percentComplete">The completion percentage of the transcode.</param>
- /// <param name="bytesTranscoded">The number of bytes transcoded.</param>
- /// <param name="bitRate">The bitrate of the transcoding job.</param>
- public void ReportTranscodingProgress(
- TranscodingJobDto job,
- StreamState state,
- TimeSpan? transcodingPosition,
- float? framerate,
- double? percentComplete,
- long? bytesTranscoded,
- int? bitRate)
- {
- var ticks = transcodingPosition?.Ticks;
-
- if (job is not null)
- {
- job.Framerate = framerate;
- job.CompletionPercentage = percentComplete;
- job.TranscodingPositionTicks = ticks;
- job.BytesTranscoded = bytesTranscoded;
- job.BitRate = bitRate;
- }
-
- var deviceId = state.Request.DeviceId;
-
- if (!string.IsNullOrWhiteSpace(deviceId))
- {
- var audioCodec = state.ActualOutputAudioCodec;
- var videoCodec = state.ActualOutputVideoCodec;
- var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType;
- HardwareEncodingType? hardwareAccelerationType = null;
- if (Enum.TryParse<HardwareEncodingType>(hardwareAccelerationTypeString, out var parsedHardwareAccelerationType))
- {
- hardwareAccelerationType = parsedHardwareAccelerationType;
- }
-
- _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
- {
- Bitrate = bitRate ?? state.TotalOutputBitrate,
- AudioCodec = audioCodec,
- VideoCodec = videoCodec,
- Container = state.OutputContainer,
- Framerate = framerate,
- CompletionPercentage = percentComplete,
- Width = state.OutputWidth,
- Height = state.OutputHeight,
- AudioChannels = state.OutputAudioChannels,
- IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
- IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
- HardwareAccelerationType = hardwareAccelerationType,
- TranscodeReasons = state.TranscodeReasons
- });
- }
- }
-
- /// <summary>
- /// Starts FFmpeg.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="outputPath">The output path.</param>
- /// <param name="commandLineArguments">The command line arguments for FFmpeg.</param>
- /// <param name="request">The <see cref="HttpRequest"/>.</param>
- /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
- /// <param name="cancellationTokenSource">The cancellation token source.</param>
- /// <param name="workingDirectory">The working directory.</param>
- /// <returns>Task.</returns>
- public async Task<TranscodingJobDto> StartFfMpeg(
- StreamState state,
- string outputPath,
- string commandLineArguments,
- HttpRequest request,
- TranscodingJobType transcodingJobType,
- CancellationTokenSource cancellationTokenSource,
- string? workingDirectory = null)
- {
- var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
- Directory.CreateDirectory(directory);
-
- await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
-
- if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
- {
- var userId = request.HttpContext.User.GetUserId();
- var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
- if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
- {
- this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
-
- throw new ArgumentException("User does not have access to video transcoding.");
- }
- }
-
- ArgumentException.ThrowIfNullOrEmpty(_mediaEncoder.EncoderPath);
-
- // If subtitles get burned in fonts may need to be extracted from the media file
- if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
- {
- var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id);
- if (state.VideoType != VideoType.Dvd)
- {
- await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false);
- }
-
- if (state.SubtitleStream.IsExternal && Path.GetExtension(state.SubtitleStream.Path.AsSpan()).Equals(".mks", StringComparison.OrdinalIgnoreCase))
- {
- string subtitlePath = state.SubtitleStream.Path;
- string subtitlePathArgument = string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", subtitlePath.Replace("\"", "\\\"", StringComparison.Ordinal));
- string subtitleId = subtitlePath.GetMD5().ToString("N", CultureInfo.InvariantCulture);
-
- await _attachmentExtractor.ExtractAllAttachmentsExternal(subtitlePathArgument, subtitleId, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
-
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- WindowStyle = ProcessWindowStyle.Hidden,
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both stdout and stderr or deadlocks may occur
- // RedirectStandardOutput = true,
- RedirectStandardError = true,
- RedirectStandardInput = true,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = commandLineArguments,
- WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? string.Empty : workingDirectory,
- ErrorDialog = false
- },
- EnableRaisingEvents = true
- };
-
- var transcodingJob = this.OnTranscodeBeginning(
- outputPath,
- state.Request.PlaySessionId,
- state.MediaSource.LiveStreamId,
- Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
- transcodingJobType,
- process,
- state.Request.DeviceId,
- state,
- cancellationTokenSource);
-
- _logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
- var logFilePrefix = "FFmpeg.Transcode-";
- if (state.VideoRequest is not null
- && EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
- {
- logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec)
- ? "FFmpeg.Remux-"
- : "FFmpeg.DirectStream-";
- }
-
- var logFilePath = Path.Combine(
- _serverConfigurationManager.ApplicationPaths.LogDirectoryPath,
- $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log");
-
- // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
-
- var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
- var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false);
-
- process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting FFmpeg");
-
- this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
-
- throw;
- }
-
- _logger.LogDebug("Launched FFmpeg process");
- state.TranscodingJob = transcodingJob;
-
- // Important - don't await the log task or we won't be able to kill FFmpeg when the user stops playback
- _ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError, logStream);
-
- // Wait for the file to exist before proceeding
- var ffmpegTargetFile = state.WaitForPath ?? outputPath;
- _logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile);
- while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited)
- {
- await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
- }
-
- _logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile);
-
- if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
- {
- await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
-
- if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited)
- {
- await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
-
- if (!transcodingJob.HasExited)
- {
- StartThrottler(state, transcodingJob);
- }
- else if (transcodingJob.ExitCode != 0)
- {
- throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "FFmpeg exited with code {0}", transcodingJob.ExitCode));
- }
-
- _logger.LogDebug("StartFfMpeg() finished successfully");
-
- return transcodingJob;
- }
-
- private void StartThrottler(StreamState state, TranscodingJobDto transcodingJob)
- {
- if (EnableThrottling(state))
- {
- transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, _loggerFactory.CreateLogger<TranscodingThrottler>(), _serverConfigurationManager, _fileSystem, _mediaEncoder);
- transcodingJob.TranscodingThrottler.Start();
- }
- }
-
- private bool EnableThrottling(StreamState state)
- {
- var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
-
- return state.InputProtocol == MediaProtocol.File &&
- state.RunTimeTicks.HasValue &&
- state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
- state.IsInputVideo &&
- state.VideoType == VideoType.VideoFile;
- }
-
- /// <summary>
- /// Called when [transcode beginning].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="playSessionId">The play session identifier.</param>
- /// <param name="liveStreamId">The live stream identifier.</param>
- /// <param name="transcodingJobId">The transcoding job identifier.</param>
- /// <param name="type">The type.</param>
- /// <param name="process">The process.</param>
- /// <param name="deviceId">The device id.</param>
- /// <param name="state">The state.</param>
- /// <param name="cancellationTokenSource">The cancellation token source.</param>
- /// <returns>TranscodingJob.</returns>
- public TranscodingJobDto OnTranscodeBeginning(
- string path,
- string? playSessionId,
- string? liveStreamId,
- string transcodingJobId,
- TranscodingJobType type,
- Process process,
- string? deviceId,
- StreamState state,
- CancellationTokenSource cancellationTokenSource)
- {
- lock (_activeTranscodingJobs)
- {
- var job = new TranscodingJobDto(_loggerFactory.CreateLogger<TranscodingJobDto>())
- {
- Type = type,
- Path = path,
- Process = process,
- ActiveRequestCount = 1,
- DeviceId = deviceId,
- CancellationTokenSource = cancellationTokenSource,
- Id = transcodingJobId,
- PlaySessionId = playSessionId,
- LiveStreamId = liveStreamId,
- MediaSource = state.MediaSource
- };
-
- _activeTranscodingJobs.Add(job);
-
- ReportTranscodingProgress(job, state, null, null, null, null, null);
-
- return job;
- }
- }
-
- /// <summary>
- /// Called when [transcode end].
- /// </summary>
- /// <param name="job">The transcode job.</param>
- public void OnTranscodeEndRequest(TranscodingJobDto job)
- {
- job.ActiveRequestCount--;
- _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount);
- if (job.ActiveRequestCount <= 0)
- {
- PingTimer(job, false);
- }
- }
-
- /// <summary>
- /// <summary>
- /// The progressive
- /// </summary>
- /// Called when [transcode failed to start].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="type">The type.</param>
- /// <param name="state">The state.</param>
- public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
- {
- lock (_activeTranscodingJobs)
- {
- var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-
- if (job is not null)
- {
- _activeTranscodingJobs.Remove(job);
- }
- }
-
- lock (_transcodingLocks)
- {
- _transcodingLocks.Remove(path);
- }
-
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
- {
- _sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
- }
- }
-
- /// <summary>
- /// Processes the exited.
- /// </summary>
- /// <param name="process">The process.</param>
- /// <param name="job">The job.</param>
- /// <param name="state">The state.</param>
- private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
- {
- job.HasExited = true;
- job.ExitCode = process.ExitCode;
-
- ReportTranscodingProgress(job, state, null, null, null, null, null);
-
- _logger.LogDebug("Disposing stream resources");
- state.Dispose();
-
- if (process.ExitCode == 0)
- {
- _logger.LogInformation("FFmpeg exited with code 0");
- }
- else
- {
- _logger.LogError("FFmpeg exited with code {0}", process.ExitCode);
- }
-
- job.Dispose();
- }
-
- private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
- {
- if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
- {
- var liveStreamResponse = await _mediaSourceManager.OpenLiveStream(
- new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken },
- cancellationTokenSource.Token)
- .ConfigureAwait(false);
- var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
-
- _encodingHelper.AttachMediaSourceInfo(state, encodingOptions, liveStreamResponse.MediaSource, state.RequestedUrl);
-
- if (state.VideoRequest is not null)
- {
- _encodingHelper.TryStreamCopy(state);
- }
- }
-
- if (state.MediaSource.BufferMs.HasValue)
- {
- await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
-
- /// <summary>
- /// Called when [transcode begin request].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="type">The type.</param>
- /// <returns>The <see cref="TranscodingJobDto"/>.</returns>
- public TranscodingJobDto? OnTranscodeBeginRequest(string path, TranscodingJobType type)
- {
- lock (_activeTranscodingJobs)
- {
- var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-
- if (job is null)
- {
- return null;
- }
-
- OnTranscodeBeginRequest(job);
-
- return job;
- }
- }
-
- private void OnTranscodeBeginRequest(TranscodingJobDto job)
- {
- job.ActiveRequestCount++;
-
- if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
- {
- job.StopKillTimer();
- }
- }
-
- /// <summary>
- /// Gets the transcoding lock.
- /// </summary>
- /// <param name="outputPath">The output path of the transcoded file.</param>
- /// <returns>A <see cref="SemaphoreSlim"/>.</returns>
- public SemaphoreSlim GetTranscodingLock(string outputPath)
- {
- lock (_transcodingLocks)
- {
- if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result))
- {
- result = new SemaphoreSlim(1, 1);
- _transcodingLocks[outputPath] = result;
- }
-
- return result;
- }
- }
-
- private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
- {
- if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
- {
- PingTranscodingJob(e.PlaySessionId, e.IsPaused);
- }
- }
-
- /// <summary>
- /// Deletes the encoded media cache.
- /// </summary>
- private void DeleteEncodedMediaCache()
- {
- var path = _serverConfigurationManager.GetTranscodePath();
- if (!Directory.Exists(path))
- {
- return;
- }
-
- foreach (var file in _fileSystem.GetFilePaths(path, true))
- {
- _fileSystem.DeleteFile(file);
- }
- }
-
- /// <summary>
- /// Dispose transcoding job helper.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Dispose throttler.
- /// </summary>
- /// <param name="disposing">Disposing.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _loggerFactory.Dispose();
- _sessionManager.PlaybackProgress -= OnPlaybackProgress;
- _sessionManager.PlaybackStart -= OnPlaybackProgress;
- }
- }
-}
diff --git a/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs
index 27bcd5570..842a69dd9 100644
--- a/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs
+++ b/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs
@@ -41,6 +41,8 @@ public class IPBasedAccessValidationMiddleware
if (!networkManager.HasRemoteAccess(remoteIP))
{
+ // No access from network, respond with 503 instead of 200.
+ httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
return;
}
diff --git a/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
index d8c95ddff..35b0a1dd0 100644
--- a/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
+++ b/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
@@ -1,3 +1,4 @@
+using System.Net;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@@ -40,6 +41,8 @@ public class LanFilteringMiddleware
var host = httpContext.GetNormalizedRemoteIP();
if (!networkManager.IsInLocalNetwork(host))
{
+ // No access from network, respond with 503 instead of 200.
+ httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
return;
}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
deleted file mode 100644
index 480ddab09..000000000
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
+++ /dev/null
@@ -1,283 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Threading;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dto;
-using Microsoft.Extensions.Logging;
-
-namespace Jellyfin.Api.Models.PlaybackDtos;
-
-/// <summary>
-/// Class TranscodingJob.
-/// </summary>
-public class TranscodingJobDto : IDisposable
-{
- /// <summary>
- /// The process lock.
- /// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1051:NoVisibleInstanceFields", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
- [SuppressMessage("Microsoft.Performance", "SA1401:PrivateField", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
- public readonly object ProcessLock = new object();
-
- /// <summary>
- /// Timer lock.
- /// </summary>
- private readonly object _timerLock = new object();
-
- /// <summary>
- /// Initializes a new instance of the <see cref="TranscodingJobDto"/> class.
- /// </summary>
- /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param>
- public TranscodingJobDto(ILogger<TranscodingJobDto> logger)
- {
- Logger = logger;
- }
-
- /// <summary>
- /// Gets or sets the play session identifier.
- /// </summary>
- /// <value>The play session identifier.</value>
- public string? PlaySessionId { get; set; }
-
- /// <summary>
- /// Gets or sets the live stream identifier.
- /// </summary>
- /// <value>The live stream identifier.</value>
- public string? LiveStreamId { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether is live output.
- /// </summary>
- public bool IsLiveOutput { get; set; }
-
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- public MediaSourceInfo? MediaSource { get; set; }
-
- /// <summary>
- /// Gets or sets path.
- /// </summary>
- public string? Path { get; set; }
-
- /// <summary>
- /// Gets or sets the type.
- /// </summary>
- /// <value>The type.</value>
- public TranscodingJobType Type { get; set; }
-
- /// <summary>
- /// Gets or sets the process.
- /// </summary>
- /// <value>The process.</value>
- public Process? Process { get; set; }
-
- /// <summary>
- /// Gets logger.
- /// </summary>
- public ILogger<TranscodingJobDto> Logger { get; private set; }
-
- /// <summary>
- /// Gets or sets the active request count.
- /// </summary>
- /// <value>The active request count.</value>
- public int ActiveRequestCount { get; set; }
-
- /// <summary>
- /// Gets or sets the kill timer.
- /// </summary>
- /// <value>The kill timer.</value>
- private Timer? KillTimer { get; set; }
-
- /// <summary>
- /// Gets or sets device id.
- /// </summary>
- public string? DeviceId { get; set; }
-
- /// <summary>
- /// Gets or sets cancellation token source.
- /// </summary>
- public CancellationTokenSource? CancellationTokenSource { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether has exited.
- /// </summary>
- public bool HasExited { get; set; }
-
- /// <summary>
- /// Gets or sets exit code.
- /// </summary>
- public int ExitCode { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether is user paused.
- /// </summary>
- public bool IsUserPaused { get; set; }
-
- /// <summary>
- /// Gets or sets id.
- /// </summary>
- public string? Id { get; set; }
-
- /// <summary>
- /// Gets or sets framerate.
- /// </summary>
- public float? Framerate { get; set; }
-
- /// <summary>
- /// Gets or sets completion percentage.
- /// </summary>
- public double? CompletionPercentage { get; set; }
-
- /// <summary>
- /// Gets or sets bytes downloaded.
- /// </summary>
- public long BytesDownloaded { get; set; }
-
- /// <summary>
- /// Gets or sets bytes transcoded.
- /// </summary>
- public long? BytesTranscoded { get; set; }
-
- /// <summary>
- /// Gets or sets bit rate.
- /// </summary>
- public int? BitRate { get; set; }
-
- /// <summary>
- /// Gets or sets transcoding position ticks.
- /// </summary>
- public long? TranscodingPositionTicks { get; set; }
-
- /// <summary>
- /// Gets or sets download position ticks.
- /// </summary>
- public long? DownloadPositionTicks { get; set; }
-
- /// <summary>
- /// Gets or sets transcoding throttler.
- /// </summary>
- public TranscodingThrottler? TranscodingThrottler { get; set; }
-
- /// <summary>
- /// Gets or sets last ping date.
- /// </summary>
- public DateTime LastPingDate { get; set; }
-
- /// <summary>
- /// Gets or sets ping timeout.
- /// </summary>
- public int PingTimeout { get; set; }
-
- /// <summary>
- /// Stop kill timer.
- /// </summary>
- public void StopKillTimer()
- {
- lock (_timerLock)
- {
- KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
- }
- }
-
- /// <summary>
- /// Dispose kill timer.
- /// </summary>
- public void DisposeKillTimer()
- {
- lock (_timerLock)
- {
- if (KillTimer is not null)
- {
- KillTimer.Dispose();
- KillTimer = null;
- }
- }
- }
-
- /// <summary>
- /// Start kill timer.
- /// </summary>
- /// <param name="callback">Callback action.</param>
- public void StartKillTimer(Action<object?> callback)
- {
- StartKillTimer(callback, PingTimeout);
- }
-
- /// <summary>
- /// Start kill timer.
- /// </summary>
- /// <param name="callback">Callback action.</param>
- /// <param name="intervalMs">Callback interval.</param>
- public void StartKillTimer(Action<object?> callback, int intervalMs)
- {
- if (HasExited)
- {
- return;
- }
-
- lock (_timerLock)
- {
- if (KillTimer is null)
- {
- Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
- }
- else
- {
- Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer.Change(intervalMs, Timeout.Infinite);
- }
- }
- }
-
- /// <summary>
- /// Change kill timer if started.
- /// </summary>
- public void ChangeKillTimerIfStarted()
- {
- if (HasExited)
- {
- return;
- }
-
- lock (_timerLock)
- {
- if (KillTimer is not null)
- {
- var intervalMs = PingTimeout;
-
- Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer.Change(intervalMs, Timeout.Infinite);
- }
- }
- }
-
- /// <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
deleted file mode 100644
index b577c4ea6..000000000
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
-
-namespace Jellyfin.Api.Models.PlaybackDtos;
-
-/// <summary>
-/// Transcoding throttler.
-/// </summary>
-public class TranscodingThrottler : IDisposable
-{
- private readonly TranscodingJobDto _job;
- private readonly ILogger<TranscodingThrottler> _logger;
- private readonly IConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
- private readonly IMediaEncoder _mediaEncoder;
- private Timer? _timer;
- private bool _isPaused;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="TranscodingThrottler"/> class.
- /// </summary>
- /// <param name="job">Transcoding job dto.</param>
- /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
- /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
- /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
- /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
- public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
- {
- _job = job;
- _logger = logger;
- _config = config;
- _fileSystem = fileSystem;
- _mediaEncoder = mediaEncoder;
- }
-
- /// <summary>
- /// Start timer.
- /// </summary>
- public void Start()
- {
- _timer = new Timer(TimerCallback, null, 5000, 5000);
- }
-
- /// <summary>
- /// Unpause transcoding.
- /// </summary>
- /// <returns>A <see cref="Task"/>.</returns>
- public async Task UnpauseTranscoding()
- {
- if (_isPaused)
- {
- _logger.LogDebug("Sending resume command to ffmpeg");
-
- try
- {
- var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine;
- await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false);
- _isPaused = false;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error resuming transcoding");
- }
- }
- }
-
- /// <summary>
- /// Stop throttler.
- /// </summary>
- /// <returns>A <see cref="Task"/>.</returns>
- public async Task Stop()
- {
- DisposeTimer();
- await UnpauseTranscoding().ConfigureAwait(false);
- }
-
- /// <summary>
- /// Dispose throttler.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Dispose throttler.
- /// </summary>
- /// <param name="disposing">Disposing.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- DisposeTimer();
- }
- }
-
- private EncodingOptions GetOptions()
- {
- return _config.GetEncodingOptions();
- }
-
- private async void TimerCallback(object? state)
- {
- if (_job.HasExited)
- {
- DisposeTimer();
- return;
- }
-
- var options = GetOptions();
-
- if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds))
- {
- await PauseTranscoding().ConfigureAwait(false);
- }
- else
- {
- await UnpauseTranscoding().ConfigureAwait(false);
- }
- }
-
- private async Task PauseTranscoding()
- {
- if (!_isPaused)
- {
- var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c";
-
- _logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey);
-
- try
- {
- await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false);
- _isPaused = true;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error pausing transcoding");
- }
- }
- }
-
- private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds)
- {
- var bytesDownloaded = job.BytesDownloaded;
- var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
- var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
-
- var path = job.Path ?? throw new ArgumentException("Path can't be null.");
-
- var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
-
- if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
- {
- // HLS - time-based consideration
-
- var targetGap = gapLengthInTicks;
- var gap = transcodingPositionTicks - downloadPositionTicks;
-
- if (gap < targetGap)
- {
- _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
- return false;
- }
-
- _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
- return true;
- }
-
- if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
- {
- // Progressive Streaming - byte-based consideration
-
- try
- {
- var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length;
-
- // Estimate the bytes the transcoder should be ahead
- double gapFactor = gapLengthInTicks;
- gapFactor /= transcodingPositionTicks;
- var targetGap = bytesTranscoded * gapFactor;
-
- var gap = bytesTranscoded - bytesDownloaded;
-
- if (gap < targetGap)
- {
- _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
- return false;
- }
-
- _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
- return true;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error getting output size");
- return false;
- }
- }
-
- _logger.LogDebug("No throttle data for {Path}", path);
- return false;
- }
-
- private void DisposeTimer()
- {
- if (_timer is not null)
- {
- _timer.Dispose();
- _timer = null;
- }
- }
-}
diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs
index b021771a0..acd3f29e3 100644
--- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs
+++ b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs
@@ -31,26 +31,11 @@ public class ClientCapabilitiesDto
public bool SupportsMediaControl { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether session supports content uploading.
- /// </summary>
- public bool SupportsContentUploading { get; set; }
-
- /// <summary>
- /// Gets or sets the message callback url.
- /// </summary>
- public string? MessageCallbackUrl { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether session supports a persistent identifier.
/// </summary>
public bool SupportsPersistentIdentifier { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether session supports sync.
- /// </summary>
- public bool SupportsSync { get; set; }
-
- /// <summary>
/// Gets or sets the device profile.
/// </summary>
public DeviceProfile? DeviceProfile { get; set; }
@@ -76,10 +61,7 @@ public class ClientCapabilitiesDto
PlayableMediaTypes = PlayableMediaTypes,
SupportedCommands = SupportedCommands,
SupportsMediaControl = SupportsMediaControl,
- SupportsContentUploading = SupportsContentUploading,
- MessageCallbackUrl = MessageCallbackUrl,
SupportsPersistentIdentifier = SupportsPersistentIdentifier,
- SupportsSync = SupportsSync,
DeviceProfile = DeviceProfile,
AppStoreUrl = AppStoreUrl,
IconUrl = IconUrl
diff --git a/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs
index 4f1abb1ff..bd176bb6a 100644
--- a/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs
+++ b/Jellyfin.Api/Models/StreamingDtos/HlsAudioRequestDto.cs
@@ -1,4 +1,6 @@
-namespace Jellyfin.Api.Models.StreamingDtos;
+using MediaBrowser.Controller.Streaming;
+
+namespace Jellyfin.Api.Models.StreamingDtos;
/// <summary>
/// The hls video request dto.
diff --git a/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs
index 1cd3d0132..53b6d7575 100644
--- a/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs
+++ b/Jellyfin.Api/Models/StreamingDtos/HlsVideoRequestDto.cs
@@ -1,4 +1,6 @@
-namespace Jellyfin.Api.Models.StreamingDtos;
+using MediaBrowser.Controller.Streaming;
+
+namespace Jellyfin.Api.Models.StreamingDtos;
/// <summary>
/// The hls video request dto.
diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs
deleted file mode 100644
index cc1f9163e..000000000
--- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-using System;
-using Jellyfin.Api.Helpers;
-using Jellyfin.Api.Models.PlaybackDtos;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dlna;
-
-namespace Jellyfin.Api.Models.StreamingDtos;
-
-/// <summary>
-/// The stream state dto.
-/// </summary>
-public class StreamState : EncodingJobInfo, IDisposable
-{
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly TranscodingJobHelper _transcodingJobHelper;
- private bool _disposed;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="StreamState" /> class.
- /// </summary>
- /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager" /> interface.</param>
- /// <param name="transcodingType">The <see cref="TranscodingJobType" />.</param>
- /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper" /> singleton.</param>
- public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper)
- : base(transcodingType)
- {
- _mediaSourceManager = mediaSourceManager;
- _transcodingJobHelper = transcodingJobHelper;
- }
-
- /// <summary>
- /// Gets or sets the requested url.
- /// </summary>
- public string? RequestedUrl { get; set; }
-
- /// <summary>
- /// Gets or sets the request.
- /// </summary>
- public StreamingRequestDto Request
- {
- get => (StreamingRequestDto)BaseRequest;
- set
- {
- BaseRequest = value;
- IsVideoRequest = VideoRequest is not null;
- }
- }
-
- /// <summary>
- /// Gets the video request.
- /// </summary>
- public VideoRequestDto? VideoRequest => Request as VideoRequestDto;
-
- /// <summary>
- /// Gets or sets the direct stream provicer.
- /// </summary>
- /// <remarks>
- /// Deprecated.
- /// </remarks>
- public IDirectStreamProvider? DirectStreamProvider { get; set; }
-
- /// <summary>
- /// Gets or sets the path to wait for.
- /// </summary>
- public string? WaitForPath { get; set; }
-
- /// <summary>
- /// Gets a value indicating whether the request outputs video.
- /// </summary>
- public bool IsOutputVideo => Request is VideoRequestDto;
-
- /// <summary>
- /// Gets the segment length.
- /// </summary>
- public int SegmentLength
- {
- get
- {
- if (Request.SegmentLength.HasValue)
- {
- return Request.SegmentLength.Value;
- }
-
- if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
- {
- var userAgent = UserAgent ?? string.Empty;
-
- if (userAgent.Contains("AppleTV", StringComparison.OrdinalIgnoreCase)
- || userAgent.Contains("cfnetwork", StringComparison.OrdinalIgnoreCase)
- || userAgent.Contains("ipad", StringComparison.OrdinalIgnoreCase)
- || userAgent.Contains("iphone", StringComparison.OrdinalIgnoreCase)
- || userAgent.Contains("ipod", StringComparison.OrdinalIgnoreCase))
- {
- return 6;
- }
-
- if (IsSegmentedLiveStream)
- {
- return 3;
- }
-
- return 6;
- }
-
- return 3;
- }
- }
-
- /// <summary>
- /// Gets the minimum number of segments.
- /// </summary>
- public int MinSegments
- {
- get
- {
- if (Request.MinSegments.HasValue)
- {
- return Request.MinSegments.Value;
- }
-
- return SegmentLength >= 10 ? 2 : 3;
- }
- }
-
- /// <summary>
- /// Gets or sets the user agent.
- /// </summary>
- public string? UserAgent { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether to estimate the content length.
- /// </summary>
- public bool EstimateContentLength { get; set; }
-
- /// <summary>
- /// Gets or sets the transcode seek info.
- /// </summary>
- public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
-
- /// <summary>
- /// Gets or sets the transcoding job.
- /// </summary>
- public TranscodingJobDto? TranscodingJob { get; set; }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <inheritdoc />
- public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
- {
- _transcodingJobHelper.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
- }
-
- /// <summary>
- /// Disposes the stream state.
- /// </summary>
- /// <param name="disposing">Whether the object is currently being disposed.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- // REVIEW: Is this the right place for this?
- if (MediaSource.RequiresClosing
- && string.IsNullOrWhiteSpace(Request.LiveStreamId)
- && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
- {
- _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
- }
- }
-
- TranscodingJob = null;
-
- _disposed = true;
- }
-}
diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs
deleted file mode 100644
index a357498d4..000000000
--- a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using MediaBrowser.Controller.MediaEncoding;
-
-namespace Jellyfin.Api.Models.StreamingDtos;
-
-/// <summary>
-/// The audio streaming request dto.
-/// </summary>
-public class StreamingRequestDto : BaseEncodingJobOptions
-{
- /// <summary>
- /// Gets or sets the params.
- /// </summary>
- public string? Params { get; set; }
-
- /// <summary>
- /// Gets or sets the play session id.
- /// </summary>
- public string? PlaySessionId { get; set; }
-
- /// <summary>
- /// Gets or sets the tag.
- /// </summary>
- public string? Tag { get; set; }
-
- /// <summary>
- /// Gets or sets the segment container.
- /// </summary>
- public string? SegmentContainer { get; set; }
-
- /// <summary>
- /// Gets or sets the segment length.
- /// </summary>
- public int? SegmentLength { get; set; }
-
- /// <summary>
- /// Gets or sets the min segments.
- /// </summary>
- public int? MinSegments { get; set; }
-
- /// <summary>
- /// Gets or sets the position of the requested segment in ticks.
- /// </summary>
- public long CurrentRuntimeTicks { get; set; }
-
- /// <summary>
- /// Gets or sets the actual segment length in ticks.
- /// </summary>
- public long ActualSegmentLengthTicks { get; set; }
-}
diff --git a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs
deleted file mode 100644
index 8548fec1a..000000000
--- a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Jellyfin.Api.Models.StreamingDtos;
-
-/// <summary>
-/// The video request dto.
-/// </summary>
-public class VideoRequestDto : StreamingRequestDto
-{
- /// <summary>
- /// Gets a value indicating whether this instance has fixed resolution.
- /// </summary>
- /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
- public bool HasFixedResolution => Width.HasValue || Height.HasValue;
-
- /// <summary>
- /// Gets or sets a value indicating whether to enable subtitles in the manifest.
- /// </summary>
- public bool EnableSubtitlesInManifest { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether to enable trickplay images.
- /// </summary>
- public bool EnableTrickplay { get; set; }
-}