ApiEntryPoint.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Api.Playback;
  9. using MediaBrowser.Common.Configuration;
  10. using MediaBrowser.Controller.Configuration;
  11. using MediaBrowser.Controller.Library;
  12. using MediaBrowser.Controller.MediaEncoding;
  13. using MediaBrowser.Controller.Net;
  14. using MediaBrowser.Controller.Plugins;
  15. using MediaBrowser.Controller.Session;
  16. using MediaBrowser.Model.Configuration;
  17. using MediaBrowser.Model.Diagnostics;
  18. using MediaBrowser.Model.Dto;
  19. using MediaBrowser.Model.IO;
  20. using MediaBrowser.Model.Session;
  21. using Microsoft.Extensions.Logging;
  22. namespace MediaBrowser.Api
  23. {
  24. /// <summary>
  25. /// Class ServerEntryPoint
  26. /// </summary>
  27. public class ApiEntryPoint : IServerEntryPoint
  28. {
  29. /// <summary>
  30. /// The instance
  31. /// </summary>
  32. public static ApiEntryPoint Instance;
  33. /// <summary>
  34. /// Gets or sets the logger.
  35. /// </summary>
  36. /// <value>The logger.</value>
  37. internal ILogger Logger { get; private set; }
  38. internal IHttpResultFactory ResultFactory { get; private set; }
  39. /// <summary>
  40. /// The application paths
  41. /// </summary>
  42. private readonly IServerConfigurationManager _config;
  43. private readonly ISessionManager _sessionManager;
  44. private readonly IFileSystem _fileSystem;
  45. private readonly IMediaSourceManager _mediaSourceManager;
  46. public readonly IProcessFactory ProcessFactory;
  47. /// <summary>
  48. /// The active transcoding jobs
  49. /// </summary>
  50. private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
  51. private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks =
  52. new Dictionary<string, SemaphoreSlim>();
  53. private bool _disposed = false;
  54. /// <summary>
  55. /// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
  56. /// </summary>
  57. /// <param name="logger">The logger.</param>
  58. /// <param name="sessionManager">The session manager.</param>
  59. /// <param name="config">The configuration.</param>
  60. /// <param name="fileSystem">The file system.</param>
  61. /// <param name="mediaSourceManager">The media source manager.</param>
  62. public ApiEntryPoint(
  63. ILogger logger,
  64. ISessionManager sessionManager,
  65. IServerConfigurationManager config,
  66. IFileSystem fileSystem,
  67. IMediaSourceManager mediaSourceManager,
  68. IProcessFactory processFactory,
  69. IHttpResultFactory resultFactory)
  70. {
  71. Logger = logger;
  72. _sessionManager = sessionManager;
  73. _config = config;
  74. _fileSystem = fileSystem;
  75. _mediaSourceManager = mediaSourceManager;
  76. ProcessFactory = processFactory;
  77. ResultFactory = resultFactory;
  78. _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
  79. _sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
  80. Instance = this;
  81. }
  82. public static string[] Split(string value, char separator, bool removeEmpty)
  83. {
  84. if (string.IsNullOrWhiteSpace(value))
  85. {
  86. return Array.Empty<string>();
  87. }
  88. if (removeEmpty)
  89. {
  90. return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
  91. }
  92. return value.Split(separator);
  93. }
  94. public SemaphoreSlim GetTranscodingLock(string outputPath)
  95. {
  96. lock (_transcodingLocks)
  97. {
  98. if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim result))
  99. {
  100. result = new SemaphoreSlim(1, 1);
  101. _transcodingLocks[outputPath] = result;
  102. }
  103. return result;
  104. }
  105. }
  106. private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
  107. {
  108. if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
  109. {
  110. PingTranscodingJob(e.PlaySessionId, e.IsPaused);
  111. }
  112. }
  113. void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
  114. {
  115. if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
  116. {
  117. PingTranscodingJob(e.PlaySessionId, e.IsPaused);
  118. }
  119. }
  120. /// <summary>
  121. /// Runs this instance.
  122. /// </summary>
  123. public Task RunAsync()
  124. {
  125. try
  126. {
  127. DeleteEncodedMediaCache();
  128. }
  129. catch (FileNotFoundException)
  130. {
  131. // Don't clutter the log
  132. }
  133. catch (IOException)
  134. {
  135. // Don't clutter the log
  136. }
  137. catch (Exception ex)
  138. {
  139. Logger.LogError(ex, "Error deleting encoded media cache");
  140. }
  141. return Task.CompletedTask;
  142. }
  143. public EncodingOptions GetEncodingOptions()
  144. {
  145. return ConfigurationManagerExtensions.GetConfiguration<EncodingOptions>(_config, "encoding");
  146. }
  147. /// <summary>
  148. /// Deletes the encoded media cache.
  149. /// </summary>
  150. private void DeleteEncodedMediaCache()
  151. {
  152. var path = _config.ApplicationPaths.TranscodingTempPath;
  153. foreach (var file in _fileSystem.GetFilePaths(path, true))
  154. {
  155. _fileSystem.DeleteFile(file);
  156. }
  157. }
  158. /// <summary>
  159. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  160. /// </summary>
  161. public void Dispose()
  162. {
  163. Dispose(true);
  164. GC.SuppressFinalize(this);
  165. }
  166. /// <summary>
  167. /// Releases unmanaged and - optionally - managed resources.
  168. /// </summary>
  169. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  170. protected virtual void Dispose(bool dispose)
  171. {
  172. if (_disposed)
  173. {
  174. return;
  175. }
  176. if (dispose)
  177. {
  178. // TODO: dispose
  179. }
  180. var jobs = _activeTranscodingJobs.ToList();
  181. var jobCount = jobs.Count;
  182. IEnumerable<Task> GetKillJobs()
  183. {
  184. foreach (var job in jobs)
  185. {
  186. yield return KillTranscodingJob(job, false, path => true);
  187. }
  188. }
  189. // Wait for all processes to be killed
  190. if (jobCount > 0)
  191. {
  192. Task.WaitAll(GetKillJobs().ToArray());
  193. }
  194. _activeTranscodingJobs.Clear();
  195. _transcodingLocks.Clear();
  196. _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress;
  197. _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
  198. _disposed = true;
  199. }
  200. /// <summary>
  201. /// Called when [transcode beginning].
  202. /// </summary>
  203. /// <param name="path">The path.</param>
  204. /// <param name="playSessionId">The play session identifier.</param>
  205. /// <param name="liveStreamId">The live stream identifier.</param>
  206. /// <param name="transcodingJobId">The transcoding job identifier.</param>
  207. /// <param name="type">The type.</param>
  208. /// <param name="process">The process.</param>
  209. /// <param name="deviceId">The device id.</param>
  210. /// <param name="state">The state.</param>
  211. /// <param name="cancellationTokenSource">The cancellation token source.</param>
  212. /// <returns>TranscodingJob.</returns>
  213. public TranscodingJob OnTranscodeBeginning(
  214. string path,
  215. string playSessionId,
  216. string liveStreamId,
  217. string transcodingJobId,
  218. TranscodingJobType type,
  219. Process process,
  220. string deviceId,
  221. StreamState state,
  222. CancellationTokenSource cancellationTokenSource)
  223. {
  224. lock (_activeTranscodingJobs)
  225. {
  226. var job = new TranscodingJob(Logger)
  227. {
  228. Type = type,
  229. Path = path,
  230. Process = process,
  231. ActiveRequestCount = 1,
  232. DeviceId = deviceId,
  233. CancellationTokenSource = cancellationTokenSource,
  234. Id = transcodingJobId,
  235. PlaySessionId = playSessionId,
  236. LiveStreamId = liveStreamId,
  237. MediaSource = state.MediaSource
  238. };
  239. _activeTranscodingJobs.Add(job);
  240. ReportTranscodingProgress(job, state, null, null, null, null, null);
  241. return job;
  242. }
  243. }
  244. public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
  245. {
  246. var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
  247. if (job != null)
  248. {
  249. job.Framerate = framerate;
  250. job.CompletionPercentage = percentComplete;
  251. job.TranscodingPositionTicks = ticks;
  252. job.BytesTranscoded = bytesTranscoded;
  253. job.BitRate = bitRate;
  254. }
  255. var deviceId = state.Request.DeviceId;
  256. if (!string.IsNullOrWhiteSpace(deviceId))
  257. {
  258. var audioCodec = state.ActualOutputAudioCodec;
  259. var videoCodec = state.ActualOutputVideoCodec;
  260. _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
  261. {
  262. Bitrate = bitRate ?? state.TotalOutputBitrate,
  263. AudioCodec = audioCodec,
  264. VideoCodec = videoCodec,
  265. Container = state.OutputContainer,
  266. Framerate = framerate,
  267. CompletionPercentage = percentComplete,
  268. Width = state.OutputWidth,
  269. Height = state.OutputHeight,
  270. AudioChannels = state.OutputAudioChannels,
  271. IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase),
  272. IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase),
  273. TranscodeReasons = state.TranscodeReasons
  274. });
  275. }
  276. }
  277. /// <summary>
  278. /// <summary>
  279. /// The progressive
  280. /// </summary>
  281. /// Called when [transcode failed to start].
  282. /// </summary>
  283. /// <param name="path">The path.</param>
  284. /// <param name="type">The type.</param>
  285. /// <param name="state">The state.</param>
  286. public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
  287. {
  288. lock (_activeTranscodingJobs)
  289. {
  290. var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
  291. if (job != null)
  292. {
  293. _activeTranscodingJobs.Remove(job);
  294. }
  295. }
  296. lock (_transcodingLocks)
  297. {
  298. _transcodingLocks.Remove(path);
  299. }
  300. if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
  301. {
  302. _sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
  303. }
  304. }
  305. /// <summary>
  306. /// Determines whether [has active transcoding job] [the specified path].
  307. /// </summary>
  308. /// <param name="path">The path.</param>
  309. /// <param name="type">The type.</param>
  310. /// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns>
  311. public bool HasActiveTranscodingJob(string path, TranscodingJobType type)
  312. {
  313. return GetTranscodingJob(path, type) != null;
  314. }
  315. public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type)
  316. {
  317. lock (_activeTranscodingJobs)
  318. {
  319. return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
  320. }
  321. }
  322. public TranscodingJob GetTranscodingJob(string playSessionId)
  323. {
  324. lock (_activeTranscodingJobs)
  325. {
  326. return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
  327. }
  328. }
  329. /// <summary>
  330. /// Called when [transcode begin request].
  331. /// </summary>
  332. /// <param name="path">The path.</param>
  333. /// <param name="type">The type.</param>
  334. public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type)
  335. {
  336. lock (_activeTranscodingJobs)
  337. {
  338. var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
  339. if (job == null)
  340. {
  341. return null;
  342. }
  343. OnTranscodeBeginRequest(job);
  344. return job;
  345. }
  346. }
  347. public void OnTranscodeBeginRequest(TranscodingJob job)
  348. {
  349. job.ActiveRequestCount++;
  350. if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
  351. {
  352. job.StopKillTimer();
  353. }
  354. }
  355. public void OnTranscodeEndRequest(TranscodingJob job)
  356. {
  357. job.ActiveRequestCount--;
  358. //Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
  359. if (job.ActiveRequestCount <= 0)
  360. {
  361. PingTimer(job, false);
  362. }
  363. }
  364. internal void PingTranscodingJob(string playSessionId, bool? isUserPaused)
  365. {
  366. if (string.IsNullOrEmpty(playSessionId))
  367. {
  368. throw new ArgumentNullException(nameof(playSessionId));
  369. }
  370. //Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
  371. List<TranscodingJob> jobs;
  372. lock (_activeTranscodingJobs)
  373. {
  374. // This is really only needed for HLS.
  375. // Progressive streams can stop on their own reliably
  376. jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
  377. }
  378. foreach (var job in jobs)
  379. {
  380. if (isUserPaused.HasValue)
  381. {
  382. //Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
  383. job.IsUserPaused = isUserPaused.Value;
  384. }
  385. PingTimer(job, true);
  386. }
  387. }
  388. private void PingTimer(TranscodingJob job, bool isProgressCheckIn)
  389. {
  390. if (job.HasExited)
  391. {
  392. job.StopKillTimer();
  393. return;
  394. }
  395. var timerDuration = 10000;
  396. if (job.Type != TranscodingJobType.Progressive)
  397. {
  398. timerDuration = 60000;
  399. }
  400. job.PingTimeout = timerDuration;
  401. job.LastPingDate = DateTime.UtcNow;
  402. // Don't start the timer for playback checkins with progressive streaming
  403. if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
  404. {
  405. job.StartKillTimer(OnTranscodeKillTimerStopped);
  406. }
  407. else
  408. {
  409. job.ChangeKillTimerIfStarted();
  410. }
  411. }
  412. /// <summary>
  413. /// Called when [transcode kill timer stopped].
  414. /// </summary>
  415. /// <param name="state">The state.</param>
  416. private async void OnTranscodeKillTimerStopped(object state)
  417. {
  418. var job = (TranscodingJob)state;
  419. if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
  420. {
  421. var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
  422. if (timeSinceLastPing < job.PingTimeout)
  423. {
  424. job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout);
  425. return;
  426. }
  427. }
  428. Logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
  429. await KillTranscodingJob(job, true, path => true);
  430. }
  431. /// <summary>
  432. /// Kills the single transcoding job.
  433. /// </summary>
  434. /// <param name="deviceId">The device id.</param>
  435. /// <param name="playSessionId">The play session identifier.</param>
  436. /// <param name="deleteFiles">The delete files.</param>
  437. /// <returns>Task.</returns>
  438. internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
  439. {
  440. return KillTranscodingJobs(j =>
  441. {
  442. if (!string.IsNullOrWhiteSpace(playSessionId))
  443. {
  444. return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
  445. }
  446. return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
  447. }, deleteFiles);
  448. }
  449. /// <summary>
  450. /// Kills the transcoding jobs.
  451. /// </summary>
  452. /// <param name="killJob">The kill job.</param>
  453. /// <param name="deleteFiles">The delete files.</param>
  454. /// <returns>Task.</returns>
  455. private Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles)
  456. {
  457. var jobs = new List<TranscodingJob>();
  458. lock (_activeTranscodingJobs)
  459. {
  460. // This is really only needed for HLS.
  461. // Progressive streams can stop on their own reliably
  462. jobs.AddRange(_activeTranscodingJobs.Where(killJob));
  463. }
  464. if (jobs.Count == 0)
  465. {
  466. return Task.CompletedTask;
  467. }
  468. IEnumerable<Task> GetKillJobs()
  469. {
  470. foreach (var job in jobs)
  471. {
  472. yield return KillTranscodingJob(job, false, deleteFiles);
  473. }
  474. }
  475. return Task.WhenAll(GetKillJobs());
  476. }
  477. /// <summary>
  478. /// Kills the transcoding job.
  479. /// </summary>
  480. /// <param name="job">The job.</param>
  481. /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
  482. /// <param name="delete">The delete.</param>
  483. private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete)
  484. {
  485. job.DisposeKillTimer();
  486. Logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
  487. lock (_activeTranscodingJobs)
  488. {
  489. _activeTranscodingJobs.Remove(job);
  490. if (!job.CancellationTokenSource.IsCancellationRequested)
  491. {
  492. job.CancellationTokenSource.Cancel();
  493. }
  494. }
  495. lock (_transcodingLocks)
  496. {
  497. _transcodingLocks.Remove(job.Path);
  498. }
  499. lock (job.ProcessLock)
  500. {
  501. if (job.TranscodingThrottler != null)
  502. {
  503. job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
  504. }
  505. var process = job.Process;
  506. var hasExited = job.HasExited;
  507. if (!hasExited)
  508. {
  509. try
  510. {
  511. Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
  512. //process.Kill();
  513. process.StandardInput.WriteLine("q");
  514. // Need to wait because killing is asynchronous
  515. if (!process.WaitForExit(5000))
  516. {
  517. Logger.LogInformation("Killing ffmpeg process for {Path}", job.Path);
  518. process.Kill();
  519. }
  520. }
  521. catch (Exception ex)
  522. {
  523. Logger.LogError(ex, "Error killing transcoding job for {Path}", job.Path);
  524. }
  525. }
  526. }
  527. if (delete(job.Path))
  528. {
  529. await DeletePartialStreamFiles(job.Path, job.Type, 0, 1500).ConfigureAwait(false);
  530. }
  531. if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
  532. {
  533. try
  534. {
  535. await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
  536. }
  537. catch (Exception ex)
  538. {
  539. Logger.LogError(ex, "Error closing live stream for {Path}", job.Path);
  540. }
  541. }
  542. }
  543. private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
  544. {
  545. if (retryCount >= 10)
  546. {
  547. return;
  548. }
  549. Logger.LogInformation("Deleting partial stream file(s) {Path}", path);
  550. await Task.Delay(delayMs).ConfigureAwait(false);
  551. try
  552. {
  553. if (jobType == TranscodingJobType.Progressive)
  554. {
  555. DeleteProgressivePartialStreamFiles(path);
  556. }
  557. else
  558. {
  559. DeleteHlsPartialStreamFiles(path);
  560. }
  561. }
  562. catch (FileNotFoundException)
  563. {
  564. }
  565. catch (IOException ex)
  566. {
  567. Logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
  568. await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
  569. }
  570. catch (Exception ex)
  571. {
  572. Logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
  573. }
  574. }
  575. /// <summary>
  576. /// Deletes the progressive partial stream files.
  577. /// </summary>
  578. /// <param name="outputFilePath">The output file path.</param>
  579. private void DeleteProgressivePartialStreamFiles(string outputFilePath)
  580. {
  581. _fileSystem.DeleteFile(outputFilePath);
  582. }
  583. /// <summary>
  584. /// Deletes the HLS partial stream files.
  585. /// </summary>
  586. /// <param name="outputFilePath">The output file path.</param>
  587. private void DeleteHlsPartialStreamFiles(string outputFilePath)
  588. {
  589. var directory = Path.GetDirectoryName(outputFilePath);
  590. var name = Path.GetFileNameWithoutExtension(outputFilePath);
  591. var filesToDelete = _fileSystem.GetFilePaths(directory)
  592. .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1);
  593. Exception e = null;
  594. foreach (var file in filesToDelete)
  595. {
  596. try
  597. {
  598. //Logger.LogDebug("Deleting HLS file {0}", file);
  599. _fileSystem.DeleteFile(file);
  600. }
  601. catch (FileNotFoundException)
  602. {
  603. }
  604. catch (IOException ex)
  605. {
  606. e = ex;
  607. Logger.LogError(ex, "Error deleting HLS file {Path}", file);
  608. }
  609. }
  610. if (e != null)
  611. {
  612. throw e;
  613. }
  614. }
  615. }
  616. /// <summary>
  617. /// Class TranscodingJob
  618. /// </summary>
  619. public class TranscodingJob
  620. {
  621. /// <summary>
  622. /// Gets or sets the play session identifier.
  623. /// </summary>
  624. /// <value>The play session identifier.</value>
  625. public string PlaySessionId { get; set; }
  626. /// <summary>
  627. /// Gets or sets the live stream identifier.
  628. /// </summary>
  629. /// <value>The live stream identifier.</value>
  630. public string LiveStreamId { get; set; }
  631. public bool IsLiveOutput { get; set; }
  632. /// <summary>
  633. /// Gets or sets the path.
  634. /// </summary>
  635. /// <value>The path.</value>
  636. public MediaSourceInfo MediaSource { get; set; }
  637. public string Path { get; set; }
  638. /// <summary>
  639. /// Gets or sets the type.
  640. /// </summary>
  641. /// <value>The type.</value>
  642. public TranscodingJobType Type { get; set; }
  643. /// <summary>
  644. /// Gets or sets the process.
  645. /// </summary>
  646. /// <value>The process.</value>
  647. public Process Process { get; set; }
  648. public ILogger Logger { get; private set; }
  649. /// <summary>
  650. /// Gets or sets the active request count.
  651. /// </summary>
  652. /// <value>The active request count.</value>
  653. public int ActiveRequestCount { get; set; }
  654. /// <summary>
  655. /// Gets or sets the kill timer.
  656. /// </summary>
  657. /// <value>The kill timer.</value>
  658. private Timer KillTimer { get; set; }
  659. public string DeviceId { get; set; }
  660. public CancellationTokenSource CancellationTokenSource { get; set; }
  661. public object ProcessLock = new object();
  662. public bool HasExited { get; set; }
  663. public bool IsUserPaused { get; set; }
  664. public string Id { get; set; }
  665. public float? Framerate { get; set; }
  666. public double? CompletionPercentage { get; set; }
  667. public long? BytesDownloaded { get; set; }
  668. public long? BytesTranscoded { get; set; }
  669. public int? BitRate { get; set; }
  670. public long? TranscodingPositionTicks { get; set; }
  671. public long? DownloadPositionTicks { get; set; }
  672. public TranscodingThrottler TranscodingThrottler { get; set; }
  673. private readonly object _timerLock = new object();
  674. public DateTime LastPingDate { get; set; }
  675. public int PingTimeout { get; set; }
  676. public TranscodingJob(ILogger logger)
  677. {
  678. Logger = logger;
  679. }
  680. public void StopKillTimer()
  681. {
  682. lock (_timerLock)
  683. {
  684. if (KillTimer != null)
  685. {
  686. KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
  687. }
  688. }
  689. }
  690. public void DisposeKillTimer()
  691. {
  692. lock (_timerLock)
  693. {
  694. if (KillTimer != null)
  695. {
  696. KillTimer.Dispose();
  697. KillTimer = null;
  698. }
  699. }
  700. }
  701. public void StartKillTimer(Action<object> callback)
  702. {
  703. StartKillTimer(callback, PingTimeout);
  704. }
  705. public void StartKillTimer(Action<object> callback, int intervalMs)
  706. {
  707. if (HasExited)
  708. {
  709. return;
  710. }
  711. lock (_timerLock)
  712. {
  713. if (KillTimer == null)
  714. {
  715. //Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
  716. KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
  717. }
  718. else
  719. {
  720. //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
  721. KillTimer.Change(intervalMs, Timeout.Infinite);
  722. }
  723. }
  724. }
  725. public void ChangeKillTimerIfStarted()
  726. {
  727. if (HasExited)
  728. {
  729. return;
  730. }
  731. lock (_timerLock)
  732. {
  733. if (KillTimer != null)
  734. {
  735. var intervalMs = PingTimeout;
  736. //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
  737. KillTimer.Change(intervalMs, Timeout.Infinite);
  738. }
  739. }
  740. }
  741. }
  742. }