diff --git a/services/playout/src/playout-manager.js b/services/playout/src/playout-manager.js index 35f1f2f..32c4eda 100644 --- a/services/playout/src/playout-manager.js +++ b/services/playout/src/playout-manager.js @@ -169,16 +169,23 @@ export class PlayoutManager { // reloads forever ("sliding 0.00 / prev-sn na / MISSED") and never appends // a fragment, so the preview stays black. // - // Fix: force a constant output frame rate (-r 30000/1001 = 29.97 CFR). This - // regenerates uniform PTS and — critically — makes the frame-based GOP land - // on regular time boundaries: at 29.97 fps a 60-frame GOP (-g 60) is exactly - // 2.0s, so every keyframe (and therefore every HLS split at -hls_time 2) is - // a clean 2.0s boundary. The original used -g 60 WITHOUT -r, so "60 frames" - // varied with the channel's irregular feed rate and segments drifted. + // The HLS preview is a VIDEO-ONLY confidence monitor. We deliberately drop + // audio (-an). // - // NOTE: -force_key_frames does NOT work here — CasparCG's FFMPEG consumer - // routes args to the muxer, not the encoder, and logs it as "Unused option". - // The CFR rate + frame GOP is the combination that actually takes effect. + // Why: CasparCG's real-time channel feeds the FFMPEG consumer an audio + // stream whose muxed time_base comes out as 1/0 (infinity). ffmpeg itself + // can't decode the resulting playlist ("Invalid data ... abuffer: Value inf + // for parameter 'time_base'"), and hls.js silently fails to append the + // fragment after demux — the video element sits at readyState 0 and the + // preview stays black. Dropping audio removes the broken stream entirely; + // the remaining video track is clean h264 and plays in hls.js. A confidence + // monitor doesn't need audio — the real program audio rides the primary + // SRT/RTMP/SDI/NDI output, which is unaffected. + // + // NOTE: encoder options like -g / -r / -force_key_frames are NOT honored + // here — CasparCG's FFMPEG consumer applies args to the muxer, not the + // encoder (it logs "Unused option"). Segment cadence follows the channel's + // own keyframes; that's fine for a video-only preview. const out = `${HLS_DIR}/index.m3u8`; const args = [ `FILE "${out}"`, @@ -187,8 +194,7 @@ export class PlayoutManager { '-hls_list_size 8', '-hls_flags delete_segments+append_list+independent_segments', '-codec:v libx264 -preset:v veryfast -tune:v zerolatency -b:v 800k -maxrate 1M -bufsize 2M', - '-r 30000/1001 -g 60 -keyint_min 60 -sc_threshold 0', - '-codec:a aac -b:a 96k -ar 48000', + '-an', '-filter:v format=yuv420p', ].join(' '); await this.amcp.send(`ADD ${CHANNEL} ${args}`);