Quickstart
Create a project, edit it with commands, and export an MP4.
1. Create a project
import { createProject } from "@miraiclip/core";
const project = createProject({ width: 1920, height: 1080, fps: 30 });2. Edit with commands
project.dispatch({
type: "asset/add",
payload: { id: "intro", kind: "video", src: "/intro.mp4", durationUs: 12_000_000 },
});
project.dispatch({ type: "track/add", payload: { id: "v1", kind: "video" } });
project.dispatch({
type: "clip/add",
payload: { kind: "video", id: "c1", trackId: "v1", assetId: "intro", startUs: 0, durationUs: 5_000_000 },
});
project.undo();
project.redo();3. Preview it
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>mountPlayer is a small helper. It's the same in every framework:
import type { Project } from "@miraiclip/core";
import {
createPixiBackend,
createPlayer,
createWebAudioOutput,
createWebCodecsDecoderFactory,
isWebCodecsSupported,
openMediabunnyAudio,
openMediabunnyDemuxer,
type Player,
} from "@miraiclip/renderer";
export async function mountPlayer(canvas: HTMLCanvasElement, project: Project): Promise<Player> {
if (!isWebCodecsSupported()) throw new Error("This browser has no WebCodecs");
const { width, height } = project.getState().doc.settings;
const dpr = Math.min(devicePixelRatio, 2); // sharp on hi-DPI, capped for speed
return createPlayer(project, {
backend: await createPixiBackend({ canvas, width, height }),
openDemuxer: openMediabunnyDemuxer,
createDecoder: createWebCodecsDecoderFactory({ maxOutputDimensionPx: 1920 }),
audioOutput: createWebAudioOutput(),
openAudio: openMediabunnyAudio,
outputSize: { width: canvas.clientWidth * dpr, height: canvas.clientHeight * dpr },
});
}4. Export
import { exportProject } from "@miraiclip/renderer";
const mp4 = await exportProject(project, { format: "mp4", quality: "high" });Export runs on the user's machine: no render servers and no per-render cost. For a button with progress and cancel, see Export UI.