Miraiclip SDK
Build an editor

Preview & playback

Mount the player on a canvas and control playback.

The player turns the project into pixels and sound on a <canvas>. Create it once per canvas and destroy it when the view goes away.

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>

One helper, every framework

mountPlayer holds the renderer setup and has no framework code in it. See it in the Quickstart.

Player API

MemberWhat it does
play() / pause()Start and stop playback
seek(us)Jump to a time in µs
setRate(rate)Playback speed
playing, timeUs, durationUsRead-only transport state
getClipBounds(clipId, timeUs?)Where a clip is drawn, in composition pixels
hitTest(x, y, options?)Topmost clip at a point: use it for click-to-select
destroy()Release decoders, audio and the GPU context

StrictMode mounts effects twice in development. The cancelled flag destroys the player from the first mount, so only one player stays alive.

On this page