MotionScript

@motion-script/core


@motion-script/core / PlaybackController

Class: PlaybackController

Defined in: runtime/playback-controller.ts:76

Orchestrates playback of a compiled motion-script project.

On construction it runs the precomp pass (Precomp.run()) to build the global asset timeline and node lifespans, then wires the master clock to the audio device and render pipeline so every tick:

  1. Loads assets required at the current frame (AssetManager.loadAt).
  2. Evaluates scene state and lays out nodes (StateEvaluator).
  3. Renders the frame to the render context.
  4. Prefetches assets for upcoming frames.

External callers interact via play, pause, seek, seekWhilePlaying, screenshot, getTreeState, and getNodeState.

Constructors

Constructor

new PlaybackController(params): PlaybackController

Defined in: runtime/playback-controller.ts:156

Parameters

params

ControllerParams

Returns

PlaybackController

Properties

fps

readonly fps: number

Defined in: runtime/playback-controller.ts:109


precomp

precomp: PrecompResult

Defined in: runtime/playback-controller.ts:120

Latest assembled precomp result. Swapped wholesale on a scene replace.


viewport

readonly viewport: Size2D

Defined in: runtime/playback-controller.ts:110

Accessors

buildErrors

Get Signature

get buildErrors(): BuildError[]

Defined in: runtime/playback-controller.ts:138

Errors collected during the precomp pass (one entry per failing scene).

Returns

BuildError[]


currentFrame

Get Signature

get currentFrame(): number

Defined in: runtime/playback-controller.ts:291

Current playback position in frames (float).

Returns

number


currentTime

Get Signature

get currentTime(): number

Defined in: runtime/playback-controller.ts:286

Current playback position in seconds.

Returns

number


globalAudio

Get Signature

get globalAudio(): WaveformInfo[]

Defined in: runtime/playback-controller.ts:148

The project's audio beds as timeline clips, in absolute seconds — already bounded by the measured duration, so re-read them whenever it grows. Exposed for the timeline's global track rows; playback schedules the underlying requests itself.

Returns

WaveformInfo[]


isPlaying

Get Signature

get isPlaying(): boolean

Defined in: runtime/playback-controller.ts:281

Returns

boolean


totalDuration

Get Signature

get totalDuration(): number

Defined in: runtime/playback-controller.ts:133

Total playback duration in seconds.

Returns

number


totalFrames

Get Signature

get totalFrames(): number

Defined in: runtime/playback-controller.ts:128

Total frame count across all scenes.

Returns

number


tracks

Get Signature

get tracks(): number[]

Defined in: runtime/playback-controller.ts:123

Per-scene frame counts in timeline order, used to build the track list.

Returns

number[]

Methods

clearNodeOverrides()

clearNodeOverrides(path?): void

Defined in: runtime/playback-controller.ts:656

Drop one node's overrides, or all of them when path is omitted, and repaint so the node snaps back to its scene-authored state.

An override is written straight onto a live signal, so simply forgetting it would leave the last dragged value on screen. The value can only come back from the generator that authored it, which means replaying the current scene — so this rebuilds that scene's node tree (node ids change; keep keying on paths). Cheap enough for a pointer-up, which is the only place it belongs; never call it per pointermove.

Parameters

path?

string

Returns

void


dispose()

dispose(): void

Defined in: runtime/playback-controller.ts:707

Returns

void


getNodeBox()

getNodeBox(path): NodeBox | null

Defined in: runtime/playback-controller.ts:569

The on-screen box of one node at the current frame, or null when the path does not resolve (or no scene is active). See NodeBox.

Parameters

path

string

Structural path from TreeState.path.

Returns

NodeBox | null


getNodeBoxes()

getNodeBoxes(): NodeBox[]

Defined in: runtime/playback-controller.ts:581

Every visible node's box in draw order — for hover highlights, marquee selection, and snap guides. One tree walk instead of N getNodeBox calls.

Returns

NodeBox[]


getNodeState()

getNodeState(nodeId): NodeState | null

Defined in: runtime/playback-controller.ts:551

Parameters

nodeId

string

Returns

NodeState | null


getTextLayout()

getTextLayout(path): NodeTextLayout | null

Defined in: runtime/playback-controller.ts:599

Where a Text node's caret slots landed on screen, or null when the path doesn't resolve, the node isn't text, or the shape has no caret model (see MeasureScope.layoutTextBlock). See NodeTextLayout.

Measured on demand rather than carried with every frame: a host wants this only while a text edit is open, and only for the node being edited.

Parameters

path

string

Structural path from TreeState.path.

Returns

NodeTextLayout | null


getTreeState()

getTreeState(): TreeState | null

Defined in: runtime/playback-controller.ts:527

Return the full node tree for the current scene, with lifespan frame ranges and waveform data attached. Used by the timeline UI. Returns null when no scene is active.

Returns

TreeState | null


onPause()

onPause(cb): void

Defined in: runtime/playback-controller.ts:306

Register a callback that fires when playback pauses.

Parameters

cb

() => void

Returns

void


onPlay()

onPlay(cb): void

Defined in: runtime/playback-controller.ts:301

Register a callback that fires when playback starts.

Parameters

cb

() => void

Returns

void


onTime()

onTime(cb): void

Defined in: runtime/playback-controller.ts:296

Register a callback that fires on every clock tick with the current time.

Parameters

cb

TimeCallback

Returns

void


pause()

pause(): void

Defined in: runtime/playback-controller.ts:697

Returns

void


pickNode()

pickNode(point, tolerance?): NodeBox | null

Defined in: runtime/playback-controller.ts:612

The topmost node under a viewport-space point (origin at the viewport centre, y-up), or null. tolerance is grab-slop in scene units — pass the host's pixel slop divided by its preview zoom so the grab area stays constant on screen. See pickNode.

Parameters

point

Vector2

tolerance?

number = 0

Returns

NodeBox | null


play()

play(speed?, reverse?): void

Defined in: runtime/playback-controller.ts:686

Parameters

speed?

number = 1

reverse?

boolean = false

Returns

void


repaint()

repaint(): void

Defined in: runtime/playback-controller.ts:672

Re-lay-out and repaint the current frame without re-running any generator. Cheap enough for a pointer-rate drag loop: stateAt early-returns for the frame that is already current, so this is layout + draw only.

Returns

void


replaceScene()

replaceScene(newScene): number

Defined in: runtime/playback-controller.ts:436

Hot-reload a single scene in place.

Re-runs only the edited scene's precomp (reusing every other scene's cached pass), swaps it into the state evaluator's matching slot, and refreshes the asset manager and clock duration. Untouched scenes keep their cached generators, so editing scene N never re-runs scenes ≠ N.

The render context is never torn down, so the next render paints the new frame over the old with no blank flash. Returns the resolved scene index, or -1 if no slot matched (caller can fall back to a full reload).

The edited scene's precomp re-runs a fresh asset pass, so any asset the edit added (a new image/video/font) is now in the global asset map — but it isn't loaded yet. We repaint synchronously for a no-flash swap of what's already warm, then kick off an async loadAt for the current frame and re-render once the new assets are ready, so the added asset actually shows up. The async pass is generation-guarded so a concurrent seek/tick wins.

Parameters

newScene

Scene

The edited scene instance (carries __sceneHotId).

Returns

number


screenshot()

screenshot(): string | undefined

Defined in: runtime/playback-controller.ts:517

Capture the current frame as a base-64 PNG data URL. Forces a fresh render before snapshotting because the WebGL drawing buffer may have been cleared since the last tick (we don't set preserveDrawingBuffer). Uses stateEvaluator.currentFrame (integer) rather than the clock's float so stateAt always hits its early-return and never resets scene state.

Returns

string | undefined


seek()

seek(frame): Promise<void>

Defined in: runtime/playback-controller.ts:383

Jump to frame, pausing playback first. Waits for required assets to load before rendering, then prefetches upcoming frames.

This is the scrub path, so its state replay runs interruptibly — a fast backward drag supersedes each in-flight seek instead of queueing a full replay per mouse move.

Parameters

frame

number

Returns

Promise<void>


seekWhilePlaying()

seekWhilePlaying(frame): void

Defined in: runtime/playback-controller.ts:502

Reposition the clock to frame without interrupting playback.

Parameters

frame

number

Returns

void


setMuted()

setMuted(muted): void

Defined in: runtime/playback-controller.ts:703

Parameters

muted

boolean

Returns

void


setNodeOverride()

setNodeOverride(path, props): void

Defined in: runtime/playback-controller.ts:639

Layer transient prop values over a node, on top of whatever the scene's generators evaluate to. Re-applied after every state evaluation and before layout, so they hold across frame changes and backward-seek replays until cleared.

This exists for direct manipulation: an editor dragging a node needs the rendered node to follow the pointer at pointer rate, and rebuilding the Scene for that is not viable — a host's scenes array is an input to the player's mount effect, so a rebuild disposes and re-creates the render surface. Overrides move the live node instead: no precomp, no teardown.

They are not persistence. The host commits the value to its own model on drop and clears the override; the next rebuild carries the same value in through the scene, and nothing is left behind.

Parameters

path

string

Structural path from TreeState.path (stable across rebuilds, unlike a node id).

props

NodeOverride

Any props the node accepts — { x, y }, { rotation }, { width, height }. Merged over any existing override.

Returns

void