I was curious about remotion’s video rendering technology, which allows for programmable video creation using a headless browser in the same way, so I looked into it.
Prepare
requestAnimationFrame
requestAnimationFrame is a web standard API. By passing a callback as an argument, the callback is executed when the browser is ready to redraw.
If redrawing is performed in a loop process without using requestAnimationFrame, there is a possibility that redrawing will be performed at a timing when it is not possible to draw. In other words, there is a possibility of iteration of the redrawing process not being displayed on the screen.
Also, if multiple requestAnimationFrame callbacks are enqueued in the same frame, they will be executed in the same frame. The following is from mozilla docs. timestamp refers to the end time of the previous drawing frame The timestamp refers to the end time of the previous drawing frame.
When multiple callbacks queued by requestAnimationFrame() begin to fire in a single frame, each receives the same timestamp even though time has passed during the computation of every previous callback’s workload.
How remotion renders
In the following, you will read about how to render videos for remotion based on the implementation of remotion v4.0.81.
Sequence diagram
The following is a sequence diagram of rendering by remotion. 1
The following are characteristic points
- Whether the headless browser is ready to render a frame or not is determined using
requestAnimationFrame. - The API for taking screenshots does not exist in JavaScript 2, so the CDP (Chrome DevTools Protocol) Page.captureScreenshot
- To execute
Page.captureScreenshotwhen the browser is ready to render, determine the execution timing as follows- On a headless browser running React, set the global variable (
remotion_renderReady) to have a flag indicating whether or not the browser is ready to render. - The waitForReady function will send
remotion_renderReadyto the renderReady. To allow the React side to execute acontinueRendercallback withrequestAnimationFrame, the polling loop should be executed byrequestAnimationFrame.
- On a headless browser running React, set the global variable (
- To execute
sequenceDiagram
participant renderer
participant puppeteer
participant browser
participant remotion_renderReady
participant react
loop screenshot a frame
Note over renderer,react: set the next frame
renderer->>puppeteer: set the next frame
puppeteer->>browser: remotion_setFrame
(Runtime.callFunctionOn)
browser->>remotion_renderReady: delayRender
remotion_renderReady->>remotion_renderReady: set false
remotion_renderReady->>browser: done
browser->>react: set frame context
browser->>remotion_renderReady: continueRender
with requestAnimationFrame
browser->>puppeteer: done
puppeteer->>renderer: done
par rerendering the next frame
react->>react: update frame context and
rerender user-defined components
remotion_renderReady->>remotion_renderReady: set true
with renderable timing
end
Note over renderer,react: wait ready state
renderer->>puppeteer: wait ready
puppeteer->>browser: onRaF
(Runtime.callFunctionOn)
loop until remotion_renderReady being true
browser->>remotion_renderReady: check value
with requestAnimationFrame
remotion_renderReady->>browser:
end
browser->>puppeteer: done
puppeteer->>renderer: ready
Note over renderer,react: take screenshot
renderer->>puppeteer: take screenshot
puppeteer->>browser: Page.captureScreenshot(CDP)
browser->>puppeteer: screenshot
end
Summary
remotion is a very practical framework that enables accurate frame-by-frame video rendering by using requestAnimationFrame and React’s Context.
Although not covered in this article, it also provides a mechanism for embedding video and audio so that they will play correctly on the rendered video.
Prior to v2 Puppetter was in dependencies, but since v3, it has been inlined. In this article, the inlined version is referred to as “puppeteer. ↩︎
There is an API called getDisplayMedia, which returns a Stream. If this API is used, it is necessary to stitch together the frames at the timing when they are finally ready to be drawn, so it is simpler to implement taking screenshots at the timing when they are ready to be drawn. ↩︎
