Skip to content

OffthreadVideo is pathologically slow / hangs on sources with sparse keyframes #5

Description

@thecodacus

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

  1. Take any 60-fps screen recording (OBS, QuickTime, ffmpeg with defaults). These typically have keyframes every 250 frames (~4s).
  2. Wrap it in <OffthreadVideo src={...} /> and render a composition that plays it.
  3. 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:

  1. 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.

  2. 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.)

  3. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions