Add ITranscodeManager service

This commit is contained in:
Patrick Barron 2023-10-31 13:26:37 -04:00
parent c2081955c8
commit 9215a4d40a
21 changed files with 306 additions and 330 deletions

View File

@ -76,6 +76,7 @@ using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.MediaEncoding.Subtitles; using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.MediaEncoding.Transcoding;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -583,7 +584,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
serviceCollection.AddSingleton<TranscodingJobHelper>(); serviceCollection.AddSingleton<ITranscodeManager, TranscodeManager>();
serviceCollection.AddScoped<MediaInfoHelper>(); serviceCollection.AddScoped<MediaInfoHelper>();
serviceCollection.AddScoped<AudioHelper>(); serviceCollection.AddScoped<AudioHelper>();
serviceCollection.AddScoped<DynamicHlsHelper>(); serviceCollection.AddScoped<DynamicHlsHelper>();

View File

@ -6,6 +6,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@ -9,6 +9,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@ -18,6 +19,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -50,7 +52,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly ILogger<DynamicHlsController> _logger; private readonly ILogger<DynamicHlsController> _logger;
private readonly EncodingHelper _encodingHelper; private readonly EncodingHelper _encodingHelper;
private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator; 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="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> 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="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="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
@ -78,7 +80,7 @@ public class DynamicHlsController : BaseJellyfinApiController
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
ILogger<DynamicHlsController> logger, ILogger<DynamicHlsController> logger,
DynamicHlsHelper dynamicHlsHelper, DynamicHlsHelper dynamicHlsHelper,
EncodingHelper encodingHelper, EncodingHelper encodingHelper,
@ -90,7 +92,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_logger = logger; _logger = logger;
_dynamicHlsHelper = dynamicHlsHelper; _dynamicHlsHelper = dynamicHlsHelper;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
@ -282,7 +284,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -292,7 +294,7 @@ public class DynamicHlsController : BaseJellyfinApiController
if (!System.IO.File.Exists(playlistPath)) if (!System.IO.File.Exists(playlistPath))
{ {
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath);
await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try try
{ {
@ -301,11 +303,11 @@ public class DynamicHlsController : BaseJellyfinApiController
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
job = await _transcodingJobHelper.StartFfMpeg( job = await _transcodeManager.StartFfMpeg(
state, state,
playlistPath, playlistPath,
GetCommandLineArguments(playlistPath, state, true, 0), GetCommandLineArguments(playlistPath, state, true, 0),
Request, Request.HttpContext.User.GetUserId(),
TranscodingJobType, TranscodingJobType,
cancellationTokenSource) cancellationTokenSource)
.ConfigureAwait(false); .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) if (job is not null)
{ {
_transcodingJobHelper.OnTranscodeEndRequest(job); _transcodeManager.OnTranscodeEndRequest(job);
} }
var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state); var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state);
@ -1382,7 +1384,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -1420,7 +1422,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -1435,12 +1437,12 @@ public class DynamicHlsController : BaseJellyfinApiController
if (System.IO.File.Exists(segmentPath)) 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); _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); 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); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
var released = false; var released = false;
var startTranscoding = false; var startTranscoding = false;
@ -1449,7 +1451,7 @@ public class DynamicHlsController : BaseJellyfinApiController
{ {
if (System.IO.File.Exists(segmentPath)) if (System.IO.File.Exists(segmentPath))
{ {
job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
transcodingLock.Release(); transcodingLock.Release();
released = true; released = true;
_logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); _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 // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
await _transcodingJobHelper.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false) await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false)
.ConfigureAwait(false); .ConfigureAwait(false);
if (currentTranscodingIndex.HasValue) if (currentTranscodingIndex.HasValue)
@ -1498,11 +1500,11 @@ public class DynamicHlsController : BaseJellyfinApiController
streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks; streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks;
state.WaitForPath = segmentPath; state.WaitForPath = segmentPath;
job = await _transcodingJobHelper.StartFfMpeg( job = await _transcodeManager.StartFfMpeg(
state, state,
playlistPath, playlistPath,
GetCommandLineArguments(playlistPath, state, false, segmentId), GetCommandLineArguments(playlistPath, state, false, segmentId),
Request, Request.HttpContext.User.GetUserId(),
TranscodingJobType, TranscodingJobType,
cancellationTokenSource).ConfigureAwait(false); cancellationTokenSource).ConfigureAwait(false);
} }
@ -1516,7 +1518,7 @@ public class DynamicHlsController : BaseJellyfinApiController
} }
else else
{ {
job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
if (job?.TranscodingThrottler is not null) if (job?.TranscodingThrottler is not null)
{ {
await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false); await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false);
@ -1533,7 +1535,7 @@ public class DynamicHlsController : BaseJellyfinApiController
} }
_logger.LogDebug("returning {0} [general case]", segmentPath); _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); 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) if (transcodingJob is not null)
{ {
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
_transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); _transcodeManager.OnTranscodeEndRequest(transcodingJob);
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -2011,7 +2013,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) 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) if (job is null || job.HasExited)
{ {

View File

@ -24,22 +24,22 @@ public class HlsSegmentController : BaseJellyfinApiController
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HlsSegmentController"/> class. /// Initializes a new instance of the <see cref="HlsSegmentController"/> class.
/// </summary> /// </summary>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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( public HlsSegmentController(
IFileSystem fileSystem, IFileSystem fileSystem,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
TranscodingJobHelper transcodingJobHelper) ITranscodeManager transcodeManager)
{ {
_fileSystem = fileSystem; _fileSystem = fileSystem;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -112,7 +112,7 @@ public class HlsSegmentController : BaseJellyfinApiController
[FromQuery, Required] string deviceId, [FromQuery, Required] string deviceId,
[FromQuery, Required] string playSessionId) [FromQuery, Required] string playSessionId)
{ {
_transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true); _transcodeManager.KillTranscodingJobs(deviceId, playSessionId, _ => true);
return NoContent(); return NoContent();
} }
@ -174,13 +174,13 @@ public class HlsSegmentController : BaseJellyfinApiController
private ActionResult GetFileResult(string path, string playlistPath) private ActionResult GetFileResult(string path, string playlistPath)
{ {
var transcodingJob = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); var transcodingJob = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
Response.OnCompleted(() => Response.OnCompleted(() =>
{ {
if (transcodingJob is not null) if (transcodingJob is not null)
{ {
_transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); _transcodeManager.OnTranscodeEndRequest(transcodingJob);
} }
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -24,6 +24,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
@ -47,7 +48,7 @@ public class LiveTvController : BaseJellyfinApiController
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _configurationManager; private readonly IConfigurationManager _configurationManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LiveTvController"/> class. /// 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="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> 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="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( public LiveTvController(
ILiveTvManager liveTvManager, ILiveTvManager liveTvManager,
IUserManager userManager, IUserManager userManager,
@ -68,7 +69,7 @@ public class LiveTvController : BaseJellyfinApiController
IDtoService dtoService, IDtoService dtoService,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IConfigurationManager configurationManager, IConfigurationManager configurationManager,
TranscodingJobHelper transcodingJobHelper) ITranscodeManager transcodeManager)
{ {
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
_userManager = userManager; _userManager = userManager;
@ -77,7 +78,7 @@ public class LiveTvController : BaseJellyfinApiController
_dtoService = dtoService; _dtoService = dtoService;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -1171,7 +1172,7 @@ public class LiveTvController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper); var stream = new ProgressiveFileStream(path, null, _transcodeManager);
return new FileStreamResult(stream, MimeTypes.GetMimeType(path)); return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
} }

View File

@ -8,6 +8,7 @@ using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
@ -30,7 +31,7 @@ public class PlaystateController : BaseJellyfinApiController
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ILogger<PlaystateController> _logger; private readonly ILogger<PlaystateController> _logger;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PlaystateController"/> class. /// 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="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> 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="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( public PlaystateController(
IUserManager userManager, IUserManager userManager,
IUserDataManager userDataRepository, IUserDataManager userDataRepository,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ISessionManager sessionManager, ISessionManager sessionManager,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
TranscodingJobHelper transcodingJobHelper) ITranscodeManager transcodeManager)
{ {
_userManager = userManager; _userManager = userManager;
_userDataRepository = userDataRepository; _userDataRepository = userDataRepository;
@ -55,7 +56,7 @@ public class PlaystateController : BaseJellyfinApiController
_sessionManager = sessionManager; _sessionManager = sessionManager;
_logger = loggerFactory.CreateLogger<PlaystateController>(); _logger = loggerFactory.CreateLogger<PlaystateController>();
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -188,7 +189,7 @@ public class PlaystateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId) public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId)
{ {
_transcodingJobHelper.PingTranscodingJob(playSessionId, null); _transcodeManager.PingTranscodingJob(playSessionId, null);
return NoContent(); return NoContent();
} }
@ -205,7 +206,7 @@ public class PlaystateController : BaseJellyfinApiController
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) 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); 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); _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) 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); playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
@ -388,7 +389,7 @@ public class PlaystateController : BaseJellyfinApiController
{ {
if (method == PlayMethod.Transcode) 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) if (job is null)
{ {
return PlayMethod.DirectPlay; return PlayMethod.DirectPlay;

View File

@ -11,6 +11,7 @@ using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -11,7 +11,6 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -20,6 +19,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -43,7 +43,7 @@ public class VideosController : BaseJellyfinApiController
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly EncodingHelper _encodingHelper; 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="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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="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="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public VideosController( public VideosController(
@ -68,7 +68,7 @@ public class VideosController : BaseJellyfinApiController
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
EncodingHelper encodingHelper) EncodingHelper encodingHelper)
{ {
@ -78,7 +78,7 @@ public class VideosController : BaseJellyfinApiController
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
} }
@ -427,7 +427,7 @@ public class VideosController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
_transcodingJobType, _transcodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -466,7 +466,7 @@ public class VideosController : BaseJellyfinApiController
if (state.MediaSource.IsInfiniteStream) if (state.MediaSource.IsInfiniteStream)
{ {
var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper); var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager);
return File(liveStream, contentType); return File(liveStream, contentType);
} }
@ -482,7 +482,7 @@ public class VideosController : BaseJellyfinApiController
state, state,
isHeadRequest, isHeadRequest,
HttpContext, HttpContext,
_transcodingJobHelper, _transcodeManager,
ffmpegCommandLineArguments, ffmpegCommandLineArguments,
_transcodingJobType, _transcodingJobType,
cancellationTokenSource).ConfigureAwait(false); cancellationTokenSource).ConfigureAwait(false);

View File

@ -2,13 +2,13 @@
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -26,7 +26,7 @@ public class AudioHelper
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EncodingHelper _encodingHelper; private readonly EncodingHelper _encodingHelper;
@ -39,7 +39,7 @@ public class AudioHelper
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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="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="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
@ -49,7 +49,7 @@ public class AudioHelper
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
EncodingHelper encodingHelper) EncodingHelper encodingHelper)
@ -59,7 +59,7 @@ public class AudioHelper
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
@ -94,7 +94,7 @@ public class AudioHelper
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
transcodingJobType, transcodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -133,7 +133,7 @@ public class AudioHelper
if (state.MediaSource.IsInfiniteStream) 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); return new FileStreamResult(stream, contentType);
} }
@ -149,7 +149,7 @@ public class AudioHelper
state, state,
isHeadRequest, isHeadRequest,
_httpContextAccessor.HttpContext, _httpContextAccessor.HttpContext,
_transcodingJobHelper, _transcodeManager,
ffmpegCommandLineArguments, ffmpegCommandLineArguments,
transcodingJobType, transcodingJobType,
cancellationTokenSource).ConfigureAwait(false); cancellationTokenSource).ConfigureAwait(false);

View File

@ -8,7 +8,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -18,6 +17,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Controller.Trickplay; using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -39,7 +39,7 @@ public class DynamicHlsHelper
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly ILogger<DynamicHlsHelper> _logger; private readonly ILogger<DynamicHlsHelper> _logger;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
@ -54,7 +54,7 @@ public class DynamicHlsHelper
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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="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="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> 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> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
@ -66,7 +66,7 @@ public class DynamicHlsHelper
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
INetworkManager networkManager, INetworkManager networkManager,
ILogger<DynamicHlsHelper> logger, ILogger<DynamicHlsHelper> logger,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
@ -78,7 +78,7 @@ public class DynamicHlsHelper
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_networkManager = networkManager; _networkManager = networkManager;
_logger = logger; _logger = logger;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
@ -130,7 +130,7 @@ public class DynamicHlsHelper
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
transcodingJobType, transcodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@ -4,8 +4,9 @@ using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Extensions;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -64,7 +65,7 @@ public static class FileStreamResponseHelpers
/// <param name="state">The current <see cref="StreamState"/>.</param> /// <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="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="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="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param> /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
/// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param> /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
@ -73,7 +74,7 @@ public static class FileStreamResponseHelpers
StreamState state, StreamState state,
bool isHeadRequest, bool isHeadRequest,
HttpContext httpContext, HttpContext httpContext,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
string ffmpegCommandLineArguments, string ffmpegCommandLineArguments,
TranscodingJobType transcodingJobType, TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource) CancellationTokenSource cancellationTokenSource)
@ -92,22 +93,28 @@ public static class FileStreamResponseHelpers
return new OkResult(); return new OkResult();
} }
var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); var transcodingLock = transcodeManager.GetTranscodingLock(outputPath);
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try try
{ {
TranscodingJob? job; TranscodingJob? job;
if (!File.Exists(outputPath)) 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 else
{ {
job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); job = transcodeManager.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
state.Dispose(); state.Dispose();
} }
var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper); var stream = new ProgressiveFileStream(outputPath, job, transcodeManager);
return new FileStreamResult(stream, contentType); return new FileStreamResult(stream, contentType);
} }
finally finally

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -15,7 +15,7 @@ public class ProgressiveFileStream : Stream
{ {
private readonly Stream _stream; private readonly Stream _stream;
private readonly TranscodingJob? _job; private readonly TranscodingJob? _job;
private readonly TranscodingJobHelper? _transcodingJobHelper; private readonly ITranscodeManager? _transcodeManager;
private readonly int _timeoutMs; private readonly int _timeoutMs;
private bool _disposed; private bool _disposed;
@ -24,12 +24,12 @@ public class ProgressiveFileStream : Stream
/// </summary> /// </summary>
/// <param name="filePath">The path to the transcoded file.</param> /// <param name="filePath">The path to the transcoded file.</param>
/// <param name="job">The transcoding job information.</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> /// <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; _job = job;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_timeoutMs = timeoutMs; _timeoutMs = timeoutMs;
_stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); _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) public ProgressiveFileStream(Stream stream, int timeoutMs = 30000)
{ {
_job = null; _job = null;
_transcodingJobHelper = null; _transcodeManager = null;
_timeoutMs = timeoutMs; _timeoutMs = timeoutMs;
_stream = stream; _stream = stream;
} }
@ -153,7 +153,7 @@ public class ProgressiveFileStream : Stream
if (_job is not null) if (_job is not null)
{ {
_transcodingJobHelper?.OnTranscodeEndRequest(_job); _transcodeManager?.OnTranscodeEndRequest(_job);
} }
} }
} }

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -14,6 +13,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; 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="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</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="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns> /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
@ -51,7 +51,7 @@ public static class StreamingHelpers
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
EncodingHelper encodingHelper, EncodingHelper encodingHelper,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
TranscodingJobType transcodingJobType, TranscodingJobType transcodingJobType,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@ -74,7 +74,7 @@ public static class StreamingHelpers
streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
} }
var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) var state = new StreamState(mediaSourceManager, transcodingJobType, transcodeManager)
{ {
Request = streamingRequest, Request = streamingRequest,
RequestedUrl = url, RequestedUrl = url,
@ -115,7 +115,7 @@ public static class StreamingHelpers
if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
{ {
var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId) var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId) ? transcodeManager.GetTranscodingJob(streamingRequest.PlaySessionId)
: null; : null;
if (currentJob is not null) if (currentJob is not null)

View File

@ -1,4 +1,6 @@
namespace Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Controller.Streaming;
namespace Jellyfin.Api.Models.StreamingDtos;
/// <summary> /// <summary>
/// The hls video request dto. /// The hls video request dto.

View File

@ -1,4 +1,6 @@
namespace Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Controller.Streaming;
namespace Jellyfin.Api.Models.StreamingDtos;
/// <summary> /// <summary>
/// The hls video request dto. /// The hls video request dto.

View File

@ -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);
}

View File

@ -1,10 +1,9 @@
using System; using System;
using Jellyfin.Api.Helpers;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Jellyfin.Api.Models.StreamingDtos; namespace MediaBrowser.Controller.Streaming;
/// <summary> /// <summary>
/// The stream state dto. /// The stream state dto.
@ -12,7 +11,7 @@ namespace Jellyfin.Api.Models.StreamingDtos;
public class StreamState : EncodingJobInfo, IDisposable public class StreamState : EncodingJobInfo, IDisposable
{ {
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private bool _disposed; private bool _disposed;
/// <summary> /// <summary>
@ -20,12 +19,12 @@ public class StreamState : EncodingJobInfo, IDisposable
/// </summary> /// </summary>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager" /> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager" /> interface.</param>
/// <param name="transcodingType">The <see cref="TranscodingJobType" />.</param> /// <param name="transcodingType">The <see cref="TranscodingJobType" />.</param>
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper" /> singleton.</param> /// <param name="transcodeManager">The <see cref="ITranscodeManager" /> singleton.</param>
public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper) public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, ITranscodeManager transcodeManager)
: base(transcodingType) : base(transcodingType)
{ {
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -152,7 +151,7 @@ public class StreamState : EncodingJobInfo, IDisposable
/// <inheritdoc /> /// <inheritdoc />
public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) 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> /// <summary>

View File

@ -1,6 +1,6 @@
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
namespace Jellyfin.Api.Models.StreamingDtos; namespace MediaBrowser.Controller.Streaming;
/// <summary> /// <summary>
/// The audio streaming request dto. /// The audio streaming request dto.

View File

@ -1,4 +1,4 @@
namespace Jellyfin.Api.Models.StreamingDtos; namespace MediaBrowser.Controller.Streaming;
/// <summary> /// <summary>
/// The video request dto. /// The video request dto.

View File

@ -8,8 +8,6 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -18,93 +16,77 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Helpers; namespace MediaBrowser.MediaEncoding.Transcoding;
/// <summary> /// <inheritdoc cref="ITranscodeManager"/>
/// Transcoding job helpers. public sealed class TranscodeManager : ITranscodeManager, IDisposable
/// </summary>
public class TranscodingJobHelper : IDisposable
{ {
/// <summary> private readonly ILoggerFactory _loggerFactory;
/// The active transcoding jobs. private readonly ILogger<TranscodeManager> _logger;
/// </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 IApplicationPaths _appPaths;
private readonly EncodingHelper _encodingHelper;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger<TranscodingJobHelper> _logger; private readonly IApplicationPaths _appPaths;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IUserManager _userManager;
private readonly ISessionManager _sessionManager;
private readonly EncodingHelper _encodingHelper;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IAttachmentExtractor _attachmentExtractor;
private readonly ISessionManager _sessionManager;
private readonly ILoggerFactory _loggerFactory; private readonly List<TranscodingJob> _activeTranscodingJobs = new();
private readonly IUserManager _userManager; private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class. /// Initializes a new instance of the <see cref="TranscodeManager"/> class.
/// </summary> /// </summary>
/// <param name="attachmentExtractor">Instance of the <see cref="IAttachmentExtractor"/> interface.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param>
/// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobHelpers}"/> interface.</param> /// <param name="appPaths">The <see cref="IApplicationPaths"/>.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="serverConfigurationManager">The <see cref="IServerConfigurationManager"/>.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="userManager">The <see cref="IUserManager"/>.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="encodingHelper">The <see cref="EncodingHelper"/>.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> /// <param name="mediaEncoder">The <see cref="IMediaEncoder"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/>.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> /// <param name="attachmentExtractor">The <see cref="IAttachmentExtractor"/>.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> public TranscodeManager(
public TranscodingJobHelper( ILoggerFactory loggerFactory,
IAttachmentExtractor attachmentExtractor,
IApplicationPaths appPaths,
ILogger<TranscodingJobHelper> logger,
IMediaSourceManager mediaSourceManager,
IFileSystem fileSystem, IFileSystem fileSystem,
IMediaEncoder mediaEncoder, IApplicationPaths appPaths,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IUserManager userManager,
ISessionManager sessionManager, ISessionManager sessionManager,
EncodingHelper encodingHelper, EncodingHelper encodingHelper,
ILoggerFactory loggerFactory, IMediaEncoder mediaEncoder,
IUserManager userManager) IMediaSourceManager mediaSourceManager,
IAttachmentExtractor attachmentExtractor)
{ {
_attachmentExtractor = attachmentExtractor; _loggerFactory = loggerFactory;
_appPaths = appPaths;
_logger = logger;
_mediaSourceManager = mediaSourceManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder; _appPaths = appPaths;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_userManager = userManager;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
_loggerFactory = loggerFactory; _mediaEncoder = mediaEncoder;
_userManager = userManager; _mediaSourceManager = mediaSourceManager;
_attachmentExtractor = attachmentExtractor;
_logger = loggerFactory.CreateLogger<TranscodeManager>();
DeleteEncodedMediaCache(); DeleteEncodedMediaCache();
_sessionManager.PlaybackProgress += OnPlaybackProgress;
sessionManager.PlaybackProgress += OnPlaybackProgress; _sessionManager.PlaybackStart += OnPlaybackProgress;
sessionManager.PlaybackStart += OnPlaybackProgress;
} }
/// <summary> /// <inheritdoc />
/// Get transcoding job.
/// </summary>
/// <param name="playSessionId">Playback session id.</param>
/// <returns>The transcoding job.</returns>
public TranscodingJob? GetTranscodingJob(string playSessionId) public TranscodingJob? GetTranscodingJob(string playSessionId)
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
@ -113,12 +95,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// 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) public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type)
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
@ -127,12 +104,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// 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) public void PingTranscodingJob(string playSessionId, bool? isUserPaused)
{ {
ArgumentException.ThrowIfNullOrEmpty(playSessionId); 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) private async void OnTranscodeKillTimerStopped(object? state)
{ {
var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(state)); var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(state));
@ -212,29 +180,8 @@ public class TranscodingJobHelper : IDisposable
await KillTranscodingJob(job, true, path => true).ConfigureAwait(false); await KillTranscodingJob(job, true, path => true).ConfigureAwait(false);
} }
/// <summary> /// <inheritdoc />
/// 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) 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>(); var jobs = new List<TranscodingJob>();
@ -242,13 +189,12 @@ public class TranscodingJobHelper : IDisposable
{ {
// This is really only needed for HLS. // This is really only needed for HLS.
// Progressive streams can stop on their own reliably. // 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.WhenAll(GetKillJobs());
{
return Task.CompletedTask;
}
IEnumerable<Task> GetKillJobs() IEnumerable<Task> GetKillJobs()
{ {
@ -257,16 +203,8 @@ public class TranscodingJobHelper : IDisposable
yield return KillTranscodingJob(job, false, deleteFiles); 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) private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete)
{ {
job.DisposeKillTimer(); 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) private void DeleteProgressivePartialStreamFiles(string outputFilePath)
{ {
if (File.Exists(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) private void DeleteHlsPartialStreamFiles(string outputFilePath)
{ {
var directory = Path.GetDirectoryName(outputFilePath) var directory = Path.GetDirectoryName(outputFilePath)
@ -400,16 +330,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// 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( public void ReportTranscodingProgress(
TranscodingJob job, TranscodingJob job,
StreamState state, StreamState state,
@ -462,22 +383,12 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// 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<TranscodingJob> StartFfMpeg( public async Task<TranscodingJob> StartFfMpeg(
StreamState state, StreamState state,
string outputPath, string outputPath,
string commandLineArguments, string commandLineArguments,
HttpRequest request, Guid userId,
TranscodingJobType transcodingJobType, TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource, CancellationTokenSource cancellationTokenSource,
string? workingDirectory = null) string? workingDirectory = null)
@ -489,7 +400,6 @@ public class TranscodingJobHelper : IDisposable
if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) 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); var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) 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"); $"{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. // 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 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); await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); process.Exited += (_, _) => OnFfMpegProcessExited(process, transcodingJob, state);
try try
{ {
@ -582,7 +505,6 @@ public class TranscodingJobHelper : IDisposable
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error starting FFmpeg"); _logger.LogError(ex, "Error starting FFmpeg");
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
throw; throw;
@ -637,31 +559,14 @@ public class TranscodingJobHelper : IDisposable
} }
} }
private bool EnableThrottling(StreamState state) private static bool EnableThrottling(StreamState state)
{ => state.InputProtocol == MediaProtocol.File
var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); && state.RunTimeTicks.HasValue
&& state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks
&& state.IsInputVideo
&& state.VideoType == VideoType.VideoFile;
return state.InputProtocol == MediaProtocol.File && private TranscodingJob OnTranscodeBeginning(
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(
string path, string path,
string? playSessionId, string? playSessionId,
string? liveStreamId, string? liveStreamId,
@ -696,10 +601,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Called when [transcode end].
/// </summary>
/// <param name="job">The transcode job.</param>
public void OnTranscodeEndRequest(TranscodingJob job) public void OnTranscodeEndRequest(TranscodingJob job)
{ {
job.ActiveRequestCount--; job.ActiveRequestCount--;
@ -710,16 +612,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> private void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
/// <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) 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) private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state)
{ {
job.HasExited = true; job.HasExited = true;
@ -794,44 +681,30 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// 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) public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type)
{ {
lock (_activeTranscodingJobs) 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) if (job is null)
{ {
return null; return null;
} }
OnTranscodeBeginRequest(job); job.ActiveRequestCount++;
if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
{
job.StopKillTimer();
}
return job; return job;
} }
} }
private void OnTranscodeBeginRequest(TranscodingJob job) /// <inheritdoc />
{
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) public SemaphoreSlim GetTranscodingLock(string outputPath)
{ {
lock (_transcodingLocks) lock (_transcodingLocks)
@ -854,9 +727,6 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary>
/// Deletes the encoded media cache.
/// </summary>
private void DeleteEncodedMediaCache() private void DeleteEncodedMediaCache()
{ {
var path = _serverConfigurationManager.GetTranscodePath(); var path = _serverConfigurationManager.GetTranscodePath();
@ -871,26 +741,10 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Dispose transcoding job helper.
/// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true); _sessionManager.PlaybackProgress -= OnPlaybackProgress;
GC.SuppressFinalize(this); _sessionManager.PlaybackStart -= OnPlaybackProgress;
}
/// <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;
}
} }
} }