< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.TranscodingJob
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs
Line coverage
39%
Covered lines: 29
Uncovered lines: 44
Coverable lines: 73
Total lines: 343
Line coverage: 39.7%
Branch coverage
37%
Covered branches: 15
Total branches: 40
Branch coverage: 37.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/59) Branch coverage: 0% (0/30) Total lines: 2888/9/2026 - 12:16:58 AM Line coverage: 12.6% (8/63) Branch coverage: 0% (0/30) Total lines: 30710/29/2026 - 12:17:11 AM Line coverage: 39.7% (29/73) Branch coverage: 37.5% (15/40) Total lines: 343 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/59) Branch coverage: 0% (0/30) Total lines: 2888/9/2026 - 12:16:58 AM Line coverage: 12.6% (8/63) Branch coverage: 0% (0/30) Total lines: 30710/29/2026 - 12:17:11 AM Line coverage: 39.7% (29/73) Branch coverage: 37.5% (15/40) Total lines: 343

Coverage delta

Coverage delta 38 -38

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_ActiveRequestCount()100%11100%
set_ActiveRequestCount(...)100%11100%
ReportSegmentDownloaded(...)100%22100%
GetHighestServedSegmentIndexEndingAtOrBefore(...)100%88100%
IncrementActiveRequestCount()100%11100%
DecrementActiveRequestCount()100%11100%
StopKillTimer()0%620%
DisposeKillTimer()0%620%
StartKillTimer(...)100%210%
StartKillTimer(...)0%2040%
ChangeKillTimerIfStarted()0%2040%
Stop()0%7280%
Dispose()50%1010100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Diagnostics;
 4using System.Threading;
 5using MediaBrowser.Model.Dto;
 6using Microsoft.Extensions.Logging;
 7
 8namespace MediaBrowser.Controller.MediaEncoding;
 9
 10/// <summary>
 11/// Class TranscodingJob.
 12/// </summary>
 13public sealed class TranscodingJob : IDisposable
 14{
 15    private readonly ILogger<TranscodingJob> _logger;
 916    private readonly Lock _processLock = new();
 917    private readonly Lock _timerLock = new();
 918    private readonly ConcurrentDictionary<int, long> _segmentEndPositionTicks = new();
 19
 20    private int _activeRequestCount;
 21    private Timer? _killTimer;
 22
 23    /// <summary>
 24    /// Initializes a new instance of the <see cref="TranscodingJob"/> class.
 25    /// </summary>
 26    /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param>
 27    public TranscodingJob(ILogger<TranscodingJob> logger)
 28    {
 929        _logger = logger;
 930    }
 31
 32    /// <summary>
 33    /// Gets or sets the play session identifier.
 34    /// </summary>
 35    public string? PlaySessionId { get; set; }
 36
 37    /// <summary>
 38    /// Gets or sets the live stream identifier.
 39    /// </summary>
 40    public string? LiveStreamId { get; set; }
 41
 42    /// <summary>
 43    /// Gets or sets a value indicating whether is live output.
 44    /// </summary>
 45    public bool IsLiveOutput { get; set; }
 46
 47    /// <summary>
 48    /// Gets or sets the path.
 49    /// </summary>
 50    public MediaSourceInfo? MediaSource { get; set; }
 51
 52    /// <summary>
 53    /// Gets or sets path.
 54    /// </summary>
 55    public string? Path { get; set; }
 56
 57    /// <summary>
 58    /// Gets or sets the type.
 59    /// </summary>
 60    public TranscodingJobType Type { get; set; }
 61
 62    /// <summary>
 63    /// Gets or sets the process.
 64    /// </summary>
 65    public Process? Process { get; set; }
 66
 67    /// <summary>
 68    /// Gets or sets the active request count.
 69    /// </summary>
 70    public int ActiveRequestCount
 71    {
 872        get => Volatile.Read(ref _activeRequestCount);
 373        set => Volatile.Write(ref _activeRequestCount, value);
 74    }
 75
 76    /// <summary>
 77    /// Gets or sets device id.
 78    /// </summary>
 79    public string? DeviceId { get; set; }
 80
 81    /// <summary>
 82    /// Gets or sets cancellation token source.
 83    /// </summary>
 84    public CancellationTokenSource? CancellationTokenSource { get; set; }
 85
 86    /// <summary>
 87    /// Gets or sets a value indicating whether has exited.
 88    /// </summary>
 89    public bool HasExited { get; set; }
 90
 91    /// <summary>
 92    /// Gets or sets exit code.
 93    /// </summary>
 94    public int ExitCode { get; set; }
 95
 96    /// <summary>
 97    /// Gets or sets a value indicating whether is user paused.
 98    /// </summary>
 99    public bool IsUserPaused { get; set; }
 100
 101    /// <summary>
 102    /// Gets or sets id.
 103    /// </summary>
 104    public string? Id { get; set; }
 105
 106    /// <summary>
 107    /// Gets or sets framerate.
 108    /// </summary>
 109    public float? Framerate { get; set; }
 110
 111    /// <summary>
 112    /// Gets or sets completion percentage.
 113    /// </summary>
 114    public double? CompletionPercentage { get; set; }
 115
 116    /// <summary>
 117    /// Gets or sets bytes downloaded.
 118    /// </summary>
 119    public long BytesDownloaded { get; set; }
 120
 121    /// <summary>
 122    /// Gets or sets bytes transcoded.
 123    /// </summary>
 124    public long? BytesTranscoded { get; set; }
 125
 126    /// <summary>
 127    /// Gets or sets bit rate.
 128    /// </summary>
 129    public int? BitRate { get; set; }
 130
 131    /// <summary>
 132    /// Gets or sets transcoding position ticks.
 133    /// </summary>
 134    public long? TranscodingPositionTicks { get; set; }
 135
 136    /// <summary>
 137    /// Gets or sets download position ticks.
 138    /// </summary>
 139    public long? DownloadPositionTicks { get; set; }
 140
 141    /// <summary>
 142    /// Gets or sets transcoding throttler.
 143    /// </summary>
 144    public TranscodingThrottler? TranscodingThrottler { get; set; }
 145
 146    /// <summary>
 147    /// Gets or sets transcoding segment cleaner.
 148    /// </summary>
 149    public TranscodingSegmentCleaner? TranscodingSegmentCleaner { get; set; }
 150
 151    /// <summary>
 152    /// Gets or sets last ping date.
 153    /// </summary>
 154    public DateTime LastPingDate { get; set; }
 155
 156    /// <summary>
 157    /// Gets or sets ping timeout.
 158    /// </summary>
 159    public int PingTimeout { get; set; }
 160
 161    /// <summary>
 162    /// Records the end position of a served segment.
 163    /// </summary>
 164    /// <param name="segmentIndex">The segment index.</param>
 165    /// <param name="segmentEndPositionTicks">The segment end position ticks.</param>
 166    public void ReportSegmentDownloaded(int segmentIndex, long segmentEndPositionTicks)
 167    {
 523168        if (segmentIndex < 0)
 169        {
 1170            return;
 171        }
 172
 522173        _segmentEndPositionTicks[segmentIndex] = segmentEndPositionTicks;
 522174    }
 175
 176    /// <summary>
 177    /// Gets the highest index among served segments whose end position is at or before the given position.
 178    /// </summary>
 179    /// <param name="positionTicks">The position ticks.</param>
 180    /// <returns>The segment index, or <c>null</c> if there is none.</returns>
 181    public int? GetHighestServedSegmentIndexEndingAtOrBefore(long positionTicks)
 182    {
 5183        int? result = null;
 1058184        foreach (var (index, endPositionTicks) in _segmentEndPositionTicks)
 185        {
 524186            if (endPositionTicks <= positionTicks && (result is null || index > result))
 187            {
 504188                result = index;
 189            }
 190        }
 191
 5192        return result;
 193    }
 194
 195    /// <summary>
 196    /// Increments the active request count.
 197    /// </summary>
 198    /// <returns>The incremented count.</returns>
 199    public int IncrementActiveRequestCount()
 1000200        => Interlocked.Increment(ref _activeRequestCount);
 201
 202    /// <summary>
 203    /// Decrements the active request count.
 204    /// </summary>
 205    /// <returns>The decremented count.</returns>
 206    public int DecrementActiveRequestCount()
 1003207        => Interlocked.Decrement(ref _activeRequestCount);
 208
 209    /// <summary>
 210    /// Stop kill timer.
 211    /// </summary>
 212    public void StopKillTimer()
 0213    {
 214        lock (_timerLock)
 215        {
 0216            _killTimer?.Change(Timeout.Infinite, Timeout.Infinite);
 0217        }
 0218    }
 219
 220    /// <summary>
 221    /// Dispose kill timer.
 222    /// </summary>
 223    public void DisposeKillTimer()
 0224    {
 225        lock (_timerLock)
 226        {
 0227            if (_killTimer is not null)
 228            {
 0229                _killTimer.Dispose();
 0230                _killTimer = null;
 231            }
 0232        }
 0233    }
 234
 235    /// <summary>
 236    /// Start kill timer.
 237    /// </summary>
 238    /// <param name="callback">Callback action.</param>
 239    public void StartKillTimer(Action<object?> callback)
 240    {
 0241        StartKillTimer(callback, PingTimeout);
 0242    }
 243
 244    /// <summary>
 245    /// Start kill timer.
 246    /// </summary>
 247    /// <param name="callback">Callback action.</param>
 248    /// <param name="intervalMs">Callback interval.</param>
 249    public void StartKillTimer(Action<object?> callback, int intervalMs)
 250    {
 0251        if (HasExited)
 252        {
 0253            return;
 254        }
 255
 256        lock (_timerLock)
 257        {
 0258            if (_killTimer is null)
 259            {
 0260                _logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessio
 0261                _killTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
 262            }
 263            else
 264            {
 0265                _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessio
 0266                _killTimer.Change(intervalMs, Timeout.Infinite);
 267            }
 0268        }
 0269    }
 270
 271    /// <summary>
 272    /// Change kill timer if started.
 273    /// </summary>
 274    public void ChangeKillTimerIfStarted()
 275    {
 0276        if (HasExited)
 277        {
 0278            return;
 279        }
 280
 281        lock (_timerLock)
 282        {
 0283            if (_killTimer is not null)
 284            {
 0285                var intervalMs = PingTimeout;
 286
 0287                _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessio
 0288                _killTimer.Change(intervalMs, Timeout.Infinite);
 289            }
 0290        }
 0291    }
 292
 293    /// <summary>
 294    /// Stops the transcoding job.
 295    /// </summary>
 296    public void Stop()
 0297    {
 298        lock (_processLock)
 299        {
 300#pragma warning disable CA1849 // Can't await in lock block
 0301            TranscodingThrottler?.Stop().GetAwaiter().GetResult();
 0302            TranscodingSegmentCleaner?.Stop();
 303
 0304            var process = Process;
 305
 0306            if (!HasExited)
 307            {
 308                try
 309                {
 0310                    _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", Path);
 311
 0312                    process!.StandardInput.WriteLine("q");
 313
 314                    // Need to wait because killing is asynchronous.
 0315                    if (!process.WaitForExit(5000))
 316                    {
 0317                        _logger.LogInformation("Killing FFmpeg process for {Path}", Path);
 0318                        process.Kill();
 319                    }
 0320                }
 0321                catch (InvalidOperationException)
 322                {
 0323                }
 324            }
 325#pragma warning restore CA1849
 0326        }
 0327    }
 328
 329    /// <inheritdoc />
 330    public void Dispose()
 331    {
 4332        Process?.Dispose();
 4333        Process = null;
 4334        _killTimer?.Dispose();
 4335        _killTimer = null;
 4336        CancellationTokenSource?.Dispose();
 4337        CancellationTokenSource = null;
 4338        TranscodingThrottler?.Dispose();
 4339        TranscodingThrottler = null;
 4340        TranscodingSegmentCleaner?.Dispose();
 4341        TranscodingSegmentCleaner = null;
 4342    }
 343}