Summary
After bumping @hyperframes/producer from 0.6.7 → 0.6.10 in a downstream repo, one of three regression fixtures (style-13) started failing deterministically with Streaming encode failed: FFmpeg exited with code 255. The other two (style-7, style-16) pass with the same bump.
Reproduced across two consecutive CI runs with different runner conditions; not a flake. Looks like it was introduced in 0.6.8 via #832 (perf(engine): faster shader transitions via page-side WebGL compositing), which bundled three coupled changes:
- Unconditional
data-hf-auto-start sentinel injection in packages/core/src/compiler/timingCompiler.ts (every <video>/<audio> without data-start)
- New
discoverVideoVisibilityFromTimeline() in packages/producer/src/services/htmlCompiler.ts that overwrites video.start/video.end with opacity-derived windows
- New
enablePageSideCompositing (default true) bypassing the layered shader-blend path
HF_PAGE_SIDE_COMPOSITING=false does not fix it — the auto-start injection in timingCompiler.ts is unconditional.
Failure mode
{"event":"test_error","suite":"style-13-prod","error":"Streaming encode failed: FFmpeg exited with code 255\nffmpeg stderr (tail):\nvideo:13530kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.020131%\n[libx264 @ ...] frame I:3 Avg QP:12.91 size: 73263\n[libx264 @ ...] frame P:431 Avg QP:14.72 size: 31633\n...\n[libx264 @ ...] kb/s:7661.05\nExiting normally, received signal 15."}
Key signals:
- libx264 prints its full end-of-encode stats (frame I:3, P:431, kb/s:7661) → encoder finished encoding every frame
Exiting normally, received signal 15 → SIGTERM (not an internal crash)
audio:0kB is expected for this stage — streamingEncoder.ts is video-only; audio comes via assembleStage → muxVideoWithAudio later
- Exit code 255 (not 143) — matches
streamingEncoder.ts:421-425 safety timeout firing during the post-frame flush
So FFmpeg encoded all 16s / ~480 frames cleanly, then was SIGTERM'd in the flush/teardown window by the 600s ffmpegStreamingTimeout safety timer in streamingEncoder.ts. The wrapper sees non-zero exit and reports "Streaming encode failed".
Why style-13 specifically
Comparing run telemetry:
- style-13:
staticDuration:16, width:1080, height:1920, audioCount:1, videoCount:1 — no Google Fonts fetch
- style-16:
staticDuration:13.88, width:1080, height:1920, audioCount:1, videoCount:1 — fetches Impact
- style-7:
staticDuration:16.7, width:1920, height:1080, audioCount:1, videoCount:1 — 4 font families
Frame-capture calibration p95Ms was actually higher on style-16 (6094ms, multiplier 8) than style-13 (1708-2399ms, multiplier 2.85-4) and style-16 passed — so the regression isn't simple slowness. Something in style-13's composition shape interacts with one of the three changes in #832.
Strongest suspect: <video> element with no explicit data-start → auto-tagged with data-hf-auto-start → discoverVideoVisibilityFromTimeline() overwrites video.start/video.end with opacity-binary-searched window. If that window is large or the binary-search adds enough wall-clock time to the probe + render pipeline, the 600s ffmpegStreamingTimeout fires during flush.
Downstream impact
heygen-com/hyperframes-internal PR #328 wants to bump from 0.6.7 → 0.6.10 specifically to pick up the lottieReadiness + import.meta.env fixes from #861 (so it can drop two local patches). All three of 0.6.8 / 0.6.9 / 0.6.10 include the regression, and 0.6.7 is missing #861 — so there's no version that gives us both.
What would unblock us
Any of:
- Make the auto-injected sentinel +
discoverVideoVisibilityFromTimeline() opt-in (config flag or env var, default off). Existing fixtures with explicit data-start are unaffected.
- Make
discoverVideoVisibilityFromTimeline() non-destructive: only override video.start/video.end when the original values came from auto-injection AND the discovered window is strictly larger than ~1 frame, AND falls inside [0, duration].
- Diagnose and fix the actual cause; ship as 0.6.11.
Happy to send a PR for (1) or (2) if useful — we have a downstream test that flips green/red on this. Just wanted to file the analysis first since the root cause inside the producer pipeline isn't fully pinned down from the outside.
Repro environment
- Linux CI runner (Dockerfile.test in
heygen-com/hyperframes-internal), in-process render mode
@hyperframes/producer@0.6.10 + downstream @app/producer-internal
- Composition: 1080×1920, 16s, 1 video element, 1 audio element, 30fps target
- Output: SDR mp4, streaming-encode path enabled (default)
cc anyone touching #832 / discoverVideoVisibilityFromTimeline.
Summary
After bumping
@hyperframes/producerfrom0.6.7→0.6.10in a downstream repo, one of three regression fixtures (style-13) started failing deterministically withStreaming encode failed: FFmpeg exited with code 255. The other two (style-7, style-16) pass with the same bump.Reproduced across two consecutive CI runs with different runner conditions; not a flake. Looks like it was introduced in 0.6.8 via #832 (perf(engine): faster shader transitions via page-side WebGL compositing), which bundled three coupled changes:
data-hf-auto-startsentinel injection inpackages/core/src/compiler/timingCompiler.ts(every<video>/<audio>withoutdata-start)discoverVideoVisibilityFromTimeline()inpackages/producer/src/services/htmlCompiler.tsthat overwritesvideo.start/video.endwith opacity-derived windowsenablePageSideCompositing(defaulttrue) bypassing the layered shader-blend pathHF_PAGE_SIDE_COMPOSITING=falsedoes not fix it — the auto-start injection intimingCompiler.tsis unconditional.Failure mode
Key signals:
Exiting normally, received signal 15→ SIGTERM (not an internal crash)audio:0kBis expected for this stage —streamingEncoder.tsis video-only; audio comes viaassembleStage→muxVideoWithAudiolaterstreamingEncoder.ts:421-425safety timeout firing during the post-frame flushSo FFmpeg encoded all 16s / ~480 frames cleanly, then was SIGTERM'd in the flush/teardown window by the 600s
ffmpegStreamingTimeoutsafety timer instreamingEncoder.ts. The wrapper sees non-zero exit and reports "Streaming encode failed".Why style-13 specifically
Comparing run telemetry:
staticDuration:16, width:1080, height:1920, audioCount:1, videoCount:1— no Google Fonts fetchstaticDuration:13.88, width:1080, height:1920, audioCount:1, videoCount:1— fetches ImpactstaticDuration:16.7, width:1920, height:1080, audioCount:1, videoCount:1— 4 font familiesFrame-capture calibration
p95Mswas actually higher on style-16 (6094ms, multiplier 8) than style-13 (1708-2399ms, multiplier 2.85-4) and style-16 passed — so the regression isn't simple slowness. Something in style-13's composition shape interacts with one of the three changes in #832.Strongest suspect:
<video>element with no explicitdata-start→ auto-tagged withdata-hf-auto-start→discoverVideoVisibilityFromTimeline()overwritesvideo.start/video.endwith opacity-binary-searched window. If that window is large or the binary-search adds enough wall-clock time to the probe + render pipeline, the 600sffmpegStreamingTimeoutfires during flush.Downstream impact
heygen-com/hyperframes-internalPR #328 wants to bump from0.6.7→0.6.10specifically to pick up the lottieReadiness +import.meta.envfixes from #861 (so it can drop two local patches). All three of 0.6.8 / 0.6.9 / 0.6.10 include the regression, and 0.6.7 is missing #861 — so there's no version that gives us both.What would unblock us
Any of:
discoverVideoVisibilityFromTimeline()opt-in (config flag or env var, default off). Existing fixtures with explicitdata-startare unaffected.discoverVideoVisibilityFromTimeline()non-destructive: only overridevideo.start/video.endwhen the original values came from auto-injection AND the discovered window is strictly larger than ~1 frame, AND falls inside[0, duration].Happy to send a PR for (1) or (2) if useful — we have a downstream test that flips green/red on this. Just wanted to file the analysis first since the root cause inside the producer pipeline isn't fully pinned down from the outside.
Repro environment
heygen-com/hyperframes-internal), in-process render mode@hyperframes/producer@0.6.10+ downstream@app/producer-internalcc anyone touching #832 /
discoverVideoVisibilityFromTimeline.