From 59551f28a516f9716b6fe69b5666df77fd75a1bd Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Sun, 31 May 2026 15:22:21 -0400 Subject: [PATCH] fix(playout): re-encode HLS preview to uniform 2s segments so hls.js syncs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The audio-strip fix made the stream decode cleanly server-side, but the browser monitor was still black: with -c:v copy the re-mux passed through CasparCG's erratic real-time keyframes (segments 0.6-2.8s) and irregular PTS. hls.js can't build a live timeline from that — it logs "sliding 0.00 / prev-sn na / MISSED" and never loads a fragment (verified live in-browser: readyState stays 0). A standalone ffmpeg honours -force_key_frames, so re-encode to libx264 with a forced 2s GOP + CFR fps=30, scaled to 720p for a light confidence monitor. Every HLS segment is now a clean, keyframe-aligned 2.0s chunk hls.js can sync to. Co-Authored-By: Claude Opus 4.8 --- services/playout/src/playout-manager.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/services/playout/src/playout-manager.js b/services/playout/src/playout-manager.js index bef98e4..989c767 100644 --- a/services/playout/src/playout-manager.js +++ b/services/playout/src/playout-manager.js @@ -221,9 +221,22 @@ export class PlayoutManager { '-fflags', '+genpts', '-analyzeduration', '2000000', '-probesize', '2000000', '-i', `${PREVIEW_UDP_URL}?fifo_size=1000000&overrun_nonfatal=1`, - // Drop the (broken) audio entirely; copy the clean video bitstream. + // Drop the (broken) audio entirely. '-an', - '-c:v', 'copy', + // Re-encode (NOT -c:v copy) to uniform, keyframe-aligned 2s segments with + // regenerated CFR timestamps. -c:v copy passed CasparCG's erratic + // real-time keyframes straight through, producing segments of 0.6–2.8s + // and irregular PTS; hls.js can't build a live timeline from that — it + // logs "sliding 0.00 / MISSED", never loads a fragment, and the monitor + // stays black even though the stream decodes cleanly server-side. A + // standalone ffmpeg honours -force_key_frames, so every GOP (and thus + // every HLS segment) is exactly 2.0s. fps=30 forces CFR; scale to 720p + // keeps the confidence monitor cheap. + '-vf', 'fps=30,scale=-2:720,format=yuv420p', + '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', + '-b:v', '1500k', '-maxrate', '2M', '-bufsize', '4M', + '-g', '60', '-keyint_min', '60', '-sc_threshold', '0', + '-force_key_frames', 'expr:gte(t,n_forced*2)', '-f', 'hls', '-hls_time', '2', '-hls_list_size', '8',