diff options
| author | Patrick Barron <barronpm@gmail.com> | 2023-10-31 13:26:37 -0400 |
|---|---|---|
| committer | Patrick Barron <barronpm@gmail.com> | 2023-12-21 12:53:50 -0500 |
| commit | 9215a4d40ae24e5996a5e16dfa296b09a7befc40 (patch) | |
| tree | 9412154b3cf4f7a654a8878d23d2f7a3a1d18b5f | |
| parent | c2081955c8b2a81eb214f321697d3462709164e0 (diff) | |
Add ITranscodeManager service
21 files changed, 305 insertions, 329 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index dce56e0a4..f385f6a51 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -76,6 +76,7 @@ using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Subtitles; +using MediaBrowser.MediaEncoding.Transcoding; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; @@ -583,7 +584,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); - serviceCollection.AddSingleton<TranscodingJobHelper>(); + serviceCollection.AddSingleton<ITranscodeManager, TranscodeManager>(); serviceCollection.AddScoped<MediaInfoHelper>(); serviceCollection.AddScoped<AudioHelper>(); serviceCollection.AddScoped<DynamicHlsHelper>(); 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/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 260ed4787..dda1e9d56 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -9,6 +9,7 @@ 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.StreamingDtos; using Jellyfin.Data.Enums; @@ -18,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; @@ -50,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; @@ -66,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> @@ -78,7 +80,7 @@ public class DynamicHlsController : BaseJellyfinApiController IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, ILogger<DynamicHlsController> logger, DynamicHlsHelper dynamicHlsHelper, EncodingHelper encodingHelper, @@ -90,7 +92,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; _fileSystem = fileSystem; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _logger = logger; _dynamicHlsHelper = dynamicHlsHelper; _encodingHelper = encodingHelper; @@ -282,7 +284,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, TranscodingJobType, cancellationToken) .ConfigureAwait(false); @@ -292,7 +294,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (!System.IO.File.Exists(playlistPath)) { - var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); + var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -301,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); @@ -330,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); @@ -1382,7 +1384,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, TranscodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -1420,7 +1422,7 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, TranscodingJobType, cancellationToken) .ConfigureAwait(false); @@ -1435,12 +1437,12 @@ public class DynamicHlsController : BaseJellyfinApiController 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; @@ -1449,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); @@ -1487,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) @@ -1498,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); } @@ -1516,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); @@ -1533,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); } @@ -2000,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; @@ -2011,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/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/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 425086895..a40f273ae 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -24,6 +24,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -47,7 +48,7 @@ public class LiveTvController : BaseJellyfinApiController 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. @@ -59,7 +60,7 @@ public class LiveTvController : BaseJellyfinApiController /// <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, IUserManager userManager, @@ -68,7 +69,7 @@ public class LiveTvController : BaseJellyfinApiController IDtoService dtoService, IMediaSourceManager mediaSourceManager, IConfigurationManager configurationManager, - TranscodingJobHelper transcodingJobHelper) + ITranscodeManager transcodeManager) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -77,7 +78,7 @@ public class LiveTvController : BaseJellyfinApiController _dtoService = dtoService; _mediaSourceManager = mediaSourceManager; _configurationManager = configurationManager; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; } /// <summary> @@ -1171,7 +1172,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/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/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/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 5d9868eb9..c231c147f 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -11,7 +11,6 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -20,6 +19,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 +43,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 +58,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 +68,7 @@ public class VideosController : BaseJellyfinApiController IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, IHttpClientFactory httpClientFactory, EncodingHelper encodingHelper) { @@ -78,7 +78,7 @@ public class VideosController : BaseJellyfinApiController _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _httpClientFactory = httpClientFactory; _encodingHelper = encodingHelper; } @@ -427,7 +427,7 @@ public class VideosController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _transcodingJobHelper, + _transcodeManager, _transcodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -466,7 +466,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 +482,7 @@ public class VideosController : BaseJellyfinApiController state, isHeadRequest, HttpContext, - _transcodingJobHelper, + _transcodeManager, ffmpegCommandLineArguments, _transcodingJobType, cancellationTokenSource).ConfigureAwait(false); 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 fafa2c055..5385979d4 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -4,8 +4,9 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -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; @@ -64,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> @@ -73,7 +74,7 @@ public static class FileStreamResponseHelpers StreamState state, bool isHeadRequest, HttpContext httpContext, - TranscodingJobHelper transcodingJobHelper, + ITranscodeManager transcodeManager, string ffmpegCommandLineArguments, TranscodingJobType transcodingJobType, CancellationTokenSource cancellationTokenSource) @@ -92,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 { 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/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 18088483d..98ea844a9 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -15,7 +15,7 @@ public class ProgressiveFileStream : Stream { private readonly Stream _stream; private readonly TranscodingJob? _job; - private readonly TranscodingJobHelper? _transcodingJobHelper; + private readonly ITranscodeManager? _transcodeManager; private readonly int _timeoutMs; private bool _disposed; @@ -24,12 +24,12 @@ public class ProgressiveFileStream : Stream /// </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="transcodeManager">The transcode manager.</param> /// <param name="timeoutMs">The timeout duration in milliseconds.</param> - public ProgressiveFileStream(string filePath, TranscodingJob? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000) + public ProgressiveFileStream(string filePath, TranscodingJob? job, ITranscodeManager transcodeManager, int timeoutMs = 30000) { _job = job; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; _timeoutMs = timeoutMs; _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); @@ -43,7 +43,7 @@ public class ProgressiveFileStream : Stream public ProgressiveFileStream(Stream stream, int timeoutMs = 30000) { _job = null; - _transcodingJobHelper = null; + _transcodeManager = null; _timeoutMs = timeoutMs; _stream = stream; } @@ -153,7 +153,7 @@ public class ProgressiveFileStream : Stream if (_job is not null) { - _transcodingJobHelper?.OnTranscodeEndRequest(_job); + _transcodeManager?.OnTranscodeEndRequest(_job); } } } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 71c62b235..78943f7b5 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, @@ -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/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/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs new file mode 100644 index 000000000..c19a12ae7 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs @@ -0,0 +1,104 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Streaming; + +namespace MediaBrowser.Controller.MediaEncoding; + +/// <summary> +/// A service for managing media transcoding. +/// </summary> +public interface ITranscodeManager +{ + /// <summary> + /// Get transcoding job. + /// </summary> + /// <param name="playSessionId">Playback session id.</param> + /// <returns>The transcoding job.</returns> + public TranscodingJob? GetTranscodingJob(string playSessionId); + + /// <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 TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type); + + /// <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); + + /// <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); + + /// <summary> + /// Report the transcoding progress to the session manager. + /// </summary> + /// <param name="job">The <see cref="TranscodingJob"/> 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( + TranscodingJob job, + StreamState state, + TimeSpan? transcodingPosition, + float? framerate, + double? percentComplete, + long? bytesTranscoded, + int? bitRate); + + /// <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="userId">The user id.</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 Task<TranscodingJob> StartFfMpeg( + StreamState state, + string outputPath, + string commandLineArguments, + Guid userId, + TranscodingJobType transcodingJobType, + CancellationTokenSource cancellationTokenSource, + string? workingDirectory = null); + + /// <summary> + /// Called when [transcode begin request]. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="type">The type.</param> + /// <returns>The <see cref="TranscodingJob"/>.</returns> + public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type); + + /// <summary> + /// Called when [transcode end]. + /// </summary> + /// <param name="job">The transcode job.</param> + public void OnTranscodeEndRequest(TranscodingJob job); + + /// <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); +} diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/MediaBrowser.Controller/Streaming/StreamState.cs index 439f8052c..b5dbe29ec 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/MediaBrowser.Controller/Streaming/StreamState.cs @@ -1,10 +1,9 @@ using System; -using Jellyfin.Api.Helpers; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; -namespace Jellyfin.Api.Models.StreamingDtos; +namespace MediaBrowser.Controller.Streaming; /// <summary> /// The stream state dto. @@ -12,7 +11,7 @@ namespace Jellyfin.Api.Models.StreamingDtos; public class StreamState : EncodingJobInfo, IDisposable { private readonly IMediaSourceManager _mediaSourceManager; - private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly ITranscodeManager _transcodeManager; private bool _disposed; /// <summary> @@ -20,12 +19,12 @@ public class StreamState : EncodingJobInfo, IDisposable /// </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) + /// <param name="transcodeManager">The <see cref="ITranscodeManager" /> singleton.</param> + public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, ITranscodeManager transcodeManager) : base(transcodingType) { _mediaSourceManager = mediaSourceManager; - _transcodingJobHelper = transcodingJobHelper; + _transcodeManager = transcodeManager; } /// <summary> @@ -152,7 +151,7 @@ public class StreamState : EncodingJobInfo, IDisposable /// <inheritdoc /> public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { - _transcodingJobHelper.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); + _transcodeManager.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); } /// <summary> diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs index a357498d4..e47ef65f0 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs +++ b/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs @@ -1,6 +1,6 @@ using MediaBrowser.Controller.MediaEncoding; -namespace Jellyfin.Api.Models.StreamingDtos; +namespace MediaBrowser.Controller.Streaming; /// <summary> /// The audio streaming request dto. diff --git a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs b/MediaBrowser.Controller/Streaming/VideoRequestDto.cs index 8548fec1a..44dc831fd 100644 --- a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs +++ b/MediaBrowser.Controller/Streaming/VideoRequestDto.cs @@ -1,4 +1,4 @@ -namespace Jellyfin.Api.Models.StreamingDtos; +namespace MediaBrowser.Controller.Streaming; /// <summary> /// The video request dto. diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 9a6ec17fd..483d0a1d8 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -8,8 +8,6 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Extensions; -using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -18,93 +16,77 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Streaming; 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; +namespace MediaBrowser.MediaEncoding.Transcoding; -/// <summary> -/// Transcoding job helpers. -/// </summary> -public class TranscodingJobHelper : IDisposable +/// <inheritdoc cref="ITranscodeManager"/> +public sealed class TranscodeManager : ITranscodeManager, IDisposable { - /// <summary> - /// The active transcoding jobs. - /// </summary> - private static readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>(); - - /// <summary> - /// The transcoding locks. - /// </summary> - private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>(); - - private readonly IAttachmentExtractor _attachmentExtractor; + private readonly ILoggerFactory _loggerFactory; + private readonly ILogger<TranscodeManager> _logger; + private readonly IFileSystem _fileSystem; private readonly IApplicationPaths _appPaths; + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; 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; + private readonly IAttachmentExtractor _attachmentExtractor; + + private readonly List<TranscodingJob> _activeTranscodingJobs = new(); + private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new(); /// <summary> - /// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class. + /// Initializes a new instance of the <see cref="TranscodeManager"/> 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, + /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> + /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param> + /// <param name="appPaths">The <see cref="IApplicationPaths"/>.</param> + /// <param name="serverConfigurationManager">The <see cref="IServerConfigurationManager"/>.</param> + /// <param name="userManager">The <see cref="IUserManager"/>.</param> + /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param> + /// <param name="encodingHelper">The <see cref="EncodingHelper"/>.</param> + /// <param name="mediaEncoder">The <see cref="IMediaEncoder"/>.</param> + /// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/>.</param> + /// <param name="attachmentExtractor">The <see cref="IAttachmentExtractor"/>.</param> + public TranscodeManager( + ILoggerFactory loggerFactory, IFileSystem fileSystem, - IMediaEncoder mediaEncoder, + IApplicationPaths appPaths, IServerConfigurationManager serverConfigurationManager, + IUserManager userManager, ISessionManager sessionManager, EncodingHelper encodingHelper, - ILoggerFactory loggerFactory, - IUserManager userManager) + IMediaEncoder mediaEncoder, + IMediaSourceManager mediaSourceManager, + IAttachmentExtractor attachmentExtractor) { - _attachmentExtractor = attachmentExtractor; - _appPaths = appPaths; - _logger = logger; - _mediaSourceManager = mediaSourceManager; + _loggerFactory = loggerFactory; _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; + _appPaths = appPaths; _serverConfigurationManager = serverConfigurationManager; + _userManager = userManager; _sessionManager = sessionManager; _encodingHelper = encodingHelper; - _loggerFactory = loggerFactory; - _userManager = userManager; + _mediaEncoder = mediaEncoder; + _mediaSourceManager = mediaSourceManager; + _attachmentExtractor = attachmentExtractor; + _logger = loggerFactory.CreateLogger<TranscodeManager>(); DeleteEncodedMediaCache(); - - sessionManager.PlaybackProgress += OnPlaybackProgress; - sessionManager.PlaybackStart += OnPlaybackProgress; + _sessionManager.PlaybackProgress += OnPlaybackProgress; + _sessionManager.PlaybackStart += OnPlaybackProgress; } - /// <summary> - /// Get transcoding job. - /// </summary> - /// <param name="playSessionId">Playback session id.</param> - /// <returns>The transcoding job.</returns> + /// <inheritdoc /> public TranscodingJob? GetTranscodingJob(string playSessionId) { lock (_activeTranscodingJobs) @@ -113,12 +95,7 @@ public class TranscodingJobHelper : IDisposable } } - /// <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> + /// <inheritdoc /> public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type) { lock (_activeTranscodingJobs) @@ -127,12 +104,7 @@ public class TranscodingJobHelper : IDisposable } } - /// <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> + /// <inheritdoc /> public void PingTranscodingJob(string playSessionId, bool? isUserPaused) { ArgumentException.ThrowIfNullOrEmpty(playSessionId); @@ -189,10 +161,6 @@ public class TranscodingJobHelper : IDisposable } } - /// <summary> - /// Called when [transcode kill timer stopped]. - /// </summary> - /// <param name="state">The state.</param> private async void OnTranscodeKillTimerStopped(object? state) { var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(state)); @@ -212,43 +180,21 @@ public class TranscodingJobHelper : IDisposable 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> + /// <inheritdoc /> 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<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles) - { var jobs = new List<TranscodingJob>(); lock (_activeTranscodingJobs) { // This is really only needed for HLS. // Progressive streams can stop on their own reliably. - jobs.AddRange(_activeTranscodingJobs.Where(killJob)); + jobs.AddRange(_activeTranscodingJobs.Where(j => string.IsNullOrWhiteSpace(playSessionId) + ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase) + : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase))); } - if (jobs.Count == 0) - { - return Task.CompletedTask; - } + return Task.WhenAll(GetKillJobs()); IEnumerable<Task> GetKillJobs() { @@ -257,16 +203,8 @@ public class TranscodingJobHelper : IDisposable 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(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete) { job.DisposeKillTimer(); @@ -353,10 +291,6 @@ public class TranscodingJobHelper : IDisposable } } - /// <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)) @@ -365,10 +299,6 @@ public class TranscodingJobHelper : IDisposable } } - /// <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) @@ -400,16 +330,7 @@ public class TranscodingJobHelper : IDisposable } } - /// <summary> - /// Report the transcoding progress to the session manager. - /// </summary> - /// <param name="job">The <see cref="TranscodingJob"/> 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> + /// <inheritdoc /> public void ReportTranscodingProgress( TranscodingJob job, StreamState state, @@ -462,22 +383,12 @@ public class TranscodingJobHelper : IDisposable } } - /// <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> + /// <inheritdoc /> public async Task<TranscodingJob> StartFfMpeg( StreamState state, string outputPath, string commandLineArguments, - HttpRequest request, + Guid userId, TranscodingJobType transcodingJobType, CancellationTokenSource cancellationTokenSource, string? workingDirectory = null) @@ -489,7 +400,6 @@ public class TranscodingJobHelper : IDisposable 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)) { @@ -567,13 +477,26 @@ public class TranscodingJobHelper : IDisposable $"{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); + 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); + var commandLineLogMessageBytes = Encoding.UTF8.GetBytes( + 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); + process.Exited += (_, _) => OnFfMpegProcessExited(process, transcodingJob, state); try { @@ -582,7 +505,6 @@ public class TranscodingJobHelper : IDisposable catch (Exception ex) { _logger.LogError(ex, "Error starting FFmpeg"); - this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); throw; @@ -637,31 +559,14 @@ public class TranscodingJobHelper : IDisposable } } - 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; - } + private static bool EnableThrottling(StreamState state) + => 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 TranscodingJob OnTranscodeBeginning( + private TranscodingJob OnTranscodeBeginning( string path, string? playSessionId, string? liveStreamId, @@ -696,10 +601,7 @@ public class TranscodingJobHelper : IDisposable } } - /// <summary> - /// Called when [transcode end]. - /// </summary> - /// <param name="job">The transcode job.</param> + /// <inheritdoc /> public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; @@ -710,16 +612,7 @@ public class TranscodingJobHelper : IDisposable } } - /// <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) + private void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state) { lock (_activeTranscodingJobs) { @@ -742,12 +635,6 @@ public class TranscodingJobHelper : IDisposable } } - /// <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, TranscodingJob job, StreamState state) { job.HasExited = true; @@ -794,44 +681,30 @@ public class TranscodingJobHelper : IDisposable } } - /// <summary> - /// Called when [transcode begin request]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - /// <returns>The <see cref="TranscodingJob"/>.</returns> + /// <inheritdoc /> public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type) { lock (_activeTranscodingJobs) { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); + var job = _activeTranscodingJobs + .FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); if (job is null) { return null; } - OnTranscodeBeginRequest(job); + job.ActiveRequestCount++; + if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive) + { + job.StopKillTimer(); + } return job; } } - private void OnTranscodeBeginRequest(TranscodingJob 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> + /// <inheritdoc /> public SemaphoreSlim GetTranscodingLock(string outputPath) { lock (_transcodingLocks) @@ -854,9 +727,6 @@ public class TranscodingJobHelper : IDisposable } } - /// <summary> - /// Deletes the encoded media cache. - /// </summary> private void DeleteEncodedMediaCache() { var path = _serverConfigurationManager.GetTranscodePath(); @@ -871,26 +741,10 @@ public class TranscodingJobHelper : IDisposable } } - /// <summary> - /// Dispose transcoding job helper. - /// </summary> + /// <inheritdoc /> 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; - } + _sessionManager.PlaybackProgress -= OnPlaybackProgress; + _sessionManager.PlaybackStart -= OnPlaybackProgress; } } |
