Miraiclip SDK

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

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>

mountPlayer is a small helper. It's the same in every framework:

mount-player.ts
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.

On this page