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
renderMedia() opens bundle server and queries Composition metadata via selectComposition()
renderFrames() opens Chrome pages equal to concurrency count and distributes frames
On each page: window.remotion_setFrame(n) → React re-render → waitForDelayRender() → screenshot()
Wait for screenshot until delayRender array is empty (= all async complete)
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