fix(playout): clean CFR HLS preview so hls.js can sync
Root cause of the black preview: CasparCG's real-time channel feeds the
HLS consumer frames with irregular timestamps (the "packet with pts X has
duration 0" warnings). With frame-count GOPs (-g 60) the muxer split
points drift, producing erratic segment durations (0.4s-4.2s) that exceed
the declared TARGETDURATION. hls.js parses the resulting live playlist but
can never establish a fragment timeline — it reloads forever
("sliding 0.00 / prev-sn na / MISSED") and never appends a fragment, so
the video element stays at readyState 0 (black). Verified live via the
browser: manifest + segments serve 200, segment is valid h264/aac with a
keyframe start, yet hls.js logs zero FRAG_LOADED.
Fix: force a constant output frame rate (-r 30000/1001, regenerates
uniform PTS) and time-based keyframes every 2s (-force_key_frames
expr:gte(t,n_forced*2)), so every segment is a clean keyframe-aligned 2.0s
chunk. Yields a spec-compliant playlist (TARGETDURATION 2, stable
8-segment/16s window) identical in shape to the capture/VOD HLS the rest
of the app already plays successfully through the same hls.js.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d778aa4cdb
commit
f28799317d
1 changed files with 20 additions and 11 deletions
|
|
@ -159,24 +159,33 @@ export class PlayoutManager {
|
|||
}
|
||||
|
||||
// Low-bitrate HLS for the web UI preview. Segments land in the shared media
|
||||
// volume; the mam-api serves /live/<channel_id>/* from there.
|
||||
// volume; the mam-api serves /media/live/<channel_id>/* from there.
|
||||
async _addHlsConsumer() {
|
||||
// mkdir is done by the entrypoint; CasparCG's ffmpeg consumer creates the
|
||||
// playlist on first segment. 2s segments / 6-window list keeps lag low
|
||||
// without thrashing disk.
|
||||
// FILE keyword (alias of the FFMPEG consumer) writing a segmented HLS
|
||||
// playlist. Same arg rules as the STREAM consumer: -param:stream form and a
|
||||
// format=yuv420p filter ahead of libx264 (channel output is RGBA).
|
||||
// The CasparCG channel feeds this consumer in real time, and its frame
|
||||
// timestamps are irregular ("packet with pts X has duration 0" warnings).
|
||||
// With frame-count GOPs (-g 60) the HLS muxer split points drift, producing
|
||||
// erratic segment durations (0.4s–4.2s) and TARGETDURATION violations. The
|
||||
// result is a live playlist hls.js parses but can never sync to — it
|
||||
// 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, regenerates uniform PTS) and
|
||||
// TIME-based keyframes every 2s (-force_key_frames) so every segment is a
|
||||
// clean, keyframe-aligned 2.0s chunk. This yields a spec-compliant playlist
|
||||
// (TARGETDURATION 2, stable 8-segment / 16s window) identical in shape to
|
||||
// the capture/VOD HLS the rest of the app already plays.
|
||||
const out = `${HLS_DIR}/index.m3u8`;
|
||||
const args = [
|
||||
`FILE "${out}"`,
|
||||
'-format hls',
|
||||
'-hls_time 2',
|
||||
'-hls_list_size 6',
|
||||
'-hls_flags delete_segments+append_list',
|
||||
'-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',
|
||||
'-g 60 -keyint_min 60 -sc_threshold 0',
|
||||
'-codec:a aac -b:a 96k',
|
||||
// 29.97 CFR confidence feed: -r forces constant frame rate (fixes the
|
||||
// duration-0 PTS), -force_key_frames pins keyframes to 2s media boundaries.
|
||||
'-r 30000/1001 -force_key_frames expr:gte(t,n_forced*2) -sc_threshold 0',
|
||||
'-codec:a aac -b:a 96k -ar 48000',
|
||||
'-filter:v format=yuv420p',
|
||||
].join(' ');
|
||||
await this.amcp.send(`ADD ${CHANNEL} ${args}`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue