Merge pull request #10758 from barronpm/transcode-manager

Add ITranscodeManager
This commit is contained in:
Bond-009 2023-12-29 15:39:59 +01:00 committed by GitHub
commit 98177b8649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 402 additions and 459 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

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

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,8 +9,8 @@ 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.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -19,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;
@ -51,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;
@ -67,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>
@ -79,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,
@ -91,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;
@ -283,17 +284,17 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
TranscodingJobDto? job = null; TranscodingJob? job = null;
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
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
{ {
@ -302,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);
@ -331,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);
@ -1383,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);
@ -1421,7 +1422,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -1432,16 +1433,16 @@ public class DynamicHlsController : BaseJellyfinApiController
var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
TranscodingJobDto? job; TranscodingJob? job;
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;
@ -1450,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);
@ -1488,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)
@ -1499,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);
} }
@ -1517,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);
@ -1534,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);
} }
@ -1922,7 +1923,7 @@ public class DynamicHlsController : BaseJellyfinApiController
string segmentPath, string segmentPath,
string segmentExtension, string segmentExtension,
int segmentIndex, int segmentIndex,
TranscodingJobDto? transcodingJob, TranscodingJob? transcodingJob,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var segmentExists = System.IO.File.Exists(segmentPath); var segmentExists = System.IO.File.Exists(segmentPath);
@ -1991,7 +1992,7 @@ public class DynamicHlsController : BaseJellyfinApiController
return GetSegmentResult(state, segmentPath, transcodingJob); return GetSegmentResult(state, segmentPath, transcodingJob);
} }
private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJobDto? transcodingJob) private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJob? transcodingJob)
{ {
var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks; var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks;
@ -2001,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;
@ -2012,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,8 @@ 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.Controller.Streaming;
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 +49,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 +61,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 +70,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 +79,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 +1173,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,9 +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.PlaybackDtos; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
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;
@ -65,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>
@ -74,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)
@ -93,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
{ {
TranscodingJobDto? 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

@ -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,49 +1,39 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading; using System.Threading;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Models.PlaybackDtos; namespace MediaBrowser.Controller.MediaEncoding;
/// <summary> /// <summary>
/// Class TranscodingJob. /// Class TranscodingJob.
/// </summary> /// </summary>
public class TranscodingJobDto : IDisposable public sealed class TranscodingJob : IDisposable
{ {
/// <summary> private readonly ILogger<TranscodingJob> _logger;
/// The process lock. private readonly object _processLock = new();
/// </summary> private readonly object _timerLock = new();
[SuppressMessage("Microsoft.Performance", "CA1051:NoVisibleInstanceFields", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "SA1401:PrivateField", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")] private Timer? _killTimer;
public readonly object ProcessLock = new object();
/// <summary> /// <summary>
/// Timer lock. /// Initializes a new instance of the <see cref="TranscodingJob"/> class.
/// </summary>
private readonly object _timerLock = new object();
/// <summary>
/// Initializes a new instance of the <see cref="TranscodingJobDto"/> class.
/// </summary> /// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param>
public TranscodingJobDto(ILogger<TranscodingJobDto> logger) public TranscodingJob(ILogger<TranscodingJob> logger)
{ {
Logger = logger; _logger = logger;
} }
/// <summary> /// <summary>
/// Gets or sets the play session identifier. /// Gets or sets the play session identifier.
/// </summary> /// </summary>
/// <value>The play session identifier.</value>
public string? PlaySessionId { get; set; } public string? PlaySessionId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the live stream identifier. /// Gets or sets the live stream identifier.
/// </summary> /// </summary>
/// <value>The live stream identifier.</value>
public string? LiveStreamId { get; set; } public string? LiveStreamId { get; set; }
/// <summary> /// <summary>
@ -54,7 +44,6 @@ public class TranscodingJobDto : IDisposable
/// <summary> /// <summary>
/// Gets or sets the path. /// Gets or sets the path.
/// </summary> /// </summary>
/// <value>The path.</value>
public MediaSourceInfo? MediaSource { get; set; } public MediaSourceInfo? MediaSource { get; set; }
/// <summary> /// <summary>
@ -65,32 +54,18 @@ public class TranscodingJobDto : IDisposable
/// <summary> /// <summary>
/// Gets or sets the type. /// Gets or sets the type.
/// </summary> /// </summary>
/// <value>The type.</value>
public TranscodingJobType Type { get; set; } public TranscodingJobType Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the process. /// Gets or sets the process.
/// </summary> /// </summary>
/// <value>The process.</value>
public Process? Process { get; set; } public Process? Process { get; set; }
/// <summary>
/// Gets logger.
/// </summary>
public ILogger<TranscodingJobDto> Logger { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the active request count. /// Gets or sets the active request count.
/// </summary> /// </summary>
/// <value>The active request count.</value>
public int ActiveRequestCount { get; set; } public int ActiveRequestCount { get; set; }
/// <summary>
/// Gets or sets the kill timer.
/// </summary>
/// <value>The kill timer.</value>
private Timer? KillTimer { get; set; }
/// <summary> /// <summary>
/// Gets or sets device id. /// Gets or sets device id.
/// </summary> /// </summary>
@ -178,7 +153,7 @@ public class TranscodingJobDto : IDisposable
{ {
lock (_timerLock) lock (_timerLock)
{ {
KillTimer?.Change(Timeout.Infinite, Timeout.Infinite); _killTimer?.Change(Timeout.Infinite, Timeout.Infinite);
} }
} }
@ -189,10 +164,10 @@ public class TranscodingJobDto : IDisposable
{ {
lock (_timerLock) lock (_timerLock)
{ {
if (KillTimer is not null) if (_killTimer is not null)
{ {
KillTimer.Dispose(); _killTimer.Dispose();
KillTimer = null; _killTimer = null;
} }
} }
} }
@ -220,15 +195,15 @@ public class TranscodingJobDto : IDisposable
lock (_timerLock) lock (_timerLock)
{ {
if (KillTimer is null) if (_killTimer is null)
{ {
Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); _logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite); _killTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
} }
else else
{ {
Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite); _killTimer.Change(intervalMs, Timeout.Infinite);
} }
} }
} }
@ -245,39 +220,61 @@ public class TranscodingJobDto : IDisposable
lock (_timerLock) lock (_timerLock)
{ {
if (KillTimer is not null) if (_killTimer is not null)
{ {
var intervalMs = PingTimeout; var intervalMs = PingTimeout;
Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite); _killTimer.Change(intervalMs, Timeout.Infinite);
} }
} }
} }
/// <summary>
/// Stops the transcoding job.
/// </summary>
public void Stop()
{
lock (_processLock)
{
#pragma warning disable CA1849 // Can't await in lock block
TranscodingThrottler?.Stop().GetAwaiter().GetResult();
var process = Process;
if (!HasExited)
{
try
{
_logger.LogInformation("Stopping ffmpeg process with q command for {Path}", Path);
process!.StandardInput.WriteLine("q");
// Need to wait because killing is asynchronous.
if (!process.WaitForExit(5000))
{
_logger.LogInformation("Killing FFmpeg process for {Path}", Path);
process.Kill();
}
}
catch (InvalidOperationException)
{
}
}
#pragma warning restore CA1849
}
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose all resources.
/// </summary>
/// <param name="disposing">Whether to dispose all resources.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{ {
Process?.Dispose(); Process?.Dispose();
Process = null; Process = null;
KillTimer?.Dispose(); _killTimer?.Dispose();
KillTimer = null; _killTimer = null;
CancellationTokenSource?.Dispose(); CancellationTokenSource?.Dispose();
CancellationTokenSource = null; CancellationTokenSource = null;
TranscodingThrottler?.Dispose(); TranscodingThrottler?.Dispose();
TranscodingThrottler = null; TranscodingThrottler = null;
} }
}
} }

View File

@ -2,19 +2,18 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Models.PlaybackDtos; namespace MediaBrowser.Controller.MediaEncoding;
/// <summary> /// <summary>
/// Transcoding throttler. /// Transcoding throttler.
/// </summary> /// </summary>
public class TranscodingThrottler : IDisposable public class TranscodingThrottler : IDisposable
{ {
private readonly TranscodingJobDto _job; private readonly TranscodingJob _job;
private readonly ILogger<TranscodingThrottler> _logger; private readonly ILogger<TranscodingThrottler> _logger;
private readonly IConfigurationManager _config; private readonly IConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -30,7 +29,7 @@ public class TranscodingThrottler : IDisposable
/// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param> /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder) public TranscodingThrottler(TranscodingJob job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
{ {
_job = job; _job = job;
_logger = logger; _logger = logger;
@ -146,7 +145,7 @@ public class TranscodingThrottler : IDisposable
} }
} }
private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds) private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
{ {
var bytesDownloaded = job.BytesDownloaded; var bytesDownloaded = job.BytesDownloaded;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;

View File

@ -3,10 +3,10 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.PlaybackDtos; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace Jellyfin.Api.Helpers; namespace MediaBrowser.Controller.Streaming;
/// <summary> /// <summary>
/// A progressive file stream for transferring transcoded files as they are written to. /// A progressive file stream for transferring transcoded files as they are written to.
@ -14,8 +14,8 @@ namespace Jellyfin.Api.Helpers;
public class ProgressiveFileStream : Stream public class ProgressiveFileStream : Stream
{ {
private readonly Stream _stream; private readonly Stream _stream;
private readonly TranscodingJobDto? _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, TranscodingJobDto? 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

@ -1,11 +1,9 @@
using System; using System;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
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.
@ -13,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>
@ -21,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>
@ -141,7 +139,7 @@ public class StreamState : EncodingJobInfo, IDisposable
/// <summary> /// <summary>
/// Gets or sets the transcoding job. /// Gets or sets the transcoding job.
/// </summary> /// </summary>
public TranscodingJobDto? TranscodingJob { get; set; } public TranscodingJob? TranscodingJob { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
@ -153,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,9 +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.PlaybackDtos;
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;
@ -19,94 +16,78 @@ 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<TranscodingJobDto> _activeTranscodingJobs = new List<TranscodingJobDto>();
/// <summary>
/// The transcoding locks.
/// </summary>
private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>();
private readonly IAttachmentExtractor _attachmentExtractor;
private readonly IApplicationPaths _appPaths;
private readonly EncodingHelper _encodingHelper;
private readonly IFileSystem _fileSystem; private readonly 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. public TranscodingJob? GetTranscodingJob(string playSessionId)
/// </summary>
/// <param name="playSessionId">Playback session id.</param>
/// <returns>The transcoding job.</returns>
public TranscodingJobDto? GetTranscodingJob(string playSessionId)
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
@ -114,13 +95,8 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Get transcoding job. public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type)
/// </summary>
/// <param name="path">Path to the transcoding file.</param>
/// <param name="type">The <see cref="TranscodingJobType"/>.</param>
/// <returns>The transcoding job.</returns>
public TranscodingJobDto? GetTranscodingJob(string path, TranscodingJobType type)
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
@ -128,19 +104,14 @@ 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);
_logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused); _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
List<TranscodingJobDto> jobs; List<TranscodingJob> jobs;
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
@ -161,7 +132,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
private void PingTimer(TranscodingJobDto job, bool isProgressCheckIn) private void PingTimer(TranscodingJob job, bool isProgressCheckIn)
{ {
if (job.HasExited) if (job.HasExited)
{ {
@ -190,13 +161,9 @@ 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 TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state)); var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(state));
if (!job.HasExited && job.Type != TranscodingJobType.Progressive) if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
{ {
var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
@ -213,43 +180,21 @@ 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( var jobs = new List<TranscodingJob>();
j => string.IsNullOrWhiteSpace(playSessionId)
? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
: string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase),
deleteFiles);
}
/// <summary>
/// Kills the transcoding jobs.
/// </summary>
/// <param name="killJob">The kill job.</param>
/// <param name="deleteFiles">The delete files.</param>
/// <returns>Task.</returns>
private Task KillTranscodingJobs(Func<TranscodingJobDto, bool> killJob, Func<string, bool> deleteFiles)
{
var jobs = new List<TranscodingJobDto>();
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
// 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()
{ {
@ -258,17 +203,9 @@ public class TranscodingJobHelper : IDisposable
yield return KillTranscodingJob(job, false, deleteFiles); yield return KillTranscodingJob(job, false, deleteFiles);
} }
} }
return Task.WhenAll(GetKillJobs());
} }
/// <summary> private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete)
/// Kills the transcoding job.
/// </summary>
/// <param name="job">The job.</param>
/// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
/// <param name="delete">The delete.</param>
private async Task KillTranscodingJob(TranscodingJobDto job, bool closeLiveStream, Func<string, bool> delete)
{ {
job.DisposeKillTimer(); job.DisposeKillTimer();
@ -282,6 +219,7 @@ public class TranscodingJobHelper : IDisposable
{ {
#pragma warning disable CA1849 // Can't await in lock block #pragma warning disable CA1849 // Can't await in lock block
job.CancellationTokenSource.Cancel(); job.CancellationTokenSource.Cancel();
#pragma warning restore CA1849
} }
} }
@ -290,35 +228,7 @@ public class TranscodingJobHelper : IDisposable
_transcodingLocks.Remove(job.Path!); _transcodingLocks.Remove(job.Path!);
} }
lock (job.ProcessLock!) job.Stop();
{
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
var process = job.Process;
var hasExited = job.HasExited;
if (!hasExited)
{
try
{
_logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
process!.StandardInput.WriteLine("q");
// Need to wait because killing is asynchronous.
if (!process.WaitForExit(5000))
{
_logger.LogInformation("Killing FFmpeg process for {Path}", job.Path);
process.Kill();
}
}
catch (InvalidOperationException)
{
}
}
#pragma warning restore CA1849
}
if (delete(job.Path!)) if (delete(job.Path!))
{ {
@ -381,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))
@ -393,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)
@ -428,18 +330,9 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Report the transcoding progress to the session manager.
/// </summary>
/// <param name="job">The <see cref="TranscodingJobDto"/> of which the progress will be reported.</param>
/// <param name="state">The <see cref="StreamState"/> of the current transcoding job.</param>
/// <param name="transcodingPosition">The current transcoding position.</param>
/// <param name="framerate">The framerate of the transcoding job.</param>
/// <param name="percentComplete">The completion percentage of the transcode.</param>
/// <param name="bytesTranscoded">The number of bytes transcoded.</param>
/// <param name="bitRate">The bitrate of the transcoding job.</param>
public void ReportTranscodingProgress( public void ReportTranscodingProgress(
TranscodingJobDto job, TranscodingJob job,
StreamState state, StreamState state,
TimeSpan? transcodingPosition, TimeSpan? transcodingPosition,
float? framerate, float? framerate,
@ -490,22 +383,12 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Starts FFmpeg. public async Task<TranscodingJob> StartFfMpeg(
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="commandLineArguments">The command line arguments for FFmpeg.</param>
/// <param name="request">The <see cref="HttpRequest"/>.</param>
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <param name="workingDirectory">The working directory.</param>
/// <returns>Task.</returns>
public async Task<TranscodingJobDto> StartFfMpeg(
StreamState state, 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)
@ -517,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))
{ {
@ -595,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
{ {
@ -610,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;
@ -656,7 +550,7 @@ public class TranscodingJobHelper : IDisposable
return transcodingJob; return transcodingJob;
} }
private void StartThrottler(StreamState state, TranscodingJobDto transcodingJob) private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{ {
if (EnableThrottling(state)) if (EnableThrottling(state))
{ {
@ -665,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 TranscodingJobDto OnTranscodeBeginning(
string path, string path,
string? playSessionId, string? playSessionId,
string? liveStreamId, string? liveStreamId,
@ -702,7 +579,7 @@ public class TranscodingJobHelper : IDisposable
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
var job = new TranscodingJobDto(_loggerFactory.CreateLogger<TranscodingJobDto>()) var job = new TranscodingJob(_loggerFactory.CreateLogger<TranscodingJob>())
{ {
Type = type, Type = type,
Path = path, Path = path,
@ -724,11 +601,8 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Called when [transcode end]. public void OnTranscodeEndRequest(TranscodingJob job)
/// </summary>
/// <param name="job">The transcode job.</param>
public void OnTranscodeEndRequest(TranscodingJobDto job)
{ {
job.ActiveRequestCount--; job.ActiveRequestCount--;
_logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount); _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount);
@ -738,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)
{ {
@ -770,13 +635,7 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state)
/// Processes the exited.
/// </summary>
/// <param name="process">The process.</param>
/// <param name="job">The job.</param>
/// <param name="state">The state.</param>
private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
{ {
job.HasExited = true; job.HasExited = true;
job.ExitCode = process.ExitCode; job.ExitCode = process.ExitCode;
@ -822,44 +681,30 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Called when [transcode begin request]. public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type)
/// </summary>
/// <param name="path">The path.</param>
/// <param name="type">The type.</param>
/// <returns>The <see cref="TranscodingJobDto"/>.</returns>
public TranscodingJobDto? OnTranscodeBeginRequest(string path, TranscodingJobType type)
{ {
lock (_activeTranscodingJobs) 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(TranscodingJobDto 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)
@ -882,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();
@ -899,26 +741,10 @@ public class TranscodingJobHelper : IDisposable
} }
} }
/// <summary> /// <inheritdoc />
/// Dispose transcoding job helper.
/// </summary>
public void Dispose() 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.PlaybackProgress -= OnPlaybackProgress;
_sessionManager.PlaybackStart -= OnPlaybackProgress; _sessionManager.PlaybackStart -= OnPlaybackProgress;
} }
}
} }