|
|
@ -1,5 +1,6 @@
|
|
|
|
using System;
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.IO;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading;
|
|
|
@ -58,6 +59,8 @@ namespace MediaBrowser.Api
|
|
|
|
private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks =
|
|
|
|
private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks =
|
|
|
|
new Dictionary<string, SemaphoreSlim>();
|
|
|
|
new Dictionary<string, SemaphoreSlim>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool _disposed = false;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
|
|
|
|
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
@ -66,7 +69,15 @@ namespace MediaBrowser.Api
|
|
|
|
/// <param name="config">The configuration.</param>
|
|
|
|
/// <param name="config">The configuration.</param>
|
|
|
|
/// <param name="fileSystem">The file system.</param>
|
|
|
|
/// <param name="fileSystem">The file system.</param>
|
|
|
|
/// <param name="mediaSourceManager">The media source manager.</param>
|
|
|
|
/// <param name="mediaSourceManager">The media source manager.</param>
|
|
|
|
public ApiEntryPoint(ILogger logger, ISessionManager sessionManager, IServerConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager, ITimerFactory timerFactory, IProcessFactory processFactory, IHttpResultFactory resultFactory)
|
|
|
|
public ApiEntryPoint(
|
|
|
|
|
|
|
|
ILogger logger,
|
|
|
|
|
|
|
|
ISessionManager sessionManager,
|
|
|
|
|
|
|
|
IServerConfigurationManager config,
|
|
|
|
|
|
|
|
IFileSystem fileSystem,
|
|
|
|
|
|
|
|
IMediaSourceManager mediaSourceManager,
|
|
|
|
|
|
|
|
ITimerFactory timerFactory,
|
|
|
|
|
|
|
|
IProcessFactory processFactory,
|
|
|
|
|
|
|
|
IHttpResultFactory resultFactory)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Logger = logger;
|
|
|
|
Logger = logger;
|
|
|
|
_sessionManager = sessionManager;
|
|
|
|
_sessionManager = sessionManager;
|
|
|
@ -77,9 +88,10 @@ namespace MediaBrowser.Api
|
|
|
|
ProcessFactory = processFactory;
|
|
|
|
ProcessFactory = processFactory;
|
|
|
|
ResultFactory = resultFactory;
|
|
|
|
ResultFactory = resultFactory;
|
|
|
|
|
|
|
|
|
|
|
|
Instance = this;
|
|
|
|
|
|
|
|
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
|
|
|
|
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
|
|
|
|
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
|
|
|
|
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Instance = this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string[] Split(string value, char separator, bool removeEmpty)
|
|
|
|
public static string[] Split(string value, char separator, bool removeEmpty)
|
|
|
@ -185,17 +197,40 @@ namespace MediaBrowser.Api
|
|
|
|
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
|
|
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
|
|
protected virtual void Dispose(bool dispose)
|
|
|
|
protected virtual void Dispose(bool dispose)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var list = _activeTranscodingJobs.ToList();
|
|
|
|
if (_disposed)
|
|
|
|
var jobCount = list.Count;
|
|
|
|
{
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (dispose)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// TODO: dispose
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true));
|
|
|
|
var jobs = _activeTranscodingJobs.ToList();
|
|
|
|
|
|
|
|
var jobCount = jobs.Count;
|
|
|
|
|
|
|
|
|
|
|
|
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
|
|
|
|
IEnumerable<Task> GetKillJobs()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var job in jobs)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
yield return KillTranscodingJob(job, false, path => true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for all processes to be killed
|
|
|
|
if (jobCount > 0)
|
|
|
|
if (jobCount > 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var task = Task.Delay(1000);
|
|
|
|
Task.WaitAll(GetKillJobs().ToArray());
|
|
|
|
Task.WaitAll(task);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_activeTranscodingJobs.Clear();
|
|
|
|
|
|
|
|
_transcodingLocks.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress;
|
|
|
|
|
|
|
|
_sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_disposed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -212,12 +247,13 @@ namespace MediaBrowser.Api
|
|
|
|
/// <param name="state">The state.</param>
|
|
|
|
/// <param name="state">The state.</param>
|
|
|
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
|
|
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
|
|
|
/// <returns>TranscodingJob.</returns>
|
|
|
|
/// <returns>TranscodingJob.</returns>
|
|
|
|
public TranscodingJob OnTranscodeBeginning(string path,
|
|
|
|
public TranscodingJob OnTranscodeBeginning(
|
|
|
|
|
|
|
|
string path,
|
|
|
|
string playSessionId,
|
|
|
|
string playSessionId,
|
|
|
|
string liveStreamId,
|
|
|
|
string liveStreamId,
|
|
|
|
string transcodingJobId,
|
|
|
|
string transcodingJobId,
|
|
|
|
TranscodingJobType type,
|
|
|
|
TranscodingJobType type,
|
|
|
|
IProcess process,
|
|
|
|
Process process,
|
|
|
|
string deviceId,
|
|
|
|
string deviceId,
|
|
|
|
StreamState state,
|
|
|
|
StreamState state,
|
|
|
|
CancellationTokenSource cancellationTokenSource)
|
|
|
|
CancellationTokenSource cancellationTokenSource)
|
|
|
@ -446,7 +482,7 @@ namespace MediaBrowser.Api
|
|
|
|
/// Called when [transcode kill timer stopped].
|
|
|
|
/// Called when [transcode kill timer stopped].
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="state">The state.</param>
|
|
|
|
/// <param name="state">The state.</param>
|
|
|
|
private void OnTranscodeKillTimerStopped(object state)
|
|
|
|
private async void OnTranscodeKillTimerStopped(object state)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var job = (TranscodingJob)state;
|
|
|
|
var job = (TranscodingJob)state;
|
|
|
|
|
|
|
|
|
|
|
@ -463,7 +499,7 @@ namespace MediaBrowser.Api
|
|
|
|
|
|
|
|
|
|
|
|
Logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
|
|
|
|
Logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
|
|
|
|
|
|
|
|
|
|
|
|
KillTranscodingJob(job, true, path => true).GetAwaiter().GetResult();
|
|
|
|
await KillTranscodingJob(job, true, path => true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
@ -551,7 +587,7 @@ namespace MediaBrowser.Api
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (job.TranscodingThrottler != null)
|
|
|
|
if (job.TranscodingThrottler != null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
job.TranscodingThrottler.Stop();
|
|
|
|
job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var process = job.Process;
|
|
|
|
var process = job.Process;
|
|
|
@ -562,7 +598,7 @@ namespace MediaBrowser.Api
|
|
|
|
{
|
|
|
|
{
|
|
|
|
try
|
|
|
|
try
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Logger.LogInformation("Stopping ffmpeg process with q command for {path}", job.Path);
|
|
|
|
Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
|
|
|
|
|
|
|
|
|
|
|
|
//process.Kill();
|
|
|
|
//process.Kill();
|
|
|
|
process.StandardInput.WriteLine("q");
|
|
|
|
process.StandardInput.WriteLine("q");
|
|
|
@ -570,13 +606,13 @@ namespace MediaBrowser.Api
|
|
|
|
// Need to wait because killing is asynchronous
|
|
|
|
// Need to wait because killing is asynchronous
|
|
|
|
if (!process.WaitForExit(5000))
|
|
|
|
if (!process.WaitForExit(5000))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Logger.LogInformation("Killing ffmpeg process for {path}", job.Path);
|
|
|
|
Logger.LogInformation("Killing ffmpeg process for {Path}", job.Path);
|
|
|
|
process.Kill();
|
|
|
|
process.Kill();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Logger.LogError(ex, "Error killing transcoding job for {path}", job.Path);
|
|
|
|
Logger.LogError(ex, "Error killing transcoding job for {Path}", job.Path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -606,7 +642,7 @@ namespace MediaBrowser.Api
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Logger.LogInformation("Deleting partial stream file(s) {0}", path);
|
|
|
|
Logger.LogInformation("Deleting partial stream file(s) {Path}", path);
|
|
|
|
|
|
|
|
|
|
|
|
await Task.Delay(delayMs).ConfigureAwait(false);
|
|
|
|
await Task.Delay(delayMs).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
@ -627,13 +663,13 @@ namespace MediaBrowser.Api
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (IOException ex)
|
|
|
|
catch (IOException ex)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path);
|
|
|
|
Logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
|
|
|
|
|
|
|
|
|
|
|
|
await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
|
|
|
|
await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path);
|
|
|
|
Logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -674,7 +710,7 @@ namespace MediaBrowser.Api
|
|
|
|
catch (IOException ex)
|
|
|
|
catch (IOException ex)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
e = ex;
|
|
|
|
e = ex;
|
|
|
|
Logger.LogError(ex, "Error deleting HLS file {path}", file);
|
|
|
|
Logger.LogError(ex, "Error deleting HLS file {Path}", file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -718,7 +754,7 @@ namespace MediaBrowser.Api
|
|
|
|
/// Gets or sets the process.
|
|
|
|
/// Gets or sets the process.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <value>The process.</value>
|
|
|
|
/// <value>The process.</value>
|
|
|
|
public IProcess Process { get; set; }
|
|
|
|
public Process Process { get; set; }
|
|
|
|
public ILogger Logger { get; private set; }
|
|
|
|
public ILogger Logger { get; private set; }
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the active request count.
|
|
|
|
/// Gets or sets the active request count.
|
|
|
|