feat(record): fragment the MP4 so a crash cannot destroy the whole take

TASK 32·4. A plain MP4 keeps its index (moov) at the end, so a power cut or a killed
process left the entire recording unreadable even though every video byte was on disk.
The record block now carries
`-movflags +frag_keyframe+empty_moov+default_base_moof`: index first, a playable
fragment flushed at every keyframe, so an interrupted take plays to the last fragment.

Record block only — the FLV stream output has no index and never gets the flags.
Rename-on-stop is untouched.

Test: `Args_RecordBlock_IsFragmentedMp4_SoACrashKeepsTheFile` asserts the flags on the
record block and their absence from the streaming block.

Not yet proven on a real interrupted take (kill ffmpeg mid-recording, play the result);
the args test is by construction.

Docs: task-32 slice 4 ticked with that caveat, TASKS row, output.md record note, HANDOFF.
verify.sh: 550 passed (mouse-injection test excluded, desktop in use), 0 warnings.
This commit is contained in:
2026-11-05 08:58:48 -08:00
parent a814bcd502
commit e37215d9c4
5 changed files with 50 additions and 4 deletions
+4 -1
View File
@@ -249,7 +249,10 @@ devices, no timers).
(Before this wiring the seams defaulted to unity and the controls were decorative.) `StopStream` →
`StopLive()` (closes the pipe → ffmpeg audio EOF) **BEFORE** stopping the frame pump (video EOF) —
the reverse order stalls on pipe backpressure. `FfmpegEncoder` itself is untouched; the VM owns the
pipe lifecycle.
pipe lifecycle. **Recordings are fragmented MP4 (TASK 32·4, 2026-11-05):** the record block carries
`-movflags +frag_keyframe+empty_moov+default_base_moof`, so the index is written first and a playable
fragment is flushed at every keyframe. A plain MP4 puts its index at the end, which is why a power cut
or a killed process previously left the whole take unreadable. The FLV stream block never gets it.
**Two clip-audio rings join that mix (2026-11-05):** `EnqueueAlertAudio` (TASK 47) is pre-scaled by the
alert layer and mixed at **unity, never ducked** — an alert must be heard over the mic.
`EnqueueDesktopMediaAudio` (TASK 21's audio slice) is the opposite on purpose: a video layer's