|
|
|
@ -30,57 +30,62 @@ public abstract class StreamingControllerBase(IGraphicsEngine graphicsEngine, IL |
|
|
|
|
|
|
|
|
|
|
|
foreach (PlayoutItemProcessModel processModel in result.RightToSeq()) |
|
|
|
foreach (PlayoutItemProcessModel processModel in result.RightToSeq()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// for process counter
|
|
|
|
return StartPlayout(processModel); |
|
|
|
var ffmpegProcess = new FFmpegProcess(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Command process = processModel.Process; |
|
|
|
// this will never happen
|
|
|
|
|
|
|
|
return new NotFoundResult(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
logger.LogDebug("ffmpeg arguments {FFmpegArguments}", process.Arguments); |
|
|
|
private FileStreamResult StartPlayout(PlayoutItemProcessModel processModel) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// for process counter
|
|
|
|
|
|
|
|
var ffmpegProcess = new FFmpegProcess(); |
|
|
|
|
|
|
|
Command process = processModel.Process; |
|
|
|
|
|
|
|
|
|
|
|
var cts = new CancellationTokenSource(); |
|
|
|
logger.LogDebug("ffmpeg arguments {FFmpegArguments}", process.Arguments); |
|
|
|
HttpContext.Response.OnCompleted(async () => |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
ffmpegProcess.Dispose(); |
|
|
|
|
|
|
|
await cts.CancelAsync(); |
|
|
|
|
|
|
|
cts.Dispose(); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( |
|
|
|
var cts = new CancellationTokenSource(); |
|
|
|
cts.Token, |
|
|
|
|
|
|
|
HttpContext.RequestAborted); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var pipe = new Pipe(); |
|
|
|
// do not use 'using' here; the token needs to live longer than this method scope
|
|
|
|
var stdErrBuffer = new StringBuilder(); |
|
|
|
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( |
|
|
|
|
|
|
|
cts.Token, |
|
|
|
|
|
|
|
HttpContext.RequestAborted); |
|
|
|
|
|
|
|
|
|
|
|
Command processWithPipe = process; |
|
|
|
var pipe = new Pipe(); |
|
|
|
foreach (GraphicsEngineContext graphicsEngineContext in processModel.GraphicsEngineContext) |
|
|
|
var stdErrBuffer = new StringBuilder(); |
|
|
|
{ |
|
|
|
|
|
|
|
var gePipe = new Pipe(); |
|
|
|
|
|
|
|
processWithPipe = process.WithStandardInputPipe(PipeSource.FromStream(gePipe.Reader.AsStream())); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// fire and forget graphics engine task
|
|
|
|
Command processWithPipe = process; |
|
|
|
_ = graphicsEngine.Run( |
|
|
|
foreach (GraphicsEngineContext graphicsEngineContext in processModel.GraphicsEngineContext) |
|
|
|
graphicsEngineContext, |
|
|
|
{ |
|
|
|
gePipe.Writer, |
|
|
|
var gePipe = new Pipe(); |
|
|
|
linkedCts.Token); |
|
|
|
processWithPipe = process.WithStandardInputPipe(PipeSource.FromStream(gePipe.Reader.AsStream())); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CommandTask<CommandResult> task = processWithPipe |
|
|
|
// fire and forget graphics engine task
|
|
|
|
.WithStandardOutputPipe(PipeTarget.ToStream(pipe.Writer.AsStream())) |
|
|
|
_ = graphicsEngine.Run( |
|
|
|
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) |
|
|
|
graphicsEngineContext, |
|
|
|
.WithValidation(CommandResultValidation.None) |
|
|
|
gePipe.Writer, |
|
|
|
.ExecuteAsync(linkedCts.Token); |
|
|
|
linkedCts.Token); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ensure pipe writer is completed when ffmpeg exits
|
|
|
|
CommandTask<CommandResult> task = processWithPipe |
|
|
|
_ = task.Task.ContinueWith( |
|
|
|
.WithStandardOutputPipe(PipeTarget.ToStream(pipe.Writer.AsStream())) |
|
|
|
(_, state) => ((PipeWriter)state!).Complete(), |
|
|
|
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) |
|
|
|
pipe.Writer, |
|
|
|
.WithValidation(CommandResultValidation.None) |
|
|
|
TaskScheduler.Default); |
|
|
|
.ExecuteAsync(linkedCts.Token); |
|
|
|
|
|
|
|
|
|
|
|
return new FileStreamResult(pipe.Reader.AsStream(), "video/mp2t"); |
|
|
|
// ensure cleanup happens when ffmpeg exits (either naturally or via cancellation)
|
|
|
|
} |
|
|
|
_ = task.Task.ContinueWith( |
|
|
|
|
|
|
|
(t, _) => |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
pipe.Writer.Complete(t.Exception); |
|
|
|
|
|
|
|
ffmpegProcess.Dispose(); |
|
|
|
|
|
|
|
linkedCts.Dispose(); |
|
|
|
|
|
|
|
cts.Dispose(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
null, |
|
|
|
|
|
|
|
TaskScheduler.Default); |
|
|
|
|
|
|
|
|
|
|
|
// this will never happen
|
|
|
|
return new FileStreamResult(pipe.Reader.AsStream(), "video/mp2t"); |
|
|
|
return new NotFoundResult(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|