Introduction
Build video editors, not video editor plumbing.
Miraiclip SDK is a framework-agnostic engine for video editors that run in the browser. You bring the UI. It brings the timeline, the commands, the renderer and the export.
What you get
| Commands, not mutations | Every edit is a validated, serializable, undoable command. |
| Undo, redo, transactions | Exact history out of the box. |
| Frame-accurate preview | WebCodecs + WebGL: the preview shows the same pixels as the export. |
| Export anywhere | MP4 or WebM in the browser, in a worker, or from Node. No render servers. |
| Creative toolkit | Keyframes, 79 effects, transitions, karaoke captions, HTML clips. |
| Templates | One composition becomes many personalized videos. |
| AI-native | LLM tools from the command catalog, an editing assistant, an MCP server. |
| Any framework | Headless core. Use it with React, Vue, Svelte, vanilla JS or Node. |
Pick your framework
The docs follow the framework picked in the sidebar. Most of the API is the same everywhere. Only the code that binds Miraiclip to your UI changes, and those snippets switch with your choice:
Mount a preview
import { project } from "../shared/project";
import { mountPlayer } from "./mount-player";
const canvas = document.querySelector<HTMLCanvasElement>("#preview")!;
const player = await mountPlayer(canvas, project);
document.querySelector("#play")!.addEventListener("click", () => player.play());
document.querySelector("#pause")!.addEventListener("click", () => player.pause());
// When the view goes away:
// player.destroy();import { useEffect, useRef } from "react";
import type { Project } from "@miraiclip/core";
import type { Player } from "@miraiclip/renderer";
import { mountPlayer } from "./mount-player";
export function Preview({ project }: { project: Project }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const playerRef = useRef<Player | null>(null);
useEffect(() => {
let cancelled = false;
mountPlayer(canvasRef.current!, project).then((player) => {
if (cancelled) player.destroy();
else playerRef.current = player;
});
return () => {
cancelled = true;
playerRef.current?.destroy();
playerRef.current = null;
};
}, [project]);
return (
<>
<canvas ref={canvasRef} style={{ width: 640, height: 360 }} />
<button onClick={() => playerRef.current?.play()}>Play</button>
<button onClick={() => playerRef.current?.pause()}>Pause</button>
</>
);
}<script setup lang="ts">
import { onBeforeUnmount, onMounted, useTemplateRef } from "vue";
import type { Project } from "@miraiclip/core";
import type { Player } from "@miraiclip/renderer";
import { mountPlayer } from "./mount-player";
const props = defineProps<{ project: Project }>();
const canvas = useTemplateRef<HTMLCanvasElement>("canvas");
let player: Player | undefined;
let unmounted = false;
onMounted(async () => {
const p = await mountPlayer(canvas.value!, props.project);
if (unmounted) p.destroy();
else player = p;
});
onBeforeUnmount(() => {
unmounted = true;
player?.destroy();
});
</script>
<template>
<canvas ref="canvas" style="width: 640px; height: 360px" />
<button @click="player?.play()">Play</button>
<button @click="player?.pause()">Pause</button>
</template><script lang="ts">
import type { Project } from "@miraiclip/core";
import type { Player } from "@miraiclip/renderer";
import { mountPlayer } from "./mount-player";
let { project }: { project: Project } = $props();
let canvas = $state<HTMLCanvasElement>();
let player: Player | undefined;
$effect(() => {
if (!canvas) return;
let cancelled = false;
mountPlayer(canvas, project).then((p) => {
if (cancelled) p.destroy();
else player = p;
});
return () => {
cancelled = true;
player?.destroy();
player = undefined;
};
});
</script>
<canvas bind:this={canvas} style="width: 640px; height: 360px"></canvas>
<button onclick={() => player?.play()}>Play</button>
<button onclick={() => player?.pause()}>Pause</button>