mirror of https://github.com/jellyfin/jellyfin.git
restore nuget targets for mono build
This commit is contained in:
parent
a3d553a7fb
commit
60d1d5cdee
|
@ -120,12 +120,15 @@ namespace MediaBrowser.Api
|
||||||
/// Called when [transcode beginning].
|
/// Called when [transcode beginning].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
/// <param name="deviceId">The device id.</param>
|
/// <param name="deviceId">The device id.</param>
|
||||||
/// <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>
|
||||||
public void OnTranscodeBeginning(string path,
|
/// <returns>TranscodingJob.</returns>
|
||||||
|
public TranscodingJob OnTranscodeBeginning(string path,
|
||||||
|
string transcodingJobId,
|
||||||
TranscodingJobType type,
|
TranscodingJobType type,
|
||||||
Process process,
|
Process process,
|
||||||
string deviceId,
|
string deviceId,
|
||||||
|
@ -134,22 +137,37 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
_activeTranscodingJobs.Add(new TranscodingJob
|
var job = new TranscodingJob
|
||||||
{
|
{
|
||||||
Type = type,
|
Type = type,
|
||||||
Path = path,
|
Path = path,
|
||||||
Process = process,
|
Process = process,
|
||||||
ActiveRequestCount = 1,
|
ActiveRequestCount = 1,
|
||||||
DeviceId = deviceId,
|
DeviceId = deviceId,
|
||||||
CancellationTokenSource = cancellationTokenSource
|
CancellationTokenSource = cancellationTokenSource,
|
||||||
});
|
Id = transcodingJobId
|
||||||
|
};
|
||||||
|
|
||||||
ReportTranscodingProgress(state, null, null);
|
_activeTranscodingJobs.Add(job);
|
||||||
|
|
||||||
|
ReportTranscodingProgress(job, state, null, null, null, null);
|
||||||
|
|
||||||
|
return job;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReportTranscodingProgress(StreamState state, float? framerate, double? percentComplete)
|
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
|
||||||
{
|
{
|
||||||
|
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
|
||||||
|
|
||||||
|
if (job != null)
|
||||||
|
{
|
||||||
|
job.Framerate = framerate;
|
||||||
|
job.CompletionPercentage = percentComplete;
|
||||||
|
job.TranscodingPositionTicks = ticks;
|
||||||
|
job.BytesTranscoded = bytesTranscoded;
|
||||||
|
}
|
||||||
|
|
||||||
var deviceId = state.Request.DeviceId;
|
var deviceId = state.Request.DeviceId;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||||
|
@ -226,12 +244,20 @@ namespace MediaBrowser.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TranscodingJob GetTranscodingJob(string id)
|
||||||
|
{
|
||||||
|
lock (_activeTranscodingJobs)
|
||||||
|
{
|
||||||
|
return _activeTranscodingJobs.FirstOrDefault(j => j.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [transcode begin request].
|
/// Called when [transcode begin request].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
public void OnTranscodeBeginRequest(string path, TranscodingJobType type)
|
public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type)
|
||||||
{
|
{
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
|
@ -239,7 +265,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
if (job == null)
|
if (job == null)
|
||||||
{
|
{
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
job.ActiveRequestCount++;
|
job.ActiveRequestCount++;
|
||||||
|
@ -249,31 +275,19 @@ namespace MediaBrowser.Api
|
||||||
job.KillTimer.Dispose();
|
job.KillTimer.Dispose();
|
||||||
job.KillTimer = null;
|
job.KillTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return job;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void OnTranscodeEndRequest(TranscodingJob job)
|
||||||
/// Called when [transcode end request].
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
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--;
|
job.ActiveRequestCount--;
|
||||||
|
|
||||||
if (job.ActiveRequestCount == 0)
|
if (job.ActiveRequestCount == 0)
|
||||||
{
|
{
|
||||||
// The HLS kill timer is long - 1/2 hr. clients should use the manual kill command when stopping.
|
// The HLS kill timer is long - 1/2 hr. clients should use the manual kill command when stopping.
|
||||||
var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 1800000;
|
var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : 1800000;
|
||||||
|
|
||||||
if (job.KillTimer == null)
|
if (job.KillTimer == null)
|
||||||
{
|
{
|
||||||
|
@ -285,7 +299,6 @@ namespace MediaBrowser.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [transcode kill timer stopped].
|
/// Called when [transcode kill timer stopped].
|
||||||
|
@ -306,7 +319,6 @@ namespace MediaBrowser.Api
|
||||||
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
|
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="ArgumentNullException">deviceId</exception>
|
/// <exception cref="ArgumentNullException">deviceId</exception>
|
||||||
/// <exception cref="System.ArgumentNullException">sourcePath</exception>
|
|
||||||
internal Task KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles, bool acquireLock)
|
internal Task KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles, bool acquireLock)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(deviceId))
|
if (string.IsNullOrEmpty(deviceId))
|
||||||
|
@ -324,8 +336,7 @@ namespace MediaBrowser.Api
|
||||||
/// <param name="deleteFiles">The delete files.</param>
|
/// <param name="deleteFiles">The delete files.</param>
|
||||||
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
|
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="System.ArgumentNullException">deviceId</exception>
|
internal async Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles, bool acquireLock)
|
||||||
internal async Task KillTranscodingJobs(Func<TranscodingJob,bool> killJob, Func<string, bool> deleteFiles, bool acquireLock)
|
|
||||||
{
|
{
|
||||||
var jobs = new List<TranscodingJob>();
|
var jobs = new List<TranscodingJob>();
|
||||||
|
|
||||||
|
@ -542,6 +553,17 @@ namespace MediaBrowser.Api
|
||||||
public object ProcessLock = new object();
|
public object ProcessLock = new object();
|
||||||
|
|
||||||
public bool HasExited { get; set; }
|
public bool HasExited { get; set; }
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public float? Framerate { get; set; }
|
||||||
|
public double? CompletionPercentage { get; set; }
|
||||||
|
|
||||||
|
public long? BytesDownloaded { get; set; }
|
||||||
|
public long? BytesTranscoded { get; set; }
|
||||||
|
|
||||||
|
public long? TranscodingPositionTicks { get; set; }
|
||||||
|
public long? DownloadPositionTicks { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -169,7 +169,7 @@
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
|
@ -90,10 +90,11 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// Gets the command line arguments.
|
/// Gets the command line arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
|
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
|
protected abstract string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of the transcoding job.
|
/// Gets the type of the transcoding job.
|
||||||
|
@ -122,7 +123,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
var outputFileExtension = GetOutputFileExtension(state);
|
var outputFileExtension = GetOutputFileExtension(state);
|
||||||
|
|
||||||
var data = GetCommandLineArguments("dummy\\dummy", state, false);
|
var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
|
||||||
|
|
||||||
data += "-" + (state.Request.DeviceId ?? string.Empty);
|
data += "-" + (state.Request.DeviceId ?? string.Empty);
|
||||||
|
|
||||||
|
@ -782,9 +783,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the input argument.
|
/// Gets the input argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetInputArgument(StreamState state)
|
protected string GetInputArgument(string transcodingJobId, StreamState state)
|
||||||
{
|
{
|
||||||
if (state.InputProtocol == MediaProtocol.File &&
|
if (state.InputProtocol == MediaProtocol.File &&
|
||||||
state.RunTimeTicks.HasValue &&
|
state.RunTimeTicks.HasValue &&
|
||||||
|
@ -795,6 +797,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/mediabrowser/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
|
var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/mediabrowser/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
|
||||||
|
|
||||||
|
url += "&transcodingJobId=" + transcodingJobId;
|
||||||
|
|
||||||
return string.Format("\"{0}\"", url);
|
return string.Format("\"{0}\"", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -897,7 +901,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="System.InvalidOperationException">ffmpeg was not found at + MediaEncoder.EncoderPath</exception>
|
/// <exception cref="System.InvalidOperationException">ffmpeg was not found at + MediaEncoder.EncoderPath</exception>
|
||||||
protected async Task StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource)
|
protected async Task<TranscodingJob> StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource)
|
||||||
{
|
{
|
||||||
if (!File.Exists(MediaEncoder.EncoderPath))
|
if (!File.Exists(MediaEncoder.EncoderPath))
|
||||||
{
|
{
|
||||||
|
@ -908,7 +912,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
|
||||||
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
var transcodingId = Guid.NewGuid().ToString("N");
|
||||||
|
var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
|
||||||
|
|
||||||
if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging)
|
if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging)
|
||||||
{
|
{
|
||||||
|
@ -938,7 +943,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
|
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
|
||||||
|
transcodingId,
|
||||||
TranscodingJobType,
|
TranscodingJobType,
|
||||||
process,
|
process,
|
||||||
state.Request.DeviceId,
|
state.Request.DeviceId,
|
||||||
|
@ -957,7 +963,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
|
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, state, outputPath);
|
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -976,16 +982,18 @@ namespace MediaBrowser.Api.Playback
|
||||||
process.BeginOutputReadLine();
|
process.BeginOutputReadLine();
|
||||||
|
|
||||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
|
StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
|
||||||
|
|
||||||
// Wait for the file to exist before proceeeding
|
// Wait for the file to exist before proceeeding
|
||||||
while (!File.Exists(outputPath))
|
while (!File.Exists(outputPath))
|
||||||
{
|
{
|
||||||
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return transcodingJob;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void StartStreamingLog(StreamState state, Stream source, Stream target)
|
private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -995,7 +1003,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
ParseLogLine(line, state);
|
ParseLogLine(line, transcodingJob, state);
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
|
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
|
||||||
|
|
||||||
|
@ -1009,10 +1017,12 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseLogLine(string line, StreamState state)
|
private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state)
|
||||||
{
|
{
|
||||||
float? framerate = null;
|
float? framerate = null;
|
||||||
double? percent = null;
|
double? percent = null;
|
||||||
|
TimeSpan? transcodingPosition = null;
|
||||||
|
long? bytesTranscoded = null;
|
||||||
|
|
||||||
var parts = line.Split(' ');
|
var parts = line.Split(' ');
|
||||||
|
|
||||||
|
@ -1051,13 +1061,36 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
var percentVal = currentMs / totalMs;
|
var percentVal = currentMs / totalMs;
|
||||||
percent = 100 * percentVal;
|
percent = 100 * percentVal;
|
||||||
|
|
||||||
|
transcodingPosition = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var size = part.Split(new[] { '=' }, 2).Last();
|
||||||
|
|
||||||
|
int? scale = null;
|
||||||
|
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
scale = 1024;
|
||||||
|
size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scale.HasValue)
|
||||||
|
{
|
||||||
|
long val;
|
||||||
|
|
||||||
|
if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
|
||||||
|
{
|
||||||
|
bytesTranscoded = val * scale.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (framerate.HasValue || percent.HasValue)
|
if (framerate.HasValue || percent.HasValue)
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.ReportTranscodingProgress(state, framerate, percent);
|
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1170,12 +1203,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
|
/// <param name="job">The job.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="outputPath">The output path.</param>
|
private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state)
|
||||||
private void OnFfMpegProcessExited(Process process, StreamState state, string outputPath)
|
|
||||||
{
|
{
|
||||||
var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType);
|
|
||||||
|
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
job.HasExited = true;
|
job.HasExited = true;
|
||||||
|
|
|
@ -88,10 +88,11 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
|
|
||||||
var playlist = state.OutputFilePath;
|
var playlist = state.OutputFilePath;
|
||||||
|
TranscodingJob job;
|
||||||
|
|
||||||
if (File.Exists(playlist))
|
if (File.Exists(playlist))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -100,14 +101,14 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
if (File.Exists(playlist))
|
if (File.Exists(playlist))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the playlist doesn't already exist, startup ffmpeg
|
// If the playlist doesn't already exist, startup ffmpeg
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
|
job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -137,7 +138,10 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
if (job != null)
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +166,10 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
if (job != null)
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,14 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||||
/// Gets the command line arguments.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPath">The output path.</param>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
|
||||||
{
|
{
|
||||||
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
|
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
|
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
|
||||||
itsOffset,
|
itsOffset,
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(state),
|
GetInputArgument(transcodingJobId, state),
|
||||||
threads,
|
threads,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state),
|
||||||
|
|
|
@ -6,6 +6,7 @@ using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
@ -107,11 +108,14 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
|
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
|
||||||
|
|
||||||
var segmentPath = GetSegmentPath(playlistPath, index);
|
var segmentPath = GetSegmentPath(playlistPath, index);
|
||||||
|
var segmentLength = state.SegmentLength;
|
||||||
|
|
||||||
|
TranscodingJob job = null;
|
||||||
|
|
||||||
if (File.Exists(segmentPath))
|
if (File.Exists(segmentPath))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
||||||
return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
|
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
@ -119,8 +123,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
if (File.Exists(segmentPath))
|
if (File.Exists(segmentPath))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
||||||
return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
|
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -141,7 +145,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
var startSeconds = index * state.SegmentLength;
|
var startSeconds = index * state.SegmentLength;
|
||||||
request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks;
|
request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks;
|
||||||
|
|
||||||
await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -165,7 +169,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info("returning {0}", segmentPath);
|
Logger.Info("returning {0}", segmentPath);
|
||||||
return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
|
job = job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType.Hls);
|
||||||
|
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? GetCurrentTranscodingIndex(string playlist)
|
public int? GetCurrentTranscodingIndex(string playlist)
|
||||||
|
@ -258,12 +263,17 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
return Path.Combine(folder, filename + index.ToString(UsCulture) + ".ts");
|
return Path.Combine(folder, filename + index.ToString(UsCulture) + ".ts");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<object> GetSegmentResult(string playlistPath, string segmentPath, int segmentIndex, CancellationToken cancellationToken)
|
private async Task<object> GetSegmentResult(string playlistPath,
|
||||||
|
string segmentPath,
|
||||||
|
int segmentIndex,
|
||||||
|
int segmentLength,
|
||||||
|
TranscodingJob transcodingJob,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// If all transcoding has completed, just return immediately
|
// If all transcoding has completed, just return immediately
|
||||||
if (!IsTranscoding(playlistPath))
|
if (!IsTranscoding(playlistPath))
|
||||||
{
|
{
|
||||||
return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
|
||||||
}
|
}
|
||||||
|
|
||||||
var segmentFilename = Path.GetFileName(segmentPath);
|
var segmentFilename = Path.GetFileName(segmentPath);
|
||||||
|
@ -277,7 +287,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
// If it appears in the playlist, it's done
|
// If it appears in the playlist, it's done
|
||||||
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
|
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +296,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
//var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
|
//var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
|
||||||
//if (currentTranscodingIndex > segmentIndex)
|
//if (currentTranscodingIndex > segmentIndex)
|
||||||
//{
|
//{
|
||||||
// return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
//return GetSegmentResult(segmentPath, segmentIndex);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// Wait for the file to stop being written to, then stream it
|
// Wait for the file to stop being written to, then stream it
|
||||||
|
@ -317,7 +327,27 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
|
||||||
|
{
|
||||||
|
var segmentEndingSeconds = (1 + index) * segmentLength;
|
||||||
|
var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
|
||||||
|
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
|
{
|
||||||
|
Path = segmentPath,
|
||||||
|
FileShare = FileShare.ReadWrite,
|
||||||
|
OnComplete = () =>
|
||||||
|
{
|
||||||
|
if (transcodingJob != null)
|
||||||
|
{
|
||||||
|
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsTranscoding(string playlistPath)
|
private bool IsTranscoding(string playlistPath)
|
||||||
|
@ -621,14 +651,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||||
/// Gets the command line arguments.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPath">The output path.</param>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
|
||||||
{
|
{
|
||||||
var threads = GetNumberOfThreads(state, false);
|
var threads = GetNumberOfThreads(state, false);
|
||||||
|
|
||||||
|
@ -639,7 +662,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(state),
|
GetInputArgument(transcodingJobId, state),
|
||||||
threads,
|
threads,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state),
|
||||||
|
|
|
@ -63,7 +63,17 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
public object Get(GetHlsPlaylist request)
|
public object Get(GetHlsPlaylist request)
|
||||||
{
|
{
|
||||||
OnBeginRequest(request.PlaylistId);
|
var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty);
|
||||||
|
|
||||||
|
foreach (var playlist in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*.m3u8")
|
||||||
|
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(playlist))
|
||||||
|
{
|
||||||
|
ExtendPlaylistTimer(playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
|
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
|
||||||
|
|
||||||
|
@ -93,32 +103,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
|
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when [begin request].
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="playlistId">The playlist id.</param>
|
|
||||||
protected void OnBeginRequest(string playlistId)
|
|
||||||
{
|
|
||||||
var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
|
|
||||||
|
|
||||||
foreach (var playlist in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*.m3u8")
|
|
||||||
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
.ToList())
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(playlist))
|
|
||||||
{
|
|
||||||
ExtendPlaylistTimer(playlist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ExtendPlaylistTimer(string playlist)
|
private async void ExtendPlaylistTimer(string playlist)
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
var job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
||||||
|
|
||||||
await Task.Delay(20000).ConfigureAwait(false);
|
await Task.Delay(20000).ConfigureAwait(false);
|
||||||
|
|
||||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
if (job != null)
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,18 +81,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
|
file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
|
||||||
|
|
||||||
OnBeginRequest(request.PlaylistId);
|
var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty);
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when [begin request].
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="playlistId">The playlist id.</param>
|
|
||||||
protected void OnBeginRequest(string playlistId)
|
|
||||||
{
|
|
||||||
var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
|
|
||||||
|
|
||||||
foreach (var playlist in Directory.EnumerateFiles(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, "*.m3u8")
|
foreach (var playlist in Directory.EnumerateFiles(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, "*.m3u8")
|
||||||
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
@ -100,15 +89,20 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
ExtendPlaylistTimer(playlist);
|
ExtendPlaylistTimer(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ExtendPlaylistTimer(string playlist)
|
private async void ExtendPlaylistTimer(string playlist)
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
var job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
||||||
|
|
||||||
await Task.Delay(20000).ConfigureAwait(false);
|
await Task.Delay(20000).ConfigureAwait(false);
|
||||||
|
|
||||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
if (job != null)
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -55,15 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
return ProcessRequest(request, true);
|
return ProcessRequest(request, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||||
/// Gets the command line arguments.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPath">The output path.</param>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
|
||||||
{
|
{
|
||||||
var audioTranscodeParams = new List<string>();
|
var audioTranscodeParams = new List<string>();
|
||||||
|
|
||||||
|
@ -92,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(state),
|
GetInputArgument(transcodingJobId, state),
|
||||||
threads,
|
threads,
|
||||||
vn,
|
vn,
|
||||||
string.Join(" ", audioTranscodeParams.ToArray()),
|
string.Join(" ", audioTranscodeParams.ToArray()),
|
||||||
|
|
|
@ -142,10 +142,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
var outputPath = state.OutputFilePath;
|
var outputPath = state.OutputFilePath;
|
||||||
var outputPathExists = File.Exists(outputPath);
|
var outputPathExists = File.Exists(outputPath);
|
||||||
|
|
||||||
var isStatic = request.Static ||
|
var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
||||||
(outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive));
|
|
||||||
|
|
||||||
AddDlnaHeaders(state, responseHeaders, isStatic);
|
AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
|
||||||
|
|
||||||
// Static stream
|
// Static stream
|
||||||
if (request.Static)
|
if (request.Static)
|
||||||
|
@ -154,6 +153,10 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
using (state)
|
using (state)
|
||||||
{
|
{
|
||||||
|
var job = string.IsNullOrEmpty(request.TranscodingJobId) ?
|
||||||
|
null :
|
||||||
|
ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId);
|
||||||
|
|
||||||
var limits = new List<long>();
|
var limits = new List<long>();
|
||||||
if (state.InputBitrate.HasValue)
|
if (state.InputBitrate.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -172,7 +175,13 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the greater of the above to methods, just to be safe
|
// Take the greater of the above to methods, just to be safe
|
||||||
var throttleLimit = limits.Count > 0 ? limits.Max() : 0;
|
var throttleLimit = limits.Count > 0 ? limits.First() : 0;
|
||||||
|
|
||||||
|
// Pad to play it safe
|
||||||
|
var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit);
|
||||||
|
|
||||||
|
// Don't even start evaluating this until at least two minutes have content have been consumed
|
||||||
|
var targetGap = throttleLimit * 120;
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
{
|
{
|
||||||
|
@ -182,17 +191,17 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
Path = state.MediaPath,
|
Path = state.MediaPath,
|
||||||
Throttle = request.Throttle,
|
Throttle = request.Throttle,
|
||||||
|
|
||||||
// Pad by 20% to play it safe
|
ThrottleLimit = bytesPerSecond,
|
||||||
ThrottleLimit = Convert.ToInt64(1.2 * throttleLimit),
|
|
||||||
|
|
||||||
// 3.5 minutes
|
MinThrottlePosition = targetGap,
|
||||||
MinThrottlePosition = throttleLimit * 210
|
|
||||||
|
ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not static but transcode cache file exists
|
// Not static but transcode cache file exists
|
||||||
if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
|
if (isTranscodeCached)
|
||||||
{
|
{
|
||||||
var contentType = state.GetMimeType(outputPath);
|
var contentType = state.GetMimeType(outputPath);
|
||||||
|
|
||||||
|
@ -225,6 +234,67 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(3).Ticks;
|
||||||
|
|
||||||
|
private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job)
|
||||||
|
{
|
||||||
|
var bytesDownloaded = job.BytesDownloaded ?? 0;
|
||||||
|
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
|
||||||
|
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
|
||||||
|
|
||||||
|
var path = job.Path;
|
||||||
|
|
||||||
|
if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
|
||||||
|
{
|
||||||
|
// Progressive Streaming - byte-based consideration
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
|
||||||
|
|
||||||
|
// Estimate the bytes the transcoder should be ahead
|
||||||
|
double gapFactor = _gapLengthInTicks;
|
||||||
|
gapFactor /= transcodingPositionTicks;
|
||||||
|
var targetGap = bytesTranscoded * gapFactor;
|
||||||
|
|
||||||
|
var gap = bytesTranscoded - bytesDownloaded;
|
||||||
|
|
||||||
|
if (gap < targetGap)
|
||||||
|
{
|
||||||
|
//Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//Logger.Error("Error getting output size");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
|
||||||
|
{
|
||||||
|
// HLS - time-based consideration
|
||||||
|
|
||||||
|
var targetGap = _gapLengthInTicks;
|
||||||
|
var gap = transcodingPositionTicks - downloadPositionTicks;
|
||||||
|
|
||||||
|
if (gap < targetGap)
|
||||||
|
{
|
||||||
|
//Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Logger.Debug("No throttle data for " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalBytesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the static remote stream result.
|
/// Gets the static remote stream result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -325,17 +395,18 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
TranscodingJob job;
|
||||||
|
|
||||||
if (!File.Exists(outputPath))
|
if (!File.Exists(outputPath))
|
||||||
{
|
{
|
||||||
await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
|
job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
|
||||||
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
|
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
|
||||||
|
|
||||||
result.Options["Content-Type"] = contentType;
|
result.Options["Content-Type"] = contentType;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
using System;
|
using System.Threading;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -73,7 +74,10 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(Path, TranscodingJobType.Progressive);
|
if (_job != null)
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +87,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly TranscodingJob _job;
|
private readonly TranscodingJob _job;
|
||||||
|
|
||||||
|
private long _bytesWritten = 0;
|
||||||
|
|
||||||
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
|
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
@ -98,7 +104,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
while (eofCount < 15)
|
while (eofCount < 15)
|
||||||
{
|
{
|
||||||
await fs.CopyToAsync(outputStream).ConfigureAwait(false);
|
await CopyToAsyncInternal(fs, outputStream, 81920, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var fsPosition = fs.Position;
|
var fsPosition = fs.Position;
|
||||||
|
|
||||||
|
@ -123,5 +129,22 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CopyToAsyncInternal(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
byte[] array = new byte[bufferSize];
|
||||||
|
int count;
|
||||||
|
while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(array, 0, count, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_bytesWritten += count;
|
||||||
|
|
||||||
|
if (_job != null)
|
||||||
|
{
|
||||||
|
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,14 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
return ProcessRequest(request, true);
|
return ProcessRequest(request, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||||
/// Gets the command line arguments.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPath">The output path.</param>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
|
||||||
{
|
{
|
||||||
// Get the output codec name
|
// Get the output codec name
|
||||||
var videoCodec = state.OutputVideoCodec;
|
var videoCodec = state.OutputVideoCodec;
|
||||||
|
@ -110,7 +103,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
|
return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(state),
|
GetInputArgument(transcodingJobId, state),
|
||||||
keyFrame,
|
keyFrame,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state, videoCodec),
|
GetVideoArguments(state, videoCodec),
|
||||||
|
|
|
@ -72,6 +72,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
public string Params { get; set; }
|
public string Params { get; set; }
|
||||||
|
|
||||||
public bool Throttle { get; set; }
|
public bool Throttle { get; set; }
|
||||||
|
public string TranscodingJobId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VideoStreamRequest : StreamRequest
|
public class VideoStreamRequest : StreamRequest
|
||||||
|
|
|
@ -205,7 +205,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
list.Add(await GetUserView(CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
|
//list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
|
||||||
|
|
||||||
return GetResult(list, query);
|
return GetResult(list, query);
|
||||||
}
|
}
|
||||||
|
@ -283,7 +283,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
list.Add(await GetUserView(CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.TvSeries, user, "3", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.TvSeries, user, "3", parent).ConfigureAwait(false));
|
||||||
//list.Add(await GetUserView(CollectionType.TvFavorites, user, "4", parent).ConfigureAwait(false));
|
//list.Add(await GetUserView(CollectionType.TvFavorites, user, "4", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
|
//list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
|
||||||
|
|
||||||
return GetResult(list, query);
|
return GetResult(list, query);
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
list.Add(await GetUserView(CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false));
|
list.Add(await GetUserView(CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false));
|
||||||
list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
|
//list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
|
||||||
|
|
||||||
return GetResult(list, query);
|
return GetResult(list, query);
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,7 +363,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
|
||||||
<PreBuildEvent>
|
<PreBuildEvent>
|
||||||
</PreBuildEvent>
|
</PreBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
|
@ -21,6 +21,9 @@ namespace MediaBrowser.Controller.Net
|
||||||
public bool Throttle { get; set; }
|
public bool Throttle { get; set; }
|
||||||
public long ThrottleLimit { get; set; }
|
public long ThrottleLimit { get; set; }
|
||||||
public long MinThrottlePosition { get; set; }
|
public long MinThrottlePosition { get; set; }
|
||||||
|
public Func<long, long, long> ThrottleCallback { get; set; }
|
||||||
|
|
||||||
|
public Action OnComplete { get; set; }
|
||||||
|
|
||||||
public StaticResultOptions()
|
public StaticResultOptions()
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,8 +22,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SendCount { get; private set; }
|
public int SendCount { get; private set; }
|
||||||
|
|
||||||
public bool HandleBindError { get; set; }
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public Datagram(IPEndPoint toEndPoint, IPEndPoint fromEndPoint, ILogger logger, string message, int totalSendCount)
|
public Datagram(IPEndPoint toEndPoint, IPEndPoint fromEndPoint, ILogger logger, string message, int totalSendCount)
|
||||||
|
@ -43,19 +41,9 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
var client = CreateSocket();
|
var client = CreateSocket();
|
||||||
|
|
||||||
if (FromEndPoint != null)
|
if (FromEndPoint != null)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
client.Bind(FromEndPoint);
|
client.Bind(FromEndPoint);
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (!HandleBindError)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, ToEndPoint, result =>
|
client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, ToEndPoint, result =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -124,22 +124,18 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
IPEndPoint localAddress,
|
IPEndPoint localAddress,
|
||||||
int sendCount = 1)
|
int sendCount = 1)
|
||||||
{
|
{
|
||||||
SendDatagram(header, values, _ssdpEndp, localAddress, false, sendCount);
|
SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendDatagram(string header,
|
public void SendDatagram(string header,
|
||||||
Dictionary<string, string> values,
|
Dictionary<string, string> values,
|
||||||
IPEndPoint endpoint,
|
IPEndPoint endpoint,
|
||||||
IPEndPoint localAddress,
|
IPEndPoint localAddress,
|
||||||
bool handleBindError,
|
|
||||||
int sendCount = 1)
|
int sendCount = 1)
|
||||||
{
|
{
|
||||||
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
|
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
|
||||||
|
|
||||||
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount)
|
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
|
||||||
{
|
|
||||||
HandleBindError = handleBindError
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_messageQueue.Count == 0)
|
if (_messageQueue.Count == 0)
|
||||||
{
|
{
|
||||||
|
@ -175,7 +171,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
values["ST"] = d.Type;
|
values["ST"] = d.Type;
|
||||||
values["USN"] = d.USN;
|
values["USN"] = d.USN;
|
||||||
|
|
||||||
SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0), true);
|
SendDatagram(header, values, endpoint, null);
|
||||||
|
|
||||||
if (_config.GetDlnaConfiguration().EnableDebugLogging)
|
if (_config.GetDlnaConfiguration().EnableDebugLogging)
|
||||||
{
|
{
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
|
@ -372,7 +372,7 @@
|
||||||
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
|
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
|
||||||
)</PostBuildEvent>
|
)</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<Import Project="Fody.targets" />
|
<Import Project="Fody.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
|
|
@ -211,7 +211,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
|
@ -378,8 +378,8 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
if (album != null)
|
if (album != null)
|
||||||
{
|
{
|
||||||
dto.Album = item.Name;
|
dto.Album = album.Name;
|
||||||
dto.AlbumId = item.Id.ToString("N");
|
dto.AlbumId = album.Id.ToString("N");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,14 +102,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool SupportsCompression
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the optimized result.
|
/// Gets the optimized result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -127,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
throw new ArgumentNullException("result");
|
throw new ArgumentNullException("result");
|
||||||
}
|
}
|
||||||
|
|
||||||
var optimizedResult = SupportsCompression ? requestContext.ToOptimizedResult(result) : result;
|
var optimizedResult = requestContext.ToOptimizedResult(result);
|
||||||
|
|
||||||
if (responseHeaders != null)
|
if (responseHeaders != null)
|
||||||
{
|
{
|
||||||
|
@ -471,7 +463,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
Throttle = options.Throttle,
|
Throttle = options.Throttle,
|
||||||
ThrottleLimit = options.ThrottleLimit,
|
ThrottleLimit = options.ThrottleLimit,
|
||||||
MinThrottlePosition = options.MinThrottlePosition
|
MinThrottlePosition = options.MinThrottlePosition,
|
||||||
|
ThrottleCallback = options.ThrottleCallback,
|
||||||
|
OnComplete = options.OnComplete
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,40 +482,21 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
Throttle = options.Throttle,
|
Throttle = options.Throttle,
|
||||||
ThrottleLimit = options.ThrottleLimit,
|
ThrottleLimit = options.ThrottleLimit,
|
||||||
MinThrottlePosition = options.MinThrottlePosition
|
MinThrottlePosition = options.MinThrottlePosition,
|
||||||
|
ThrottleCallback = options.ThrottleCallback,
|
||||||
|
OnComplete = options.OnComplete
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
string content;
|
string content;
|
||||||
long originalContentLength = 0;
|
|
||||||
|
|
||||||
using (var stream = await factoryFn().ConfigureAwait(false))
|
using (var stream = await factoryFn().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
using (var memoryStream = new MemoryStream())
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
|
||||||
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
|
||||||
memoryStream.Position = 0;
|
|
||||||
|
|
||||||
originalContentLength = memoryStream.Length;
|
|
||||||
|
|
||||||
using (var reader = new StreamReader(memoryStream))
|
|
||||||
{
|
{
|
||||||
content = await reader.ReadToEndAsync().ConfigureAwait(false);
|
content = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!SupportsCompression)
|
|
||||||
{
|
|
||||||
responseHeaders["Content-Length"] = originalContentLength.ToString(UsCulture);
|
|
||||||
|
|
||||||
if (isHeadRequest)
|
|
||||||
{
|
|
||||||
return GetHttpResult(new byte[] { }, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HttpResult(content, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
var contents = content.Compress(requestedCompressionType);
|
var contents = content.Compress(requestedCompressionType);
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
public bool Throttle { get; set; }
|
public bool Throttle { get; set; }
|
||||||
public long ThrottleLimit { get; set; }
|
public long ThrottleLimit { get; set; }
|
||||||
public long MinThrottlePosition;
|
public long MinThrottlePosition;
|
||||||
|
public Func<long, long, long> ThrottleCallback { get; set; }
|
||||||
|
public Action OnComplete { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _options
|
/// The _options
|
||||||
|
@ -167,7 +169,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
||||||
{
|
{
|
||||||
MinThrottlePosition = MinThrottlePosition
|
MinThrottlePosition = MinThrottlePosition,
|
||||||
|
ThrottleCallback = ThrottleCallback
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var task = WriteToAsync(responseStream);
|
var task = WriteToAsync(responseStream);
|
||||||
|
@ -181,6 +184,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task WriteToAsync(Stream responseStream)
|
private async Task WriteToAsync(Stream responseStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Headers only
|
// Headers only
|
||||||
if (IsHeadRequest)
|
if (IsHeadRequest)
|
||||||
|
@ -201,6 +206,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (OnComplete != null)
|
||||||
|
{
|
||||||
|
OnComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CopyToAsyncInternal(Stream source, Stream destination, int copyLength, CancellationToken cancellationToken)
|
private async Task CopyToAsyncInternal(Stream source, Stream destination, int copyLength, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
public bool Throttle { get; set; }
|
public bool Throttle { get; set; }
|
||||||
public long ThrottleLimit { get; set; }
|
public long ThrottleLimit { get; set; }
|
||||||
public long MinThrottlePosition;
|
public long MinThrottlePosition;
|
||||||
|
public Func<long, long, long> ThrottleCallback { get; set; }
|
||||||
|
public Action OnComplete { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
||||||
|
@ -85,7 +87,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
||||||
{
|
{
|
||||||
MinThrottlePosition = MinThrottlePosition
|
MinThrottlePosition = MinThrottlePosition,
|
||||||
|
ThrottleCallback = ThrottleCallback
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var task = WriteToAsync(responseStream);
|
var task = WriteToAsync(responseStream);
|
||||||
|
@ -113,6 +116,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (OnComplete != null)
|
||||||
|
{
|
||||||
|
OnComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const long Infinite = 0;
|
public const long Infinite = 0;
|
||||||
|
|
||||||
|
public Func<long, long, long> ThrottleCallback { get; set; }
|
||||||
|
|
||||||
#region Private members
|
#region Private members
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base stream.
|
/// The base stream.
|
||||||
|
@ -278,6 +280,32 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private bool ThrottleCheck(int bufferSizeInBytes)
|
||||||
|
{
|
||||||
|
if (_bytesWritten < MinThrottlePosition)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the buffer isn't empty.
|
||||||
|
if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ThrottleCallback != null)
|
||||||
|
{
|
||||||
|
var val = ThrottleCallback(_maximumBytesPerSecond, _bytesWritten);
|
||||||
|
|
||||||
|
if (val == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#region Protected methods
|
#region Protected methods
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Throttles for the specified buffer size in bytes.
|
/// Throttles for the specified buffer size in bytes.
|
||||||
|
@ -285,15 +313,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// <param name="bufferSizeInBytes">The buffer size in bytes.</param>
|
/// <param name="bufferSizeInBytes">The buffer size in bytes.</param>
|
||||||
protected void Throttle(int bufferSizeInBytes)
|
protected void Throttle(int bufferSizeInBytes)
|
||||||
{
|
{
|
||||||
if (_bytesWritten < MinThrottlePosition)
|
if (!ThrottleCheck(bufferSizeInBytes))
|
||||||
{
|
{
|
||||||
return;
|
return ;
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the buffer isn't empty.
|
|
||||||
if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_byteCount += bufferSizeInBytes;
|
_byteCount += bufferSizeInBytes;
|
||||||
|
@ -332,13 +354,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
|
|
||||||
protected async Task ThrottleAsync(int bufferSizeInBytes, CancellationToken cancellationToken)
|
protected async Task ThrottleAsync(int bufferSizeInBytes, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_bytesWritten < MinThrottlePosition)
|
if (!ThrottleCheck(bufferSizeInBytes))
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the buffer isn't empty.
|
|
||||||
if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,7 +501,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
|
@ -2136,7 +2136,7 @@
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
Loading…
Reference in New Issue