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

OptionTypeDefaultDescription
fpsnumber30Frames per second
codec"avc" | "hevc" | "vp9" | "av1""avc"Output codec
bitratenumberTarget bitrate in bits/s
scalenumber1Render scale factor (0.5 = half resolution)
fromMs / toMsnumberRender a sub-range of the composition
onProgress(p: RenderProgress) => voidPer-frame progress callback
progressPreviewIntervalnumber0Emit framePreviewCanvas every N frames (0 = off)
signalAbortSignalCancellation signal
includeAudiobooleantrueMix 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.