Miraiclip SDK

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 mutationsEvery edit is a validated, serializable, undoable command.
Undo, redo, transactionsExact history out of the box.
Frame-accurate previewWebCodecs + WebGL: the preview shows the same pixels as the export.
Export anywhereMP4 or WebM in the browser, in a worker, or from Node. No render servers.
Creative toolkitKeyframes, 79 effects, transitions, karaoke captions, HTML clips.
TemplatesOne composition becomes many personalized videos.
AI-nativeLLM tools from the command catalog, an editing assistant, an MCP server.
Any frameworkHeadless 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
main.ts
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();
Preview.tsx
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>
    </>
  );
}
Preview.vue
<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>
Preview.svelte
<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>

Next steps

On this page