Rendering
Render a composition to an MP4 file directly in the browser.
Call renderToVideo on the root ef-timegroup element to encode the composition to MP4 entirely in the browser using WebCodecs — no server required.
Basic usage
const timegroup = document.getElementById("root");
const buffer = await timegroup.renderToVideo({ fps: 30, codec: "avc" });
const blob = new Blob([buffer], { type: "video/mp4" });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement("a"), { href: url, download: "output.mp4" });
a.click();
Progress
Pass onProgress to receive updates on each captured frame:
const buffer = await timegroup.renderToVideo({
fps: 30,
onProgress: ({ progress, currentFrame, totalFrames, estimatedRemainingMs }) => {
progressBar.style.width = `${progress * 100}%`;
console.log(`frame ${currentFrame}/${totalFrames}, ETA ${estimatedRemainingMs}ms`);
},
});
progress is a 0–1 fraction. estimatedRemainingMs is a rolling estimate based on observed frame rate.
Live frame preview
Set progressPreviewInterval to receive a framePreviewCanvas every N frames:
const buffer = await timegroup.renderToVideo({
fps: 30,
progressPreviewInterval: 15,
onProgress: ({ progress, framePreviewCanvas }) => {
progressBar.style.width = `${progress * 100}%`;
if (framePreviewCanvas) previewContainer.replaceChildren(framePreviewCanvas);
},
});
Cancellation
Pass an AbortSignal to stop mid-render:
const controller = new AbortController();
cancelButton.onclick = () => controller.abort();
const buffer = await timegroup.renderToVideo({
fps: 30,
signal: controller.signal,
onProgress: ({ progress }) => { progressBar.style.width = `${progress * 100}%`; },
});
Options
| Option | Type | Default | Description |
|---|---|---|---|
fps | number | 30 | Frames per second |
codec | "avc" | "hevc" | "vp9" | "av1" | "avc" | Output codec |
bitrate | number | — | Target bitrate in bits/s |
scale | number | 1 | Render scale factor (0.5 = half resolution) |
fromMs / toMs | number | — | Render a sub-range of the composition |
onProgress | (p: RenderProgress) => void | — | Per-frame progress callback |
progressPreviewInterval | number | 0 | Emit framePreviewCanvas every N frames (0 = off) |
signal | AbortSignal | — | Cancellation signal |
includeAudio | boolean | true | Mix audio tracks into the output |
React
Get a ref to the root timegroup element and call renderToVideo on it:
import { useRef } from "react";
import { TimelineRoot } from "@editframe/react";
import type { EFTimegroup } from "@editframe/elements";
function App() {
const tgRef = useRef<EFTimegroup>(null);
async function handleRender() {
const buffer = await tgRef.current!.renderToVideo({
fps: 30,
onProgress: ({ progress }) => console.log(`${(progress * 100).toFixed(1)}%`),
});
// download buffer …
}
return (
<>
<TimelineRoot id="root" component={MyComposition} />
<button onClick={handleRender}>Render</button>
</>
);
}
TimelineRoot ensures createRenderClone() works correctly when renderToVideo forks the composition for parallel encoding.