< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.TranscodingThrottler
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 49
Coverable lines: 49
Total lines: 218
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 20
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Start()100%210%
Dispose()100%210%
Dispose(...)0%620%
GetOptions()100%210%
IsThrottleAllowed(...)0%272160%
DisposeTimer()0%620%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs

#LineLine coverage
 1using System;
 2using System.Threading;
 3using System.Threading.Tasks;
 4using MediaBrowser.Common.Configuration;
 5using MediaBrowser.Model.Configuration;
 6using MediaBrowser.Model.IO;
 7using Microsoft.Extensions.Logging;
 8
 9namespace MediaBrowser.Controller.MediaEncoding;
 10
 11/// <summary>
 12/// Transcoding throttler.
 13/// </summary>
 14public class TranscodingThrottler : IDisposable
 15{
 16    private readonly TranscodingJob _job;
 17    private readonly ILogger<TranscodingThrottler> _logger;
 18    private readonly IConfigurationManager _config;
 19    private readonly IFileSystem _fileSystem;
 20    private readonly IMediaEncoder _mediaEncoder;
 21    private Timer? _timer;
 22    private bool _isPaused;
 23
 24    /// <summary>
 25    /// Initializes a new instance of the <see cref="TranscodingThrottler"/> class.
 26    /// </summary>
 27    /// <param name="job">Transcoding job dto.</param>
 28    /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
 29    /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
 30    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 31    /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 32    public TranscodingThrottler(TranscodingJob job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, 
 33    {
 034        _job = job;
 035        _logger = logger;
 036        _config = config;
 037        _fileSystem = fileSystem;
 038        _mediaEncoder = mediaEncoder;
 039    }
 40
 41    /// <summary>
 42    /// Start timer.
 43    /// </summary>
 44    public void Start()
 45    {
 046        _timer = new Timer(TimerCallback, null, 5000, 5000);
 047    }
 48
 49    /// <summary>
 50    /// Unpause transcoding.
 51    /// </summary>
 52    /// <returns>A <see cref="Task"/>.</returns>
 53    public async Task UnpauseTranscoding()
 54    {
 55        if (_isPaused)
 56        {
 57            _logger.LogDebug("Sending resume command to ffmpeg");
 58
 59            try
 60            {
 61                var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine;
 62                await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false);
 63                _isPaused = false;
 64            }
 65            catch (Exception ex)
 66            {
 67                _logger.LogError(ex, "Error resuming transcoding");
 68            }
 69        }
 70    }
 71
 72    /// <summary>
 73    /// Stop throttler.
 74    /// </summary>
 75    /// <returns>A <see cref="Task"/>.</returns>
 76    public async Task Stop()
 77    {
 78        DisposeTimer();
 79        await UnpauseTranscoding().ConfigureAwait(false);
 80    }
 81
 82    /// <summary>
 83    /// Dispose throttler.
 84    /// </summary>
 85    public void Dispose()
 86    {
 087        Dispose(true);
 088        GC.SuppressFinalize(this);
 089    }
 90
 91    /// <summary>
 92    /// Dispose throttler.
 93    /// </summary>
 94    /// <param name="disposing">Disposing.</param>
 95    protected virtual void Dispose(bool disposing)
 96    {
 097        if (disposing)
 98        {
 099            DisposeTimer();
 100        }
 0101    }
 102
 103    private EncodingOptions GetOptions()
 104    {
 0105        return _config.GetEncodingOptions();
 106    }
 107
 108    private async void TimerCallback(object? state)
 109    {
 110        if (_job.HasExited)
 111        {
 112            DisposeTimer();
 113            return;
 114        }
 115
 116        var options = GetOptions();
 117
 118        if (options.EnableThrottling && IsThrottleAllowed(_job, Math.Max(options.ThrottleDelaySeconds, 60)))
 119        {
 120            await PauseTranscoding().ConfigureAwait(false);
 121        }
 122        else
 123        {
 124            await UnpauseTranscoding().ConfigureAwait(false);
 125        }
 126    }
 127
 128    private async Task PauseTranscoding()
 129    {
 130        if (!_isPaused)
 131        {
 132            var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c";
 133
 134            _logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey);
 135
 136            try
 137            {
 138                await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false);
 139                _isPaused = true;
 140            }
 141            catch (Exception ex)
 142            {
 143                _logger.LogError(ex, "Error pausing transcoding");
 144            }
 145        }
 146    }
 147
 148    private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
 149    {
 0150        var bytesDownloaded = job.BytesDownloaded;
 0151        var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
 0152        var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
 153
 0154        var path = job.Path ?? throw new ArgumentException("Path can't be null.");
 155
 0156        var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
 157
 0158        if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
 159        {
 160            // HLS - time-based consideration
 161
 0162            var targetGap = gapLengthInTicks;
 0163            var gap = transcodingPositionTicks - downloadPositionTicks;
 164
 0165            if (gap < targetGap)
 166            {
 0167                _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
 0168                return false;
 169            }
 170
 0171            _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
 0172            return true;
 173        }
 174
 0175        if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
 176        {
 177            // Progressive Streaming - byte-based consideration
 178
 179            try
 180            {
 0181                var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length;
 182
 183                // Estimate the bytes the transcoder should be ahead
 0184                double gapFactor = gapLengthInTicks;
 0185                gapFactor /= transcodingPositionTicks;
 0186                var targetGap = bytesTranscoded * gapFactor;
 187
 0188                var gap = bytesTranscoded - bytesDownloaded;
 189
 0190                if (gap < targetGap)
 191                {
 0192                    _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targe
 0193                    return false;
 194                }
 195
 0196                _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, by
 0197                return true;
 198            }
 0199            catch (Exception ex)
 200            {
 0201                _logger.LogError(ex, "Error getting output size");
 0202                return false;
 203            }
 204        }
 205
 0206        _logger.LogDebug("No throttle data for {Path}", path);
 0207        return false;
 0208    }
 209
 210    private void DisposeTimer()
 211    {
 0212        if (_timer is not null)
 213        {
 0214            _timer.Dispose();
 0215            _timer = null;
 216        }
 0217    }
 218}