using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Composition; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api { /// /// Class Plugin /// [Export(typeof(IPlugin))] public class Plugin : BasePlugin { /// /// Gets the name of the plugin /// /// The name. public override string Name { get { return "Web Api"; } } /// /// Gets a value indicating whether this instance is a core plugin. /// /// true if this instance is a core plugin; otherwise, false. public override bool IsCorePlugin { get { return true; } } /// /// Gets the instance. /// /// The instance. public static Plugin Instance { get; private set; } /// /// Initializes a new instance of the class. /// public Plugin() { Instance = this; } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void DisposeOnServer(bool dispose) { if (dispose) { var jobCount = ActiveTranscodingJobs.Count; Parallel.ForEach(ActiveTranscodingJobs, OnTranscodeKillTimerStopped); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) { Thread.Sleep(1000); } } base.DisposeOnServer(dispose); } /// /// The active transcoding jobs /// private readonly List ActiveTranscodingJobs = new List(); /// /// Called when [transcode beginning]. /// /// The path. /// The type. /// The process. public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process) { lock (ActiveTranscodingJobs) { ActiveTranscodingJobs.Add(new TranscodingJob { Type = type, Path = path, Process = process, ActiveRequestCount = 1 }); } } /// /// Called when [transcode failed to start]. /// /// The path. /// The type. public void OnTranscodeFailedToStart(string path, TranscodingJobType type) { lock (ActiveTranscodingJobs) { var job = ActiveTranscodingJobs.First(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); ActiveTranscodingJobs.Remove(job); } } /// /// Determines whether [has active transcoding job] [the specified path]. /// /// The path. /// The type. /// true if [has active transcoding job] [the specified path]; otherwise, false. public bool HasActiveTranscodingJob(string path, TranscodingJobType type) { lock (ActiveTranscodingJobs) { return ActiveTranscodingJobs.Any(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); } } /// /// Called when [transcode begin request]. /// /// The path. /// The type. public void OnTranscodeBeginRequest(string path, TranscodingJobType type) { lock (ActiveTranscodingJobs) { var job = ActiveTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); if (job == null) { return; } job.ActiveRequestCount++; if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } } /// /// Called when [transcode end request]. /// /// The path. /// The type. public void OnTranscodeEndRequest(string path, TranscodingJobType type) { lock (ActiveTranscodingJobs) { var job = ActiveTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); if (job == null) { return; } job.ActiveRequestCount--; if (job.ActiveRequestCount == 0) { var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 30000; if (job.KillTimer == null) { job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); } else { job.KillTimer.Change(timerDuration, Timeout.Infinite); } } } } /// /// Called when [transcoding finished]. /// /// The path. /// The type. public void OnTranscodingFinished(string path, TranscodingJobType type) { lock (ActiveTranscodingJobs) { var job = ActiveTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); if (job == null) { return; } ActiveTranscodingJobs.Remove(job); if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } } /// /// Called when [transcode kill timer stopped]. /// /// The state. private void OnTranscodeKillTimerStopped(object state) { var job = (TranscodingJob)state; lock (ActiveTranscodingJobs) { ActiveTranscodingJobs.Remove(job); if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } var process = job.Process; var hasExited = true; try { hasExited = process.HasExited; } catch (Win32Exception ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } catch (InvalidOperationException ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } catch (NotSupportedException ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } if (hasExited) { return; } try { Logger.Info("Killing ffmpeg process for {0}", job.Path); process.Kill(); } catch (Win32Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (InvalidOperationException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (NotSupportedException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } } /// /// Class TranscodingJob /// public class TranscodingJob { /// /// Gets or sets the path. /// /// The path. public string Path { get; set; } /// /// Gets or sets the type. /// /// The type. public TranscodingJobType Type { get; set; } /// /// Gets or sets the process. /// /// The process. public Process Process { get; set; } /// /// Gets or sets the active request count. /// /// The active request count. public int ActiveRequestCount { get; set; } /// /// Gets or sets the kill timer. /// /// The kill timer. public Timer KillTimer { get; set; } } /// /// Enum TranscodingJobType /// public enum TranscodingJobType { /// /// The progressive /// Progressive, /// /// The HLS /// Hls } }