Summary
OffthreadVideo performs a per-frame ffmpeg seek+decode. When the source video has a large GOP (sparse keyframes — common in screen recordings, OBS captures, and re-encodes that didn't override default x264 settings), each seek has to decode from the previous keyframe forward, which is slow and can hang the render entirely on some sources.
Reproduction
- Take any 60-fps screen recording (OBS, QuickTime, ffmpeg with defaults). These typically have keyframes every 250 frames (~4s).
- Wrap it in
<OffthreadVideo src={...} /> and render a composition that plays it.
- Render hangs or crawls at ~1 frame/sec when the shot is on-screen. Sometimes hangs indefinitely.
In our project, an 1140-frame <OffthreadVideo> over ubatch_glm.mp4 (20s @ 60fps, ~7 keyframes total) stalled the MyVideo render at the exact frame where that video started playing. Re-encoding the source with -g 30 -keyint_min 30 (keyframe every 0.5s) — going from 7 → 41 keyframes — fully resolved it.
Repro one-liner for the fix
ffmpeg -i input.mp4 -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p \
-g 30 -keyint_min 30 -movflags +faststart output.mp4
Expected / suggested behavior
Any one of the following would prevent users from hitting this:
-
Probe + warn at first use. When OffthreadVideo loads a source, count keyframes vs total frames. If keyframes / total_frames < 1 / fps (i.e. less than 1 keyframe per second of content), log a warning:
OffthreadVideo: \"video.mp4\" has only N keyframes in M frames (1 every X seconds). Per-frame seek cost will be high. Re-encode with -g <fps> -keyint_min <fps> for fast scrub.
-
Internal re-mux on first use. Auto-generate a sidecar *.optimized.mp4 with -g 30 and use that for rendering. Cache it next to the source. (Remotion does effectively this by extracting frames to a temp directory.)
-
Frame cache. Extract all source frames upfront to a temp directory on the first render, then read frames from the cache. Eliminates seek cost entirely after the first pass.
(3) is the cleanest but most invasive. (1) is a one-screen probe at media-load time and prevents the surprise.
Impact
This is the kind of bug where the framework looks broken but the source is at fault — and there's no diagnostic to point users at the source. The render just stalls.
Environment
- `@rendiv/core`: ^0.1.0
- `@rendiv/cli`: ^0.1.0
- ffmpeg: 7.0.2-static
- OS: Debian inside Docker container (headless)
Summary
OffthreadVideoperforms a per-frame ffmpeg seek+decode. When the source video has a large GOP (sparse keyframes — common in screen recordings, OBS captures, and re-encodes that didn't override default x264 settings), each seek has to decode from the previous keyframe forward, which is slow and can hang the render entirely on some sources.Reproduction
<OffthreadVideo src={...} />and render a composition that plays it.In our project, an 1140-frame
<OffthreadVideo>overubatch_glm.mp4(20s @ 60fps, ~7 keyframes total) stalled theMyVideorender at the exact frame where that video started playing. Re-encoding the source with-g 30 -keyint_min 30(keyframe every 0.5s) — going from 7 → 41 keyframes — fully resolved it.Repro one-liner for the fix
ffmpeg -i input.mp4 -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p \ -g 30 -keyint_min 30 -movflags +faststart output.mp4Expected / suggested behavior
Any one of the following would prevent users from hitting this:
Probe + warn at first use. When
OffthreadVideoloads a source, count keyframes vs total frames. Ifkeyframes / total_frames < 1 / fps(i.e. less than 1 keyframe per second of content), log a warning:Internal re-mux on first use. Auto-generate a sidecar
*.optimized.mp4with-g 30and use that for rendering. Cache it next to the source. (Remotion does effectively this by extracting frames to a temp directory.)Frame cache. Extract all source frames upfront to a temp directory on the first render, then read frames from the cache. Eliminates seek cost entirely after the first pass.
(3) is the cleanest but most invasive. (1) is a one-screen probe at media-load time and prevents the surprise.
Impact
This is the kind of bug where the framework looks broken but the source is at fault — and there's no diagnostic to point users at the source. The render just stalls.
Environment