ApiEntryPoint.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. using MediaBrowser.Controller.Plugins;
  2. using MediaBrowser.Model.Logging;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. namespace MediaBrowser.Api
  11. {
  12. /// <summary>
  13. /// Class ServerEntryPoint
  14. /// </summary>
  15. public class ApiEntryPoint : IServerEntryPoint
  16. {
  17. /// <summary>
  18. /// The instance
  19. /// </summary>
  20. public static ApiEntryPoint Instance;
  21. /// <summary>
  22. /// Gets or sets the logger.
  23. /// </summary>
  24. /// <value>The logger.</value>
  25. private ILogger Logger { get; set; }
  26. /// <summary>
  27. /// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
  28. /// </summary>
  29. /// <param name="logger">The logger.</param>
  30. public ApiEntryPoint(ILogger logger)
  31. {
  32. Logger = logger;
  33. Instance = this;
  34. }
  35. /// <summary>
  36. /// Runs this instance.
  37. /// </summary>
  38. public void Run()
  39. {
  40. }
  41. /// <summary>
  42. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  43. /// </summary>
  44. public void Dispose()
  45. {
  46. Dispose(true);
  47. GC.SuppressFinalize(this);
  48. }
  49. /// <summary>
  50. /// Releases unmanaged and - optionally - managed resources.
  51. /// </summary>
  52. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  53. protected virtual void Dispose(bool dispose)
  54. {
  55. var jobCount = _activeTranscodingJobs.Count;
  56. Parallel.ForEach(_activeTranscodingJobs, OnTranscodeKillTimerStopped);
  57. // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
  58. if (jobCount > 0)
  59. {
  60. Thread.Sleep(1000);
  61. }
  62. }
  63. /// <summary>
  64. /// The active transcoding jobs
  65. /// </summary>
  66. private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
  67. /// <summary>
  68. /// Called when [transcode beginning].
  69. /// </summary>
  70. /// <param name="path">The path.</param>
  71. /// <param name="type">The type.</param>
  72. /// <param name="process">The process.</param>
  73. public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process)
  74. {
  75. lock (_activeTranscodingJobs)
  76. {
  77. _activeTranscodingJobs.Add(new TranscodingJob
  78. {
  79. Type = type,
  80. Path = path,
  81. Process = process,
  82. ActiveRequestCount = 1
  83. });
  84. }
  85. }
  86. /// <summary>
  87. /// <summary>
  88. /// The progressive
  89. /// </summary>
  90. /// Called when [transcode failed to start].
  91. /// </summary>
  92. /// <param name="path">The path.</param>
  93. /// <param name="type">The type.</param>
  94. public void OnTranscodeFailedToStart(string path, TranscodingJobType type)
  95. {
  96. lock (_activeTranscodingJobs)
  97. {
  98. var job = _activeTranscodingJobs.First(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
  99. _activeTranscodingJobs.Remove(job);
  100. }
  101. }
  102. /// <summary>
  103. /// Determines whether [has active transcoding job] [the specified path].
  104. /// </summary>
  105. /// <param name="path">The path.</param>
  106. /// <param name="type">The type.</param>
  107. /// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns>
  108. public bool HasActiveTranscodingJob(string path, TranscodingJobType type)
  109. {
  110. lock (_activeTranscodingJobs)
  111. {
  112. return _activeTranscodingJobs.Any(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
  113. }
  114. }
  115. /// <summary>
  116. /// Called when [transcode begin request].
  117. /// </summary>
  118. /// <param name="path">The path.</param>
  119. /// <param name="type">The type.</param>
  120. public void OnTranscodeBeginRequest(string path, TranscodingJobType type)
  121. {
  122. lock (_activeTranscodingJobs)
  123. {
  124. var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
  125. if (job == null)
  126. {
  127. return;
  128. }
  129. job.ActiveRequestCount++;
  130. if (job.KillTimer != null)
  131. {
  132. job.KillTimer.Dispose();
  133. job.KillTimer = null;
  134. }
  135. }
  136. }
  137. /// <summary>
  138. /// Called when [transcode end request].
  139. /// </summary>
  140. /// <param name="path">The path.</param>
  141. /// <param name="type">The type.</param>
  142. public void OnTranscodeEndRequest(string path, TranscodingJobType type)
  143. {
  144. lock (_activeTranscodingJobs)
  145. {
  146. var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
  147. if (job == null)
  148. {
  149. return;
  150. }
  151. job.ActiveRequestCount--;
  152. if (job.ActiveRequestCount == 0)
  153. {
  154. var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 30000;
  155. if (job.KillTimer == null)
  156. {
  157. job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
  158. }
  159. else
  160. {
  161. job.KillTimer.Change(timerDuration, Timeout.Infinite);
  162. }
  163. }
  164. }
  165. }
  166. /// <summary>
  167. /// Called when [transcoding finished].
  168. /// </summary>
  169. /// <param name="path">The path.</param>
  170. /// <param name="type">The type.</param>
  171. public void OnTranscodingFinished(string path, TranscodingJobType type)
  172. {
  173. lock (_activeTranscodingJobs)
  174. {
  175. var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
  176. if (job == null)
  177. {
  178. return;
  179. }
  180. _activeTranscodingJobs.Remove(job);
  181. if (job.KillTimer != null)
  182. {
  183. job.KillTimer.Dispose();
  184. job.KillTimer = null;
  185. }
  186. }
  187. }
  188. /// <summary>
  189. /// Called when [transcode kill timer stopped].
  190. /// </summary>
  191. /// <param name="state">The state.</param>
  192. private void OnTranscodeKillTimerStopped(object state)
  193. {
  194. var job = (TranscodingJob)state;
  195. lock (_activeTranscodingJobs)
  196. {
  197. _activeTranscodingJobs.Remove(job);
  198. if (job.KillTimer != null)
  199. {
  200. job.KillTimer.Dispose();
  201. job.KillTimer = null;
  202. }
  203. }
  204. var process = job.Process;
  205. var hasExited = true;
  206. try
  207. {
  208. hasExited = process.HasExited;
  209. }
  210. catch (Win32Exception ex)
  211. {
  212. Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path);
  213. }
  214. catch (InvalidOperationException ex)
  215. {
  216. Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path);
  217. }
  218. catch (NotSupportedException ex)
  219. {
  220. Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path);
  221. }
  222. if (hasExited)
  223. {
  224. return;
  225. }
  226. try
  227. {
  228. Logger.Info("Killing ffmpeg process for {0}", job.Path);
  229. process.Kill();
  230. }
  231. catch (Win32Exception ex)
  232. {
  233. Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
  234. }
  235. catch (InvalidOperationException ex)
  236. {
  237. Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
  238. }
  239. catch (NotSupportedException ex)
  240. {
  241. Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
  242. }
  243. }
  244. }
  245. /// <summary>
  246. /// Class TranscodingJob
  247. /// </summary>
  248. public class TranscodingJob
  249. {
  250. /// <summary>
  251. /// Gets or sets the path.
  252. /// </summary>
  253. /// <value>The path.</value>
  254. public string Path { get; set; }
  255. /// <summary>
  256. /// Gets or sets the type.
  257. /// </summary>
  258. /// <value>The type.</value>
  259. public TranscodingJobType Type { get; set; }
  260. /// <summary>
  261. /// Gets or sets the process.
  262. /// </summary>
  263. /// <value>The process.</value>
  264. public Process Process { get; set; }
  265. /// <summary>
  266. /// Gets or sets the active request count.
  267. /// </summary>
  268. /// <value>The active request count.</value>
  269. public int ActiveRequestCount { get; set; }
  270. /// <summary>
  271. /// Gets or sets the kill timer.
  272. /// </summary>
  273. /// <value>The kill timer.</value>
  274. public Timer KillTimer { get; set; }
  275. }
  276. /// <summary>
  277. /// Enum TranscodingJobType
  278. /// </summary>
  279. public enum TranscodingJobType
  280. {
  281. /// <summary>
  282. /// The progressive
  283. /// </summary>
  284. Progressive,
  285. /// <summary>
  286. /// The HLS
  287. /// </summary>
  288. Hls
  289. }
  290. }