Summary
On macOS, the experimental fast-capture path (drawElementImage) fails at frame 0 with drawElement canvas not initialized and the render aborts. The docs for --experimental-fast-capture say incompatible compositions and self-verification failures fall back to screenshot capture automatically, but this error is rethrown instead of triggering the fallback. Passing --experimental-fast-capture=false renders fine.
Reproduced on 0.7.109 and 0.8.4. I diffed dist/cli.js between 0.8.4 and 0.8.8: the capture functions involved (captureFramesBatchPipelined, captureFrameCore, captureFrameToBufferPipelined) are byte-identical, so 0.8.8 has the same behavior.
Environment
- hyperframes 0.8.4 (also 0.7.109), installed globally via bun
- macOS 26.6.1, Apple M4 Max
- Node v26.7.0
- Chrome: hyperframes-managed
chrome-headless-shell mac_arm-152.0.7928.2
- ffmpeg 9.0.1 (Homebrew)
Minimal reproduction
index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
html, body { margin: 0; padding: 0; }
#composition { width: 640px; height: 360px; background: #1b2a3a; position: relative; overflow: hidden; }
h1 { color: #f5efe2; font-family: sans-serif; position: absolute; top: 150px; left: 0; right: 0; text-align: center; }
</style>
</head>
<body>
<div id="composition" class="clip" data-duration="2" data-width="640" data-height="360" data-fps="30">
<h1>audio fix test</h1>
<audio src="assets/tone.m4a" data-start="0" data-duration="2" data-media-start="0" data-track-index="10" data-volume="1"></audio>
</div>
</body>
</html>
assets/tone.m4a is any 2s audio file, e.g.:
ffmpeg -f lavfi -i "sine=frequency=440:duration=2" -c:a aac assets/tone.m4a
Run:
hyperframes render . --fps 30 --gpu --output rendered/test.mp4
Same result without --gpu.
Actual output
[INFO] [Render:trace] {"phase":"capture_streaming","status":"start","stagePhase":"capturing","workerCount":1,"totalFrames":60,"framesCompleted":0,"captureMode":"drawelement","captureOperation":"drawElement"}
[engine] fast capture: batch produce failed at frame 0 (drawElement canvas not initialized); re-capturing 4 frame(s) per-frame
[INFO] [Render:trace] {"phase":"capture_streaming","status":"error","message":"drawElement canvas not initialized","captureMode":"drawelement"}
██████░░░░░░░░░░░░░░░░░░░ 25% Failed: drawElement canvas not initialized
[INFO] [Render] Failure summary {"failedStage":"Starting frame capture","error":"drawElement canvas not initialized","browserDiagnostics":{"errors":0,"pageErrors":0,"consoleErrors":0}}
✗ Render failed
drawElement canvas not initialized
Try --docker for containerized rendering
Also noted right before capture: [WARN] [Render] Reduced auto worker count for high-cost capture workload ... "reasons":["calibration-failed"], which may be the same underlying failure showing up earlier.
Expected
Either the drawElement capture works, or the error triggers the documented automatic fallback to screenshot capture (as isNoCachedPaintRecordError already does).
Workaround
hyperframes render . --fps 30 --gpu --experimental-fast-capture=false --output rendered/test.mp4
Renders successfully (captureMode: beginframe), output has h264 video and aac audio.
Where it goes wrong (from reading dist/cli.js)
In captureFrameCore, the catch around captureDrawElementFrame(...) only handles isNoCachedPaintRecordError(err) by falling back to pageScreenshotCapture; every other error hits else { throw err; }. The batch path logs "re-capturing N frame(s) per-frame", but the per-frame retry calls captureDrawElementFrame again, so it fails the same way and the render dies. Treating "canvas not initialized" like the no-cached-paint-record case (fall back to screenshot) would fix the hard failure.
Summary
On macOS, the experimental fast-capture path (
drawElementImage) fails at frame 0 withdrawElement canvas not initializedand the render aborts. The docs for--experimental-fast-capturesay incompatible compositions and self-verification failures fall back to screenshot capture automatically, but this error is rethrown instead of triggering the fallback. Passing--experimental-fast-capture=falserenders fine.Reproduced on 0.7.109 and 0.8.4. I diffed
dist/cli.jsbetween 0.8.4 and 0.8.8: the capture functions involved (captureFramesBatchPipelined,captureFrameCore,captureFrameToBufferPipelined) are byte-identical, so 0.8.8 has the same behavior.Environment
chrome-headless-shellmac_arm-152.0.7928.2Minimal reproduction
index.html:assets/tone.m4ais any 2s audio file, e.g.:ffmpeg -f lavfi -i "sine=frequency=440:duration=2" -c:a aac assets/tone.m4aRun:
hyperframes render . --fps 30 --gpu --output rendered/test.mp4Same result without
--gpu.Actual output
Also noted right before capture:
[WARN] [Render] Reduced auto worker count for high-cost capture workload ... "reasons":["calibration-failed"], which may be the same underlying failure showing up earlier.Expected
Either the drawElement capture works, or the error triggers the documented automatic fallback to screenshot capture (as
isNoCachedPaintRecordErroralready does).Workaround
hyperframes render . --fps 30 --gpu --experimental-fast-capture=false --output rendered/test.mp4Renders successfully (
captureMode: beginframe), output has h264 video and aac audio.Where it goes wrong (from reading dist/cli.js)
In
captureFrameCore, thecatcharoundcaptureDrawElementFrame(...)only handlesisNoCachedPaintRecordError(err)by falling back topageScreenshotCapture; every other error hitselse { throw err; }. The batch path logs "re-capturing N frame(s) per-frame", but the per-frame retry callscaptureDrawElementFrameagain, so it fails the same way and the render dies. Treating "canvas not initialized" like the no-cached-paint-record case (fall back to screenshot) would fix the hard failure.