Browse Source

fix hls direct streams remaining open (#2660)

pull/2661/head
Jason Dove 1 month ago committed by GitHub
parent
commit
cc287ffc6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 87
      ErsatzTV/Controllers/StreamingControllerBase.cs

1
CHANGELOG.md

@ -88,6 +88,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Playout builds now use JsonSchema.Net library which has no validation limit - Playout builds now use JsonSchema.Net library which has no validation limit
- Validation tool in the UI still uses Newtonsoft.Json.Schema (with 1000/hr limit) as the error output is easier to understand - Validation tool in the UI still uses Newtonsoft.Json.Schema (with 1000/hr limit) as the error output is easier to understand
- Fix editing scripted and sequential playouts when using MySql - Fix editing scripted and sequential playouts when using MySql
- Fix HLS Direct streams remaining open after client disconnect
### Changed ### Changed
- Classic schedules: `Refresh` classic playouts from playout list; do not `Reset` them - Classic schedules: `Refresh` classic playouts from playout list; do not `Reset` them

87
ErsatzTV/Controllers/StreamingControllerBase.cs

@ -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();
} }
} }

Loading…
Cancel
Save