mirror of https://github.com/ErsatzTV/ErsatzTV.git
5 changed files with 137 additions and 91 deletions
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
using System.IO.Pipelines; |
||||
using System.Text; |
||||
using CliWrap; |
||||
using ErsatzTV.Application.Streaming; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.FFmpeg; |
||||
using ErsatzTV.Core.Interfaces.Streaming; |
||||
using Microsoft.AspNetCore.Mvc; |
||||
|
||||
namespace ErsatzTV.Controllers; |
||||
|
||||
public abstract class StreamingControllerBase(IGraphicsEngine graphicsEngine, ILogger logger) |
||||
: ControllerBase |
||||
{ |
||||
protected IActionResult GetProcessResponse( |
||||
Either<BaseError, PlayoutItemProcessModel> result, |
||||
string channelNumber, |
||||
StreamingMode mode) |
||||
{ |
||||
foreach (BaseError error in result.LeftToSeq()) |
||||
{ |
||||
logger.LogError( |
||||
"Failed to create stream for channel {ChannelNumber}: {Error}", |
||||
channelNumber, |
||||
error.Value); |
||||
|
||||
return BadRequest(error.Value); |
||||
} |
||||
|
||||
foreach (PlayoutItemProcessModel processModel in result.RightToSeq()) |
||||
{ |
||||
// for process counter
|
||||
var ffmpegProcess = new FFmpegProcess(); |
||||
|
||||
Command process = processModel.Process; |
||||
|
||||
logger.LogDebug("ffmpeg arguments {FFmpegArguments}", process.Arguments); |
||||
|
||||
var cts = new CancellationTokenSource(); |
||||
HttpContext.Response.OnCompleted(async () => |
||||
{ |
||||
ffmpegProcess.Dispose(); |
||||
await cts.CancelAsync(); |
||||
cts.Dispose(); |
||||
}); |
||||
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( |
||||
cts.Token, |
||||
HttpContext.RequestAborted); |
||||
|
||||
var pipe = new Pipe(); |
||||
var stdErrBuffer = new StringBuilder(); |
||||
|
||||
Command processWithPipe = process; |
||||
foreach (GraphicsEngineContext graphicsEngineContext in processModel.GraphicsEngineContext) |
||||
{ |
||||
var gePipe = new Pipe(); |
||||
processWithPipe = process.WithStandardInputPipe(PipeSource.FromStream(gePipe.Reader.AsStream())); |
||||
|
||||
// fire and forget graphics engine task
|
||||
_ = graphicsEngine.Run( |
||||
graphicsEngineContext, |
||||
gePipe.Writer, |
||||
linkedCts.Token); |
||||
} |
||||
|
||||
CommandTask<CommandResult> task = processWithPipe |
||||
.WithStandardOutputPipe(PipeTarget.ToStream(pipe.Writer.AsStream())) |
||||
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) |
||||
.WithValidation(CommandResultValidation.None) |
||||
.ExecuteAsync(linkedCts.Token); |
||||
|
||||
// ensure pipe writer is completed when ffmpeg exits
|
||||
_ = task.Task.ContinueWith( |
||||
(_, state) => ((PipeWriter)state!).Complete(), |
||||
pipe.Writer, |
||||
TaskScheduler.Default); |
||||
|
||||
string contentType = mode switch |
||||
{ |
||||
StreamingMode.HttpLiveStreamingSegmenterV2 => "video/x-matroska", |
||||
_ => "video/mp2t" |
||||
}; |
||||
|
||||
return new FileStreamResult(pipe.Reader.AsStream(), contentType); |
||||
} |
||||
|
||||
// this will never happen
|
||||
return new NotFoundResult(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue