Miraiclip SDK
Framework packages

React

Providers, hooks and unstyled components for building a Miraiclip editor in React.

@miraiclip/react puts a project and its preview into React context, gives you narrow hooks that re-render only when the slice they read changes, and ships unstyled components for the timeline, the preview, a seek bar, waveforms and on-canvas transform handles.

npm install @miraiclip/core @miraiclip/renderer @miraiclip/react

React 18 and 19 are supported. The renderer is browser-only, so in Next.js render the editor in a client component.

Setup

Wrap the editor in ProjectProvider, then PlaybackProvider if it has a preview. Every hook below reads from these.

App.tsx
import { PlaybackProvider, ProjectProvider } from "@miraiclip/react";
import { PreviewPanel } from "./PreviewPanel";
import { StatusBar } from "./StatusBar";
import { TimelineTrack } from "./TimelineTrack";

export function App() {
  return (
    <ProjectProvider init={{ width: 1920, height: 1080, fps: 30 }}>
      <PlaybackProvider volumeStorageKey="my-editor:volume">
        <PreviewPanel />
        <StatusBar />
        <TimelineTrack trackId="main" pxPerSecond={100} />
      </PlaybackProvider>
    </ProjectProvider>
  );
}
Also uses PreviewPanel.tsx, StatusBar.tsx, TimelineTrack.tsx
PreviewPanel.tsx
import { Preview, usePlayback } from "@miraiclip/react";

// Inside <ProjectProvider> and <PlaybackProvider>.
export function PreviewPanel() {
  const { isPlaying, toggle } = usePlayback();
  return (
    <>
      {/* Hands its canvas to <PlaybackProvider>, keeps it sized, releases it on unmount. */}
      <Preview width={640} height={360} />
      <button type="button" onClick={toggle}>
        {isPlaying ? "Pause" : "Play"}
      </button>
    </>
  );
}
StatusBar.tsx
import { useProjectState, useSelection, useUndoRedo } from "@miraiclip/react";

// Inside <ProjectProvider>. Each hook re-renders only when its slice changes.
export function StatusBar() {
  const playheadUs = useProjectState((s) => s.playheadUs);
  const selection = useSelection();
  const { canUndo, undo } = useUndoRedo();

  return (
    <div>
      {(playheadUs / 1e6).toFixed(2)}s · {selection.length} selected
      <button disabled={!canUndo} onClick={undo}>Undo</button>
    </div>
  );
}
TimelineTrack.tsx
import { Timeline, useTrack } from "@miraiclip/react";

// Inside <ProjectProvider> and <PlaybackProvider>.
// <Timeline.*> handles placement, select, drag (one clip/move on release), trim and snapping.
export function TimelineTrack({ trackId, pxPerSecond }: { trackId: string; pxPerSecond: number }) {
  const track = useTrack(trackId);
  if (!track) return null;

  return (
    <Timeline.Root pxPerSecond={pxPerSecond}>
      <Timeline.Viewport style={{ height: 64 }}>
        <Timeline.Track track={track} style={{ height: 48 }}>
          <Timeline.Lane>
            {(clip) => (
              // State arrives as data attributes: style .clip[data-selected], .clip[data-dragging].
              <Timeline.Clip clip={clip} className="clip" style={{ top: 4, bottom: 4 }}>
                {clip.id}
                <Timeline.TrimHandle side="start" style={{ width: 6 }} />
                <Timeline.TrimHandle side="end" style={{ width: 6 }} />
              </Timeline.Clip>
            )}
          </Timeline.Lane>
        </Timeline.Track>
      </Timeline.Viewport>
    </Timeline.Root>
  );
}
ProviderPropsGives
ProjectProviderinit (settings or a saved document) and optional seed, or project (one you created)The project, via useProject()
ProjectSessionProviderinitial: { init, seed? }A project you can replace at runtime: useProjectSession() returns loadDocument(doc) (Open) and newProject() (New)
PlaybackProvideroptions (renderer and player options), volumeStorageKeyTransport, preview and volume, via usePlayback()

seed runs once on a fresh project and is cleared from history, so users can't undo the demo content.

Reading state

HookReturnsRe-renders when
useProjectState(selector, isEqual?)Any slice of stateThe selected value changes
useSettings()Width, height, frame rate, nameSettings change
useTracks(), useTrack(id), useTrackOrder()Tracks in render orderTracks change
useClips(trackId), useClip(id), useClipCount(trackId)A track's clips in time orderThat track's clips change
useTransitions(trackId)A track's transitionsThose transitions change
useSelection(), useSelectedClip()Selected ids, or the one selected clipSelection changes
useDurationUs()End of the last clipA clip's placement changes
usePlayheadUs()Playhead in µsEvery played frame
usePlayheadRef(onChange?)A ref holding the playheadNever
useUndoRedo()canUndo, canRedo, undo, redoAvailability changes

The playhead updates every frame

usePlayheadUs re-renders 60 times a second during playback. Use it in small leaf components, or use usePlayheadRef and write to the DOM in onChange.

Keep selectors cheap and return stable values: useProjectState((s) => s.doc.clips[id]) is fine; building a new array on every call re-renders every time unless you pass shallowEqual as isEqual.

Editing

useCommands() returns typed builders for every built-in command, bound to the project:

import { useCommands, useSelectedClip } from "@miraiclip/react";

export function VolumeSlider() {
  const clip = useSelectedClip();
  const commands = useCommands();
  if (!clip || !("volume" in clip)) return null;
  return (
    <input
      type="range" min={0} max={1} step={0.01} value={clip.volume ?? 1}
      onChange={(e) => commands.setClipProperty(clip.id, { volume: Number(e.target.value) })}
    />
  );
}

A slider like this commits one history entry per change. To make a whole drag one undo step, use useGesture(): call gesture.update(apply) on every move and gesture.end() on release (see Sliders and undo).

useEditorShortcuts(actions?) binds Delete/Backspace, S (split at the playhead), and undo/redo on window, ignoring keys typed into fields. Pass the actions you want, for example useEditorShortcuts(["undo", "redo"]).

Preview and playback

Render the canvas with <Preview>; build controls from usePlayback().

usePlayback() returns:

MemberWhat it does
attachCanvas(canvas, size), resizePreview(w, h), detachCanvas()Hand the <canvas> to the renderer; size is its CSS size
isPlaying, play(), pause(), toggle(), seek(us)Transport. isPlaying follows the player's own events
mode"live", "fallback" (no renderer yet, or it failed: a clock still moves the playhead) or "unsupported" (no WebCodecs)
lastErrorThe last renderer or media error, as text
volume, muted, setVolume(v), toggleMute()Monitoring volume. Never written to the project or exports
getClipBounds(id), hitTest(x, y, filter?)Where clips are drawn, for selection overlays and click-to-select
controllerThe underlying preview controller

PlaybackProvider is safe under StrictMode: the double mount builds one renderer, not two on the same WebGL context.

Timeline hooks

HookUse
useVisibleRange(scrollRef, pxPerSecond, headerWidthPx?)The time range to render, with overscan; mount only those clips
useCompositorPlayhead(ref, toTransform, deps)Moves a playhead line without re-rendering: the browser's compositor animates it during playback
usePlayheadFollow(scrollRef, pxPerSecond, headerWidthPx?)Pages the timeline to follow playback; side and reveal() for an off-screen indicator
useTimelineZoom(scrollRef, options)Ctrl/⌘ + wheel zooms around the pointer

useTimelineZoom takes enabled: false to leave Ctrl/⌘ + wheel to the browser.

To build a timeline from these hooks instead of the components, see Build an editor › Timeline. The drag, snapping and range maths come from @miraiclip/editor, which works without React.

Components

Added in 0.2.0. Unstyled components that own an editor's interactions and expose their state as data attributes for your styles. Each has its own page with a live demo:

ComponentHooksPage
<Timeline.*>: Root, Viewport, Ruler, Track, TrackHeader, Lane, Clip, TrimHandle, Playhead, Cuts, PlayheadFollowuseTimeline, useTimelineClip, useTimelineDrag, useTimelineRange, useTimelineTrack, rulerStepUsTimeline
<Preview>usePreviewPreview
<Scrubber.*>: Root, Range, ThumbScrubber
<Waveform>useWaveformPeaksWaveform
<TransformBox.*>: Root, Outline, Guides, Body, Handle, RotateHandle, RotateLineuseTransformBoxTransform box

See Components › Overview for how styling works.

On this page