From 1bf5b9567b0a4dbc240601a30d8ad3247bd6f61e Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:56:48 +0000 Subject: [PATCH] use graphics engine with segmenter v2 (#2294) --- .../Streaming/HlsSessionWorkerV2.cs | 2 +- ErsatzTV/Controllers/InternalController.cs | 67 +++++++++++++------ ErsatzTV/Pages/ChannelEditor.razor | 2 +- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs b/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs index b857d3ce..2f2a9747 100644 --- a/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs +++ b/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs @@ -24,7 +24,7 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker private readonly IMediator _mediator; private readonly string _scheme; private readonly SemaphoreSlim _slim = new(1, 1); - private readonly object _sync = new(); + private readonly Lock _sync = new(); private readonly Option _targetFramerate; private CancellationTokenSource _cancellationTokenSource; private string _channelNumber; diff --git a/ErsatzTV/Controllers/InternalController.cs b/ErsatzTV/Controllers/InternalController.cs index f4130a2b..68824a11 100644 --- a/ErsatzTV/Controllers/InternalController.cs +++ b/ErsatzTV/Controllers/InternalController.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.IO.Pipelines; +using System.Text; using CliWrap; using ErsatzTV.Application.Emby; using ErsatzTV.Application.Jellyfin; @@ -9,6 +11,7 @@ using ErsatzTV.Application.Subtitles.Queries; using ErsatzTV.Core; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg; +using ErsatzTV.Core.Interfaces.Streaming; using ErsatzTV.Extensions; using Flurl; using MediatR; @@ -21,15 +24,18 @@ namespace ErsatzTV.Controllers; public class InternalController : ControllerBase { private readonly IFFmpegSegmenterService _ffmpegSegmenterService; + private readonly IGraphicsEngine _graphicsEngine; private readonly ILogger _logger; private readonly IMediator _mediator; public InternalController( IFFmpegSegmenterService ffmpegSegmenterService, + IGraphicsEngine graphicsEngine, IMediator mediator, ILogger logger) { _ffmpegSegmenterService = ffmpegSegmenterService; + _graphicsEngine = graphicsEngine; _mediator = mediator; _logger = logger; } @@ -298,38 +304,55 @@ public class InternalController : ControllerBase foreach (PlayoutItemProcessModel processModel in result.RightToSeq()) { - Command command = processModel.Process; + // for process counter + var ffmpegProcess = new FFmpegProcess(); - _logger.LogDebug("ffmpeg arguments {FFmpegArguments}", command.Arguments); + Command process = processModel.Process; - var process = new FFmpegProcess - { - StartInfo = new ProcessStartInfo + _logger.LogDebug("ffmpeg arguments {FFmpegArguments}", process.Arguments); + + var cts = new CancellationTokenSource(); + HttpContext.Response.OnCompleted( + async () => { - FileName = command.TargetFilePath, - Arguments = command.Arguments, - RedirectStandardOutput = true, - RedirectStandardError = false, - UseShellExecute = false, - CreateNoWindow = true - } - }; + ffmpegProcess.Dispose(); + await cts.CancelAsync(); + cts.Dispose(); + }); - HttpContext.Response.RegisterForDispose(process); + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( + cts.Token, + HttpContext.RequestAborted); - foreach ((string key, string value) in command.EnvironmentVariables) + var pipe = new Pipe(); + var stdErrBuffer = new StringBuilder(); + + var processWithPipe = process; + foreach (var graphicsEngineContext in processModel.GraphicsEngineContext) { - process.StartInfo.Environment[key] = value; + 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); } - var contentType = "video/mp2t"; - if (mode.Equals("hls-direct", StringComparison.OrdinalIgnoreCase)) + _ = processWithPipe + .WithStandardOutputPipe(PipeTarget.ToStream(pipe.Writer.AsStream())) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.None) + .ExecuteAsync(linkedCts.Token); + + var contentType = mode switch { - contentType = "video/mp4"; - } + "segmenter-v2" => "video/x-matroska", + _ => "video/mp2t" + }; - process.Start(); - return new FileStreamResult(process.StandardOutput.BaseStream, contentType); + return new FileStreamResult(pipe.Reader.AsStream(), contentType); } // this will never happen diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index 4655b9bb..01ccb8b9 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -94,7 +94,7 @@ MPEG-TS - @* MPEG-TS (Legacy) *@ + MPEG-TS (Legacy) HLS Direct HLS Segmenter HLS Segmenter V2