Repository navigation
snapshot can capture a <video> inside a vfx capture canvas before its seeked frame decodes (shows frame 0; rate rises with load) #4762
Description
Summary
hyperframes snapshot can capture a <video> that sits inside a vfx capture canvas (canvas[layoutsubtree].hf-vfx-src) before its seeked frame has decoded, so the snapshot shows the video's first frame instead of the frame at --at. It is a race whose rate rises with machine load: frozen in 10 of 10 snapshots at load 80+, and 4 of 10 at load 40–50. With the capture canvas replaced by a plain div, 0 of 12 snapshots froze at any load. hyperframes render shows the correct frame.
Observed on hyperframes 0.8.83, macOS (arm64), hyperframes snapshot . --no-browser-gpu --at 5 --no-end.
Minimal reproduction
Footage (synthetic): a white bar moving across black at 160 px/s. At t = 5 s the bar spans columns 800–1099, and at frame 0 it spans 0–299.
mkdir -p assets
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:r=24:d=10" -f lavfi -i "color=c=white:s=300x1080:r=24:d=10" \
-filter_complex "[0][1]overlay=x='t*160':y=0:shortest=1,format=yuv420p" -c:v libx264 -profile:v main -g 30 assets/clip.mp4index.html (video inside a capture canvas feeding a one-node Noise data-vfx-chain):
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>h264 in vfx capture canvas</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>html,body{margin:0;padding:0;background:#000}</style></head>
<body>
<div id="root" data-composition-id="main" data-width="1920" data-height="1080" data-duration="10" data-fps="24" style="position:relative;overflow:hidden;width:1920px;height:1080px;background:#000">
<canvas id="bg-vfxbd" layoutsubtree="" class="hf-vfx-src" data-vfx-for="bg" width="1920" height="1080" style="position:absolute;left:0;top:0;width:1920px;height:1080px"><div id="bg-vfxin" class="hf-vfx-in" style="position:absolute;left:0;top:0;width:1920px;height:1080px"><video id="vid" data-start="0" data-duration="10" data-track-index="16" src="assets/clip.mp4" muted="" style="position:absolute;left:0;top:0;width:1920px;height:1080px"></video></div></canvas>
<div id="bg" class="clip" data-start="0" data-duration="10" data-track-index="15" data-vfx-chain="{"version":1,"nodes":[{"type":"noise","id":"n2","params":{"useColorNoise":true,"clipping":true}}]}" style="position:absolute;left:0;top:0;width:1920px;height:1080px;--vfx-n2-amount:0.2"></div>
</div>
<script>window.__timelines = window.__timelines || {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>
</body></html>Control: replace the <canvas …>…</canvas> element with a plain wrapper, keeping the video:
<div style="position:absolute;inset:0"><div id="bg-vfxin" class="hf-vfx-in" style="position:absolute;left:0;top:0;width:1920px;height:1080px"><video id="vid" data-start="0" data-duration="10" data-track-index="16" src="assets/clip.mp4" muted="" style="position:absolute;left:0;top:0;width:1920px;height:1080px"></video></div></div>Steps: run hyperframes snapshot . --no-browser-gpu --at 5 --no-end 10 times while the machine is busy (e.g. two other renders running in parallel). For each frame, check where the bright columns are: 800–1099 is correct (t = 5 s), 0–299 is frozen at frame 0.
Results
| page | frozen at frame 0 | machine load |
|---|---|---|
| video inside capture canvas | 8 of 8 | 110–140 |
canvas replaced by a div |
0 of 6 | 80–115 |
On a larger page with the same structure: H.264 inside the canvas froze 4 of 10 at load 40–50, and VP9 (-g 12) inside the canvas froze 0 of 9.
On an idle machine it may not reproduce in a handful of runs, so please run it under load.
Expected
The captured video frame is the one at --at, as in hyperframes render and as with the canvas removed.
Activity
Dante-dan commentedon Sep 30, 2026
I would like to investigate and fix the snapshot-specific video/VFX race. My intended approach is to trace the ordering between the snapshot seek, video-frame readiness, and refreshing the VFX capture canvas, then make capture wait for the requested frame before that canvas is sampled. I would keep the change scoped to this ordering and avoid adding a fixed sleep.
For verification I would use the moving-bar reproduction in this issue, including the capture-canvas and plain-div control under load, and check the expected columns at
--at 5. I would also check that the render path still produces the requested frame. I have read the reproduction but have not yet reproduced it locally, so the exact fix still needs confirmation from that investigation.Please assign this to me if this scope fits.