Skip to main content

@twick/browser-render

Browser-native video rendering for Twick using WebCodecs API.

Installation​

npm install @twick/browser-render

Usage​

Basic Example​

import { renderTwickVideoInBrowser } from '@twick/browser-render';

const videoBlob = await renderTwickVideoInBrowser({
variables: {
input: {
properties: { width: 1920, height: 1080, fps: 30 },
tracks: [
// Your tracks configuration
]
}
},
settings: {
width: 1920,
height: 1080,
fps: 30,
onProgress: (progress) => console.log(`${(progress * 100).toFixed(0)}%`)
}
});

// Download the video
const url = URL.createObjectURL(videoBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'video.mp4';
a.click();

React Hook​

import { useBrowserRenderer } from '@twick/browser-render';

function VideoRenderer() {
const { render, progress, isRendering, videoBlob, download, error, reset } =
useBrowserRenderer({
width: 720,
height: 1280,
fps: 30,
includeAudio: true, // enable audio rendering + FFmpeg mux
autoDownload: true, // auto-download final MP4
});

const handleRender = async () => {
await render({
input: {
properties: { width: 720, height: 1280, fps: 30 },
tracks: [/* ... */],
},
});
};

return (
<div>
<button onClick={handleRender} disabled={isRendering}>
Render Video
</button>
{isRendering && <progress value={progress} max={1} />}
{videoBlob && <button onClick={download}>Download</button>}
{error && (
<div>
<p>{error.message}</p>
<button onClick={reset}>Clear error</button>
</div>
)}
</div>
);
}

Configuration​

BrowserRenderConfig​

{
projectFile?: Project; // Optional custom project (defaults to @twick/visualizer)
variables: {
input: any; // Required: project input data
playerId?: string;
[key: string]: any;
};
settings?: {
width?: number; // Video width (default: 1920)
height?: number; // Video height (default: 1080)
fps?: number; // Frames per second (default: 30)
quality?: 'low' | 'medium' | 'high';
range?: [number, number]; // [start, end] in seconds
includeAudio?: boolean; // Enable audio rendering and muxing (default: false)
downloadAudioSeparately?: boolean; // Also download audio.wav when muxing fails
onAudioReady?: (audioBlob: Blob) => void; // Callback when raw audio is ready
onProgress?: (progress: number) => void;
onComplete?: (videoBlob: Blob) => void;
onError?: (error: Error) => void;
};
}

Quality levels​

The settings.quality flag controls the internal render resolution (supersampling), not the final video width/height:

  • 'low' (or omitted): resolutionScale = 1 (base project resolution)
  • 'medium': resolutionScale ≈ 1.5 (higher internal resolution, sharper output)
  • 'high': resolutionScale = 2 (2× internal resolution for maximum sharpness)

Higher quality levels produce noticeably sharper text and images at the same output size, but increase render time and memory usage.

Custom Project​

import myProject from './my-project';

const videoBlob = await renderTwickVideoInBrowser({
projectFile: myProject, // Must be an imported Project object
variables: { input: {...} }
});

Note: String paths only work in Node.js. In the browser, you must import and pass the Project object directly.

No extra scripts required​

CRA, Vite, and Next.js: Install @twick/browser-render and use it. No postinstall, copy scripts, or patches are required.

This package uses @twick/ffmpeg-web (a wrapper around @ffmpeg/ffmpeg) so that dynamic imports work correctly with webpack, Next.js, CRA, and Vite—no consumer-level patches needed.

  • Video encoding: mp4-wasm is loaded from your app’s public paths first, then from CDN (jsdelivr) if not found.
  • Audio muxing: FFmpeg core is loaded via @twick/ffmpeg-web from same-origin paths (e.g. /ffmpeg/) if present, otherwise from CDN. Works with webpack (CRA, Next.js) and Vite out of the box.

Optional: offline or custom assets​

To avoid CDN and serve everything from your app, copy assets into your public directory:

node node_modules/@twick/browser-render/scripts/copy-public-assets.js

Or add to package.json:

{
"scripts": {
"prestart": "node node_modules/@twick/browser-render/scripts/copy-public-assets.js",
"prebuild": "node node_modules/@twick/browser-render/scripts/copy-public-assets.js",
"start": "react-scripts start",
"build": "react-scripts build"
}
}

The script copies FFmpeg core files (from @twick/ffmpeg-web), mp4-wasm.wasm, and audio-worker.js to your public/ directory. If assets cannot be loaded (e.g. offline), the renderer falls back to video-only; for Vite you can use twickBrowserRenderPlugin() (see @twick/browser-render/vite-plugin-ffmpeg).

Manual setup (same-origin only)​

If you prefer to serve FFmpeg and WASM from your app only (no CDN), ensure these are available:

  • FFmpeg: /ffmpeg/ffmpeg-core.js and /ffmpeg/ffmpeg-core.wasm (e.g. via the copy script or Vite plugin above).
  • mp4-wasm: mp4-wasm.wasm in your public/ root (or path your app serves it from).

The muxer loads FFmpeg from ${window.location.origin}/ffmpeg when present. If FFmpeg cannot be loaded, the renderer returns a video-only file and (optionally) downloads audio.wav separately when downloadAudioSeparately is true. For Vite, use twickBrowserRenderPlugin() to copy these files automatically; see @twick/browser-render/vite-plugin-ffmpeg.

Limitations​

  • Audio: Audio rendering and FFmpeg-based muxing run entirely in the browser and are still considered experimental. If FFmpeg assets are not available, only video will be muxed and audio may be downloaded as a separate file.
  • Browser Support: Requires WebCodecs API (Chrome 94+, Edge 94+)

License​

This package is licensed under the Sustainable Use License (SUL) Version 1.0.

  • Free for use in commercial and non-commercial apps
  • Can be modified and self-hosted
  • Cannot be sold, rebranded, or distributed as a standalone SDK

For commercial licensing inquiries, contact: contact@kifferai.com

For full license terms, see the main LICENSE.md file in the project root.