diff options
| author | Dominik <git@secnd.me> | 2023-06-15 19:38:42 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-15 19:38:42 +0200 |
| commit | 17f1e8d19b1fd693893d66d2275ed8ae2476344e (patch) | |
| tree | 7f48be975faa92042769870957587b3c7864f631 /Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs | |
| parent | e8ae7e5c38e28f13fa8de295e26c930cb46d9b79 (diff) | |
| parent | 6771b5cabe96b4b3cbd1cd0c998d564f3dd17ed4 (diff) | |
Merge branch 'master' into segment-deletion
Diffstat (limited to 'Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs')
| -rw-r--r-- | Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs | 305 |
1 files changed, 152 insertions, 153 deletions
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index 99376873c..b577c4ea6 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -7,214 +7,213 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; -namespace Jellyfin.Api.Models.PlaybackDtos +namespace Jellyfin.Api.Models.PlaybackDtos; + +/// <summary> +/// Transcoding throttler. +/// </summary> +public class TranscodingThrottler : IDisposable { + private readonly TranscodingJobDto _job; + private readonly ILogger<TranscodingThrottler> _logger; + private readonly IConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IMediaEncoder _mediaEncoder; + private Timer? _timer; + private bool _isPaused; + /// <summary> - /// Transcoding throttler. + /// Initializes a new instance of the <see cref="TranscodingThrottler"/> class. /// </summary> - public class TranscodingThrottler : IDisposable + /// <param name="job">Transcoding job dto.</param> + /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param> + /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder) { - private readonly TranscodingJobDto _job; - private readonly ILogger<TranscodingThrottler> _logger; - private readonly IConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IMediaEncoder _mediaEncoder; - private Timer? _timer; - private bool _isPaused; - - /// <summary> - /// Initializes a new instance of the <see cref="TranscodingThrottler"/> class. - /// </summary> - /// <param name="job">Transcoding job dto.</param> - /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param> - /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> - public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder) - { - _job = job; - _logger = logger; - _config = config; - _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; - } + _job = job; + _logger = logger; + _config = config; + _fileSystem = fileSystem; + _mediaEncoder = mediaEncoder; + } - /// <summary> - /// Start timer. - /// </summary> - public void Start() - { - _timer = new Timer(TimerCallback, null, 5000, 5000); - } + /// <summary> + /// Start timer. + /// </summary> + public void Start() + { + _timer = new Timer(TimerCallback, null, 5000, 5000); + } - /// <summary> - /// Unpause transcoding. - /// </summary> - /// <returns>A <see cref="Task"/>.</returns> - public async Task UnpauseTranscoding() + /// <summary> + /// Unpause transcoding. + /// </summary> + /// <returns>A <see cref="Task"/>.</returns> + public async Task UnpauseTranscoding() + { + if (_isPaused) { - if (_isPaused) - { - _logger.LogDebug("Sending resume command to ffmpeg"); + _logger.LogDebug("Sending resume command to ffmpeg"); - try - { - var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine; - await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false); - _isPaused = false; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error resuming transcoding"); - } + try + { + var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine; + await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false); + _isPaused = false; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error resuming transcoding"); } } + } - /// <summary> - /// Stop throttler. - /// </summary> - /// <returns>A <see cref="Task"/>.</returns> - public async Task Stop() + /// <summary> + /// Stop throttler. + /// </summary> + /// <returns>A <see cref="Task"/>.</returns> + public async Task Stop() + { + DisposeTimer(); + await UnpauseTranscoding().ConfigureAwait(false); + } + + /// <summary> + /// Dispose throttler. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Dispose throttler. + /// </summary> + /// <param name="disposing">Disposing.</param> + protected virtual void Dispose(bool disposing) + { + if (disposing) { DisposeTimer(); - await UnpauseTranscoding().ConfigureAwait(false); } + } - /// <summary> - /// Dispose throttler. - /// </summary> - public void Dispose() + private EncodingOptions GetOptions() + { + return _config.GetEncodingOptions(); + } + + private async void TimerCallback(object? state) + { + if (_job.HasExited) { - Dispose(true); - GC.SuppressFinalize(this); + DisposeTimer(); + return; } - /// <summary> - /// Dispose throttler. - /// </summary> - /// <param name="disposing">Disposing.</param> - protected virtual void Dispose(bool disposing) + var options = GetOptions(); + + if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds)) { - if (disposing) - { - DisposeTimer(); - } + await PauseTranscoding().ConfigureAwait(false); } - - private EncodingOptions GetOptions() + else { - return _config.GetEncodingOptions(); + await UnpauseTranscoding().ConfigureAwait(false); } + } - private async void TimerCallback(object? state) + private async Task PauseTranscoding() + { + if (!_isPaused) { - if (_job.HasExited) - { - DisposeTimer(); - return; - } + var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c"; - var options = GetOptions(); + _logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey); - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds)) + try { - await PauseTranscoding().ConfigureAwait(false); + await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false); + _isPaused = true; } - else + catch (Exception ex) { - await UnpauseTranscoding().ConfigureAwait(false); + _logger.LogError(ex, "Error pausing transcoding"); } } + } - private async Task PauseTranscoding() + private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds) + { + var bytesDownloaded = job.BytesDownloaded; + var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; + var downloadPositionTicks = job.DownloadPositionTicks ?? 0; + + var path = job.Path ?? throw new ArgumentException("Path can't be null."); + + var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; + + if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) { - if (!_isPaused) - { - var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c"; + // HLS - time-based consideration - _logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey); + var targetGap = gapLengthInTicks; + var gap = transcodingPositionTicks - downloadPositionTicks; - try - { - await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false); - _isPaused = true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error pausing transcoding"); - } + if (gap < targetGap) + { + _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); + return false; } + + _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); + return true; } - private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds) + if (bytesDownloaded > 0 && transcodingPositionTicks > 0) { - var bytesDownloaded = job.BytesDownloaded; - var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; - var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - - var path = job.Path ?? throw new ArgumentException("Path can't be null."); - - var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; + // Progressive Streaming - byte-based consideration - if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) + try { - // HLS - time-based consideration + var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length; - var targetGap = gapLengthInTicks; - var gap = transcodingPositionTicks - downloadPositionTicks; + // Estimate the bytes the transcoder should be ahead + double gapFactor = gapLengthInTicks; + gapFactor /= transcodingPositionTicks; + var targetGap = bytesTranscoded * gapFactor; + + var gap = bytesTranscoded - bytesDownloaded; if (gap < targetGap) { - _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); + _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); return false; } - _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); + _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); return true; } - - if (bytesDownloaded > 0 && transcodingPositionTicks > 0) + catch (Exception ex) { - // Progressive Streaming - byte-based consideration - - try - { - var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length; - - // Estimate the bytes the transcoder should be ahead - double gapFactor = gapLengthInTicks; - gapFactor /= transcodingPositionTicks; - var targetGap = bytesTranscoded * gapFactor; - - var gap = bytesTranscoded - bytesDownloaded; - - if (gap < targetGap) - { - _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return false; - } - - _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting output size"); - return false; - } + _logger.LogError(ex, "Error getting output size"); + return false; } - - _logger.LogDebug("No throttle data for {Path}", path); - return false; } - private void DisposeTimer() + _logger.LogDebug("No throttle data for {Path}", path); + return false; + } + + private void DisposeTimer() + { + if (_timer is not null) { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } + _timer.Dispose(); + _timer = null; } } } |
