Project & time
The project store, its document, and microsecond timing.
A project holds everything: the composition document, undo history, and the playhead and selection. Create one per open video.
import { createProject } from "@miraiclip/core";
const project = createProject({ width: 1920, height: 1080, fps: 30 });createProject(init, options?) | |
|---|---|
init | Settings { width, height, fps, name? } for a new project, or a saved document from toJSON() |
options.historyLimit | Undo steps kept (default 500) |
State
const state = project.getState();
state.doc; // the composition: saved, undoable
state.playheadUs; // ephemeral
state.selection; // ephemeral, clip ids| Part | Changed by | Undoable | Saved by toJSON() |
|---|---|---|---|
doc | Commands only | Yes | Yes |
playheadUs | project.setPlayhead(us), the player | No | No |
selection | project.setSelection(ids) | No | No |
Never mutate state you read. Dispatch a command instead.
The document
const { settings, assets, tracks, trackOrder, clips, transitions } = project.getState().doc;| Field | Shape |
|---|---|
settings | { width, height, fps, frameRate?, name? } |
assets | Record<id, Asset>: Assets |
tracks | Record<id, Track>: Tracks |
trackOrder | Track ids, bottom layer first |
clips | Record<id, Clip>: Clips |
transitions | Record<id, Transition>: Transitions |
Subscribing
subscribe(selector, listener) calls the listener only when the selected value changes. It returns an unsubscribe function.
const stop = project.subscribe(
(s) => s.doc.clips,
(clips, previous) => console.log("clips changed"),
);
stop();Wiring this into React, Vue or Svelte: Reading state.
Time is in microseconds
Every position and duration is a whole number of microseconds (µs): 1 second = 1,000,000 µs.
import { secondsToUs, usToSeconds, US_PER_SECOND } from "@miraiclip/core";
secondsToUs(2.5); // 2_500_000
usToSeconds(1_500_000); // 1.5
5 * US_PER_SECOND; // 5_000_000Integer µs keep the math exact: no rounding drift, even over an hour at 29.97 fps.
Frames
A frame rarely lasts a whole number of µs (33,333.3… at 30 fps). One rule maps between them everywhere:
| Helper | Returns |
|---|---|
frameToUs(n, rate) | Start of frame n: the first whole µs inside it |
usToFrame(us, rate) | The frame that contains us |
snapToFrame(us, rate) | The nearest frame start |
frameCount(us, rate) | Frames in a duration |
frameDurationUs(rate) | Length of one frame (fractional) |
usToTimecode(us, rate, { dropFrame? }) | "HH:MM:SS:FF", or "HH:MM:SS;FF" with drop-frame |
import { frameToUs, snapToFrame, usToFrame, usToTimecode } from "@miraiclip/core";
frameToUs(1, 30); // 33_334
usToFrame(33_334, 30); // 1
snapToFrame(50_000, 30); // 33_334
usToTimecode(3_600_000_000, 30); // "01:00:00:00"Snap user input (drags, typed times) with snapToFrame so edits land on frame boundaries.
NTSC rates
fps accepts decimals. 29.97, 23.976 and 59.94 are stored as exact ratios:
import { projectFrameRate } from "@miraiclip/core";
project.dispatch({ type: "project/set-settings", payload: { fps: 29.97 } });
project.getState().doc.settings.frameRate; // { num: 30000, den: 1001 }
projectFrameRate(project.getState().doc.settings); // { num: 30000, den: 1001 }Pass projectFrameRate(settings) to the frame helpers: they accept a number or a { num, den } ratio.
Save and load
import { createProject } from "@miraiclip/core";
const saved = project.toJSON(); // plain JSON: store it anywhere
const restored = createProject(saved); // history starts emptytoJSON() returns the document only. The playhead, selection and undo history aren't saved.