| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 | using System;using System.Diagnostics;using System.Threading;using MediaBrowser.Model.Dto;using Microsoft.Extensions.Logging;namespace MediaBrowser.Controller.MediaEncoding;/// <summary>/// Class TranscodingJob./// </summary>public sealed class TranscodingJob : IDisposable{    private readonly ILogger<TranscodingJob> _logger;    private readonly Lock _processLock = new();    private readonly Lock _timerLock = new();    private Timer? _killTimer;    /// <summary>    /// Initializes a new instance of the <see cref="TranscodingJob"/> class.    /// </summary>    /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param>    public TranscodingJob(ILogger<TranscodingJob> logger)    {        _logger = logger;    }    /// <summary>    /// Gets or sets the play session identifier.    /// </summary>    public string? PlaySessionId { get; set; }    /// <summary>    /// Gets or sets the live stream identifier.    /// </summary>    public string? LiveStreamId { get; set; }    /// <summary>    /// Gets or sets a value indicating whether is live output.    /// </summary>    public bool IsLiveOutput { get; set; }    /// <summary>    /// Gets or sets the path.    /// </summary>    public MediaSourceInfo? MediaSource { get; set; }    /// <summary>    /// Gets or sets path.    /// </summary>    public string? Path { get; set; }    /// <summary>    /// Gets or sets the type.    /// </summary>    public TranscodingJobType Type { get; set; }    /// <summary>    /// Gets or sets the process.    /// </summary>    public Process? Process { get; set; }    /// <summary>    /// Gets or sets the active request count.    /// </summary>    public int ActiveRequestCount { get; set; }    /// <summary>    /// Gets or sets device id.    /// </summary>    public string? DeviceId { get; set; }    /// <summary>    /// Gets or sets cancellation token source.    /// </summary>    public CancellationTokenSource? CancellationTokenSource { get; set; }    /// <summary>    /// Gets or sets a value indicating whether has exited.    /// </summary>    public bool HasExited { get; set; }    /// <summary>    /// Gets or sets exit code.    /// </summary>    public int ExitCode { get; set; }    /// <summary>    /// Gets or sets a value indicating whether is user paused.    /// </summary>    public bool IsUserPaused { get; set; }    /// <summary>    /// Gets or sets id.    /// </summary>    public string? Id { get; set; }    /// <summary>    /// Gets or sets framerate.    /// </summary>    public float? Framerate { get; set; }    /// <summary>    /// Gets or sets completion percentage.    /// </summary>    public double? CompletionPercentage { get; set; }    /// <summary>    /// Gets or sets bytes downloaded.    /// </summary>    public long BytesDownloaded { get; set; }    /// <summary>    /// Gets or sets bytes transcoded.    /// </summary>    public long? BytesTranscoded { get; set; }    /// <summary>    /// Gets or sets bit rate.    /// </summary>    public int? BitRate { get; set; }    /// <summary>    /// Gets or sets transcoding position ticks.    /// </summary>    public long? TranscodingPositionTicks { get; set; }    /// <summary>    /// Gets or sets download position ticks.    /// </summary>    public long? DownloadPositionTicks { get; set; }    /// <summary>    /// Gets or sets transcoding throttler.    /// </summary>    public TranscodingThrottler? TranscodingThrottler { get; set; }    /// <summary>    /// Gets or sets transcoding segment cleaner.    /// </summary>    public TranscodingSegmentCleaner? TranscodingSegmentCleaner { get; set; }    /// <summary>    /// Gets or sets last ping date.    /// </summary>    public DateTime LastPingDate { get; set; }    /// <summary>    /// Gets or sets ping timeout.    /// </summary>    public int PingTimeout { get; set; }    /// <summary>    /// Stop kill timer.    /// </summary>    public void StopKillTimer()    {        lock (_timerLock)        {            _killTimer?.Change(Timeout.Infinite, Timeout.Infinite);        }    }    /// <summary>    /// Dispose kill timer.    /// </summary>    public void DisposeKillTimer()    {        lock (_timerLock)        {            if (_killTimer is not null)            {                _killTimer.Dispose();                _killTimer = null;            }        }    }    /// <summary>    /// Start kill timer.    /// </summary>    /// <param name="callback">Callback action.</param>    public void StartKillTimer(Action<object?> callback)    {        StartKillTimer(callback, PingTimeout);    }    /// <summary>    /// Start kill timer.    /// </summary>    /// <param name="callback">Callback action.</param>    /// <param name="intervalMs">Callback interval.</param>    public void StartKillTimer(Action<object?> callback, int intervalMs)    {        if (HasExited)        {            return;        }        lock (_timerLock)        {            if (_killTimer is null)            {                _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);            }            else            {                _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);                _killTimer.Change(intervalMs, Timeout.Infinite);            }        }    }    /// <summary>    /// Change kill timer if started.    /// </summary>    public void ChangeKillTimerIfStarted()    {        if (HasExited)        {            return;        }        lock (_timerLock)        {            if (_killTimer is not null)            {                var intervalMs = PingTimeout;                _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);                _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();            TranscodingSegmentCleaner?.Stop();            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 />    public void Dispose()    {        Process?.Dispose();        Process = null;        _killTimer?.Dispose();        _killTimer = null;        CancellationTokenSource?.Dispose();        CancellationTokenSource = null;        TranscodingThrottler?.Dispose();        TranscodingThrottler = null;        TranscodingSegmentCleaner?.Dispose();        TranscodingSegmentCleaner = null;    }}
 |