🔄

Remotion Code Analysis — renderMedia() and the Frame Rendering Loop

Source code analysis of the core rendering loop: screenshotting frames with Puppeteer and combining with FFmpeg

GitHub: remotion-dev/remotion/packages/renderer

After bundling completes, actual rendering begins. renderMedia() is the orchestrator.

The flow: renderMedia() calls selectComposition() to get metadata, renderFrames() to generate PNG sequences, and stitchFramesToVideo() for FFmpeg encoding.

In renderFrames(), multiple Chrome pages run in parallel. For each frame: window.remotion_setFrame(n) is injected, React re-renders, waitForDelayRender() checks if all async work is done, then page.screenshot() captures the frame.

delayRender/continueRender is a simple counter pattern — window.__REMOTION_DELAY_RENDER array must be empty before screenshots proceed.

How It Works

1

renderMedia() opens bundle server and queries Composition metadata via selectComposition()

2

renderFrames() opens Chrome pages equal to concurrency count and distributes frames

3

On each page: window.remotion_setFrame(n) → React re-render → waitForDelayRender() → screenshot()

4

Wait for screenshot until delayRender array is empty (= all async complete)

5

stitchFramesToVideo() encodes PNG sequence → MP4/WebM with FFmpeg + audio mux

Pros

  • remotion_setFrame → React re-render pattern: elegant design leveraging existing React ecosystem
  • delayRender as simple counter → safely handles complex async scenarios

Cons

  • Complex Chrome process management: page crashes, memory leaks and browser-specific issues

Use Cases

Debugging rendering bottlenecks: identifying which stage (bundle/frame/encoding) is slow Debugging delayRender timeouts: tracing why continueRender is not called