Skip to content
Quickstart

Quickstart

Miraiclip is pre-1.0 — expect API changes between minor versions. @miraiclip/core and @miraiclip/renderer are published on npm.

Installation

npm install @miraiclip/core
# for in-browser playback and preview, add the renderer:
npm install @miraiclip/renderer

The core is headless (browser + Node); the renderer is browser-only (WebCodecs, WebGL, Web Audio).

Create a project

The project state is a Zustand store — the single source of truth for your composition.

import { createProject } from "@miraiclip/core";

const project = createProject({ width: 1920, height: 1080, fps: 30 });

Add media, tracks, and clips

Everything is a command — deterministic, undoable, serializable.

// Register a media asset (metadata only; core does no decoding)
project.dispatch({
  type: "asset/add",
  payload: { id: "intro", kind: "video", src: "/media/intro.mp4", durationUs: 12_000_000 },
});

project.dispatch({ type: "track/add", payload: { id: "video-1", kind: "video" } });

project.dispatch({
  type: "clip/add",
  payload: {
    kind: "video",
    id: "clip-1",
    trackId: "video-1",
    assetId: "intro",
    startUs: 0,            // timeline position in microseconds
    durationUs: 5_000_000, // 5 seconds
  },
});

Transactions and time travel

Batch commands into one undoable history entry:

project.transaction(() => {
  project.dispatch({
    type: "clip/split",
    payload: { clipId: "clip-1", atUs: 2_000_000, newClipId: "clip-1b" },
  });
  project.dispatch({ type: "clip/move", payload: { clipId: "clip-1", startUs: 1_000_000 } });
});

project.undo();
project.redo();

React to changes

State changes are emitted as granular patches — the substrate for sync and collaboration:

project.events.on("patches", ({ patches, inverse, source }) => {
  console.log(patches);
  // e.g. [{ op: "replace", path: "/clips/clip-1/startUs", value: 1000000 }]
});

Read state and move the playhead

// The composition document lives under state.doc
const { tracks, clips, trackOrder } = project.getState().doc;

// Playhead and selection are ephemeral: not undoable, not serialized
project.setPlayhead(1_500_000);
project.subscribe(
  (s) => s.playheadUs,
  (playheadUs) => console.log("playhead moved", playheadUs),
);

Save and load

const saved = project.toJSON();
const restored = createProject(saved);

Play it back

Everything above is headless. To turn the project into pixels and sound, hand it to the renderer’s createPlayer — one facade wiring the decode pipeline, compositor, and audio engine to a canvas. See the Rendering guide for the full setup:

import { createPlayer } from "@miraiclip/renderer";

const player = createPlayer(project, {/* backend, demuxer, decoder, audio */});
player.play();