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.
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>One helper, every framework
mountPlayer holds the renderer setup and has no framework code in it. See it in the Quickstart.
Player API
| Member | What it does |
|---|---|
play() / pause() | Start and stop playback |
seek(us) | Jump to a time in µs |
setRate(rate) | Playback speed |
playing, timeUs, durationUs | Read-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.