Getting started
@vidstudio/engine is a video editing engine for the browser. It keeps a project (tracks, clips, effects, captions) and plays, renders and exports it on the user's device. @vidstudio/editor is the user interface for that engine: one React component, or one mount function for pages without React.
Three names are used on every page. "The editor" is the component. "The store" is where projects live. "The backend" is the engine object behind one project.
Install
npm install @vidstudio/editor
The engine comes with the editor. react and react-dom 18 or later are peer dependencies. Install @vidstudio/engine alone when you drive the backend without the user interface; see Driving the engine.
Mount in React
Create the store once, create or pick a project, and give the editor a parent with a height.
import { useEffect, useState } from "react";
import { VidstudioEditor, createStore } from "@vidstudio/editor";
import "@vidstudio/editor/editor.css";
type Store = Awaited<ReturnType<typeof createStore>>;
// Defined once: a new options object rebuilds the editor.
const OPTIONS = { namespace: "my-app" };
export function Editor() {
const [opened, setOpened] = useState<{ store: Store; projectId: string } | null>(null);
useEffect(() => {
let cancelled = false;
(async () => {
const store = await createStore({ namespace: "my-app" });
const project = await store.projects.create("My project", { frame_rate: 30 });
if (!cancelled) setOpened({ store, projectId: project.id });
})();
return () => { cancelled = true; };
}, []);
if (!opened) return <p>Opening</p>;
return (
<div style={{ height: "100vh" }}>
<VidstudioEditor store={opened.store} projectId={opened.projectId} options={OPTIONS} />
</div>
);
}
The editor fills its parent. Keep the options object stable, or the editor rebuilds on every render.
Mount without React
mountEditor renders the editor into any element with a height.
import { createStore, mountEditor, lightTheme } from "@vidstudio/editor";
import "@vidstudio/editor/editor.css";
const store = await createStore({ namespace: "my-app" });
const project = await store.projects.create("My project", { frame_rate: 30 });
const editor = mountEditor(document.getElementById("editor")!, { store, projectId: project.id });
editor.update({ theme: lightTheme });
// Later, when the page is done with it:
editor.unmount();
update replaces only the props you pass. unmount disposes the backend and empties the element.
Next.js
The engine touches browser globals when it loads, so load it on the client only. No bundler setting is needed, with webpack or with Turbopack.
"use client";
import { useEffect, useRef } from "react";
import type { MountedEditor } from "@vidstudio/editor";
import "@vidstudio/editor/editor.css";
export function Editor() {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
let editor: MountedEditor | null = null;
let cancelled = false;
(async () => {
const { createStore, mountEditor } = await import("@vidstudio/editor");
const store = await createStore({ namespace: "my-app" });
const project = await store.projects.create("My project", { frame_rate: 30 });
if (!cancelled) editor = mountEditor(element.current!, { store, projectId: project.id });
})();
return () => { cancelled = true; editor?.unmount(); };
}, []);
return <div ref={element} style={{ height: "100vh" }} />;
}
Vite
Set the worker format to ES modules. The engine's workers load their code in chunks, and Vite's default worker format cannot emit chunks.
import { defineConfig } from "vite";
export default defineConfig({
worker: { format: "es" },
});
The dev server needs no optimizeDeps setting. The workers load from node_modules/@vidstudio/engine/dist/workers/, checked with version 0.1.0 on Vite 5.4.21.
Two folders to serve
| Folder | What it holds | Command |
|---|---|---|
/fonts/ |
The twelve text fonts | cp -r node_modules/@vidstudio/engine/assets/fonts public/fonts |
/models/ |
The speech models for captions, about 450 MB, plus 19 MB for background removal | npx vidstudio-fetch-models public/models |
Serve them elsewhere and pass the addresses, each ending in a slash: options={{ assets: { fonts: "https://cdn.example.com/fonts/", models: "https://cdn.example.com/models/" } }}. With features={{ captions: false }} no model is ever downloaded. Details, sizes and flags are in Media, fonts and models.
Cross-origin isolation is optional
The editor runs without the headers Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. With them, captions transcribe on several threads, about twice as fast. Import, preview and export are the same either way. The headers also restrict which files your page can load from other origins; see Media, fonts and models.
Licence
Without a licence key the editor works in full on localhost, with a small "vidstudio.app" mark on the preview and on every exported frame. Any other public domain requires a licence key for that domain, which also removes the mark; see Limits and support.
Next pages
- Projects and storage: the store, namespaces, where data lives, quotas, two editors on one page.
- Media, fonts and models: what imports, proxies, web addresses, fonts and models.
- Save and load: saving a project with its media to your server and loading it back.
- Theme, features and events: theme keys, feature flags, shortcuts, events.
- Driving the engine: the backend without the user interface.
- Limits and support: browsers, sizes, the licence, help.
- Troubleshooting: symptoms and fixes.
- The reference: every type and method.