Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Hosts

A host owns the clock. It picks a frame, waits until the frame is ready, then shows or captures it.

A composition draws whatever frame it is given. The host decides which frame that is and when it may be shown. Every host does the same two jobs through the same two contracts, so a scene, its renderers and its backends never know which host they are in.

Contract Job Read by
Frame source Holds the current frame, the viewport and the preview scale Every renderer, through useMotionactorFrame and the render context
Frame gate Lets anything on the page hold a frame until it is ready Fonts, images, video frames, GPU work, layout measurement

A frame is ready when its render has committed and every hold on the gate is released. Only then does a host show it, capture it or move on.

Host Package Clock Holds become
Native preview @motionactor/engine/react The page’s clock, or the audio clock when audio is decoded Promises that seek awaits
Player @motionactor/player The engine’s playback clock, driven by Video.js controls The player’s waiting state
Remotion @motionactor/remotion Remotion’s Player, Studio and renderer delayRender handles
HyperFrames @motionactor/hyperframes The HyperFrames runtime, which seeks in seconds Promises passed to waitUntil

Native preview

mountComposition renders a composition into an element you own and returns a controller. It builds the scene, measures any media the build asks for, loads fonts and assets, and resolves once frame 0 is on screen.

import { 
function mountComposition<P extends Record<string, unknown>, T>(options: Omit<CompositionHostOptions<P, T>, "mediaAdapter" | "preparedMedia"> & VideoDecodingOptions & AudioDecodingOptions & {
    onError?(error: unknown): void;
}): Promise<{
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    ... 8 more ...;
    updateEditor(next: unknown): Promise<...>;
}>
Native local preview. Frame readiness and one media inventory own playback.
mountComposition
} from "@motionactor/engine/react";
import {
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
} from "./title-card";
const
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
= await
mountComposition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>(options: (Omit<CompositionHostOptions<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>, "mediaAdapter" | "preparedMedia"> & VideoDecodingOptions) & (AudioDecodingOptions & {
    ...;
})): Promise<...>
Native local preview. Frame readiness and one media inventory own playback.
mountComposition
({
composition: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>
composition
:
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
,
element: HTMLElementelement: var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
The **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
getElementById
("stage")!,
props: HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>
props
: { title?: string | undefinedtitle: "Hello" },
});
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
.
viewport: {
    width: number;
    height: number;
    fps: number;
    durationInFrames: number;
}
viewport
.durationInFrames: numberdurationInFrames; // 60
await
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
.function seek(frame: number, mode?: PresentMode): Promise<void>
Pauses, then resolves once `frame` is on screen; with `playing`, once it is complete, painting at the next refresh (for a player keeping its own time).
seek
(30); // resolves once frame 30 is on screen
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
.getSnapshot: () => PreviewSnapshotgetSnapshot(); // { frame: 30, playing: false }
await
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
.play: () => Promise<void>play();
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
.pause: () => voidpause();
const preview: {
    play: () => Promise<void>;
    pause: () => void;
    dispose: (reason?: unknown) => void;
    seek(frame: number, mode?: PresentMode): Promise<void>;
    getSnapshot: () => PreviewSnapshot;
    subscribe(listener: () => void): () => void;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    readonly mediaPlan: MediaPlan;
    readonly composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    ... 4 more ...;
    updateEditor(next: unknown): Promise<...>;
}
preview
.dispose: (reason?: unknown) => voiddispose();
  • play follows the clock and skips frames rather than falling behind it. It stops at the end, and the next play starts over at frame 0.
  • seek(frame) pauses first. Seeks run one at a time in the order they were asked for, and a repeated frame still paints.
  • subscribe and getSnapshot have the shape React’s useSyncExternalStore takes, so a timeline or a play button reads the preview directly.
  • updateProps(next) rebuilds the scene and repaints the current frame. A change that alters the scene’s length throws, because the host’s timeline has a fixed length; mount again.
  • setPreviewScale(scale) tells canvas backends how far the stage is zoomed, so they draw at the pixel density the viewer sees.
  • resolver maps the composition’s asset references to URLs. Leave it out and files resolve against the page’s base URL.

The native host adds the media renderers from @motionactor/media after the composition’s own, so a scene with Video or Audio actors plays without listing them.

Remotion

toRemotionComposition is the whole integration for a composition. It returns the props of a Remotion <Composition>, with the duration measured by a build in calculateMetadata. See Compositions.

Underneath, RemotionHostBinding adapts Remotion to the two contracts: useCurrentFrame and useVideoConfig become the frame source, delayRender becomes the gate, and staticFile resolves assets. To draw a compiled scene inside a component you already have, wrap it yourself:

import { const useCurrentFrame: () => numberuseCurrentFrame } from "remotion";
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntime
The floor's `createRenderRuntime` for React: every backend's host is a React component and its stages wrap React nodes, so mixed backends (each with its own host props) compose without naming a type.
createRenderRuntime
,
const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
    compiled: CompiledScene<TSceneExports>;
    frame: number;
    width: number;
    height: number;
    sceneMeta?: unknown;
    style?: React.CSSProperties;
    children?: React.ReactNode;
    runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves the frame and paints its layers (`SceneLayers`) on a stage of `width` by `height`. Each layer is painted by the host its backend registers on the runtime; the default layer by the runtime's default backend. `children` are page markup: they render in the scope of the scene's DOM layer, where `<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor (`SceneLayers`).
SceneRenderer
} from "@motionactor/renderers/react";
import { function RemotionHostBinding({ children }: RemotionHostBindingProps): JSX.ElementRemotionHostBinding } from "@motionactor/remotion"; import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>
The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`, `createRenderRuntime({ backends: [dom] })`.
dom
} from "@motionactor/dom/react";
import {
const compiled: CompiledScene<{
    heading: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled
} from "./title.scene";
const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntime
The floor's `createRenderRuntime` for React: every backend's host is a React component and its stages wrap React nodes, so mixed backends (each with its own host props) compose without naming a type.
createRenderRuntime
({ backends: readonly AnyBackend<ReactBackendHost, React.ReactNode>[]backends: [const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>
The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`, `createRenderRuntime({ backends: [dom] })`.
dom
] });
export function function Intro(): JSX.ElementIntro() { const const frame: numberframe = function useCurrentFrame(): numberuseCurrentFrame(); return ( <function RemotionHostBinding({ children }: RemotionHostBindingProps): JSX.ElementRemotionHostBinding> <
const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
    compiled: CompiledScene<TSceneExports>;
    frame: number;
    width: number;
    height: number;
    sceneMeta?: unknown;
    style?: React.CSSProperties;
    children?: React.ReactNode;
    runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves the frame and paints its layers (`SceneLayers`) on a stage of `width` by `height`. Each layer is painted by the host its backend registers on the runtime; the default layer by the runtime's default backend. `children` are page markup: they render in the scope of the scene's DOM layer, where `<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor (`SceneLayers`).
SceneRenderer
compiled: CompiledScene<{
    heading: Title;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled
={
const compiled: CompiledScene<{
    heading: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled
}
frame: numberframe={const frame: numberframe} width: numberwidth={1920} height: numberheight={1080} runtime?: ReactRenderRuntime | undefinedruntime={const runtime: ReactRenderRuntimeruntime} /> </function RemotionHostBinding({ children }: RemotionHostBindingProps): JSX.ElementRemotionHostBinding> ); }

toRemotionComposition adds the media renderers after the composition’s own, as every host does. A SceneRenderer you mount yourself draws with the runtime you pass it, so a scene with media actors lists mediaRendererRegistry from @motionactor/media/react in that runtime’s renderers.

HyperFrames

The HyperFrames runtime owns time and dispatches an hf-seek event with a time in seconds. mountHyperframesComposition listens for it, rounds the time to a frame, and passes the frame’s readiness to the event’s waitUntil, so HyperFrames captures only finished frames.

import { 
function mountHyperframesComposition<P extends Record<string, unknown>, T>(options: Omit<CompositionHostOptions<P, T>, "mediaAdapter"> & {
    eventTarget?: EventTarget;
}): Promise<{
    dispose: (reason?: unknown) => void;
    setPreviewScale: (scale: number) => Promise<void>;
    updateProps: (next: P) => Promise<void>;
    seek(seconds: number): Promise<void>;
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    advance(frame: number): Promise<...>;
    commitFrame(frame: number): Promise<...>;
    settle: () => Promise<...>;
    outstandingHolds: () => readonly string[];
}>
The shared host owns React/media readiness; HyperFrames supplies its protocol.
mountHyperframesComposition
} from "@motionactor/hyperframes/react";
import {
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
} from "./title-card";
const
const mounted: {
    dispose: (reason?: unknown) => void;
    setPreviewScale: (scale: number) => Promise<void>;
    updateProps: (next: HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>) => Promise<void>;
    seek(seconds: number): Promise<void>;
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    advance(frame: number): Promise<void>;
    commitFrame(frame: number): Promise<...>;
    settle: () => Promise<...>;
    outstandingHolds: () => readonly string[];
}
mounted
= await
mountHyperframesComposition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>(options: Omit<CompositionHostOptions<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>, "mediaAdapter"> & {
    eventTarget?: EventTarget;
}): Promise<{
    dispose: (reason?: unknown) => void;
    setPreviewScale: (scale: number) => Promise<...>;
    ... 7 more ...;
    outstandingHolds: () => readonly string[];
}>
The shared host owns React/media readiness; HyperFrames supplies its protocol.
mountHyperframesComposition
({
composition: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>
composition
:
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
,
element: HTMLElementelement: var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
The **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
getElementById
("composition")!,
props: HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>
props
: { title?: string | undefinedtitle: "Hello" },
}); var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.addEventListener<"pagehide">(type: "pagehide", listener: (this: Window, ev: PageTransitionEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)
The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
("pagehide", () =>
const mounted: {
    dispose: (reason?: unknown) => void;
    setPreviewScale: (scale: number) => Promise<void>;
    updateProps: (next: HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>) => Promise<void>;
    seek(seconds: number): Promise<void>;
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    advance(frame: number): Promise<void>;
    commitFrame(frame: number): Promise<...>;
    settle: () => Promise<...>;
    outstandingHolds: () => readonly string[];
}
mounted
.dispose: (reason?: unknown) => voiddispose());

The element gets the data-composition-id, data-duration, data-width and data-height attributes HyperFrames reads. A seek that arrives while another is running waits its turn.

HyperFrames Studio discovers compositions from generated pages. buildHyperframesProject writes them from your entries, taking the id, size and duration from each composition:

import {
  
function buildHyperframesProject(project: HyperframesProject, options?: {
    cwd?: string;
}): Promise<{
    outDir: string;
    compositions: (CompositionMetadata & {
        directory: string;
    })[];
}>
Generates upstream Studio-discoverable HTML. Does not start Studio or a browser.
buildHyperframesProject
,
const defineHyperframesProject: (project: HyperframesProject) => HyperframesProjectdefineHyperframesProject, } from "@motionactor/hyperframes/node"; const const project: HyperframesProjectproject = function defineHyperframesProject(project: HyperframesProject): HyperframesProjectdefineHyperframesProject({
HyperframesProject.compositions: readonly {
    entry: string;
    export: string;
    path?: string;
    props?: Record<string, unknown>;
    binding?: CompositionBinding;
}[]
compositions
: [
{ entry: stringentry: "./title-card.tsx", export: stringexport: "titleCard" }, { entry: stringentry: "./title-card.tsx", export: stringexport: "titleCard", path?: string | undefinedpath: "hello", props?: Record<string, unknown> | undefinedprops: { title: stringtitle: "Hello" }, }, ], HyperframesProject.publicDir?: string | undefinedpublicDir: "./public", HyperframesProject.outDir: stringoutDir: "./.hyperframes", }); await
function buildHyperframesProject(project: HyperframesProject, options?: {
    cwd?: string;
}): Promise<{
    outDir: string;
    compositions: (CompositionMetadata & {
        directory: string;
    })[];
}>
Generates upstream Studio-discoverable HTML. Does not start Studio or a browser.
buildHyperframesProject
(const project: HyperframesProjectproject);

The first composition goes at the root of outDir, and each other one in a directory named by path or by its id. Props have to be JSON. Files in publicDir are copied into each composition’s assets directory, and media is measured with FFprobe at build time. esbuild, @hyperframes/core and @hyperframes/engine are optional peers needed only for this build.

Writing a host

A host is a clock wrapped around three calls. prepareCompositionHost from @motionactor/engine/react mounts a composition into an element and resolves to commitFrame, which renders a frame, and settle, which resolves when the gate is idle and the browser has painted. createSceneController from @motionactor/engine queues those calls so that seeks never overlap:

import { 
function createSceneController({ host, fps, totalFrames, onError }: {
    host: SceneHost;
    onError?(error: unknown): void;
    fps: number;
    totalFrames: number;
}): {
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    seek(frame: number): Promise<void>;
    advance(frame: number): Promise<void>;
    commitFrame(frame: number): Promise<void>;
    settle: () => Promise<void>;
    outstandingHolds: () => readonly string[];
    dispose(reason?: unknown): void;
}
Serial, cancellable frame work. Repeated requests for the same frame still paint.
createSceneController
} from "@motionactor/engine";
import { function createDOMMediaAdapter(options?: DOMMediaSessionOptions): MediaAdapter
Browser media elements as a React `MediaAdapter`.
createDOMMediaAdapter
,
function prepareCompositionHost<P extends Record<string, unknown>, T>(options: CompositionHostOptions<P, T>): Promise<{
    source: ExternalFrameSource;
    gate: PromiseFrameGate;
    viewport: {
        width: number;
        height: number;
        fps: number;
        durationInFrames: number;
    };
    resolver: AssetResolver;
    composition: CompositionDefinition<P, T>;
    readonly compiled: CompiledScene<T, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    ... 8 more ...;
    dispose(reason?: unknown): void;
}>
Resolve metadata requested by a build before allocating its React root.
prepareCompositionHost
,
} from "@motionactor/engine/react"; import {
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
} from "./title-card";
const
const host: {
    source: ExternalFrameSource;
    gate: PromiseFrameGate;
    viewport: {
        width: number;
        height: number;
        fps: number;
        durationInFrames: number;
    };
    resolver: AssetResolver;
    composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    ... 8 more ...;
    dispose(reason?: unknown): void;
}
host
= await
prepareCompositionHost<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>(options: CompositionHostOptions<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>): Promise<{
    source: ExternalFrameSource;
    gate: PromiseFrameGate;
    viewport: {
        width: number;
        height: number;
        fps: number;
        durationInFrames: number;
    };
    ... 11 more ...;
    dispose(reason?: unknown): void;
}>
Resolve metadata requested by a build before allocating its React root.
prepareCompositionHost
({
CompositionHostOptions<HostInputArgs<{ readonly title: ValueInput<DataType<"text", ZodString>, string, false>; }>, { heading: Title; }>.composition: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}>
composition
:
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
,
CompositionHostOptions<P extends Record<string, unknown>, T>.element: HTMLElementelement: var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
The **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
getElementById
("stage")!,
CompositionHostOptions<HostInputArgs<{ readonly title: ValueInput<DataType<"text", ZodString>, string, false>; }>, { heading: Title; }>.props: HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>
props
:
const titleCard: CompositionDefinition<HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>, {
    heading: Title;
}, ReactBackendHost, ReactNode, ReactElement<unknown, string | JSXElementConstructor<any>>>
titleCard
.
CompositionDefinition<HostInputArgs<{ readonly title: ValueInput<DataType<"text", ZodString>, string, false>; }>, { heading: Title; }, ReactBackendHost, ReactNode, ReactElement<...>>.defaultProps: HostInputArgs<{
    readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>
The props a host does not pass. Always present on a defined composition.
defaultProps
,
CompositionHostOptions<P extends Record<string, unknown>, T>.mediaAdapter: MediaAdaptermediaAdapter: function createDOMMediaAdapter(options?: DOMMediaSessionOptions): MediaAdapter
Browser media elements as a React `MediaAdapter`.
createDOMMediaAdapter
(),
}); const
const controller: {
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    seek(frame: number): Promise<void>;
    advance(frame: number): Promise<void>;
    commitFrame(frame: number): Promise<void>;
    settle: () => Promise<void>;
    outstandingHolds: () => readonly string[];
    dispose(reason?: unknown): void;
}
controller
=
function createSceneController({ host, fps, totalFrames, onError }: {
    host: SceneHost;
    onError?(error: unknown): void;
    fps: number;
    totalFrames: number;
}): {
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    seek(frame: number): Promise<void>;
    advance(frame: number): Promise<void>;
    commitFrame(frame: number): Promise<void>;
    settle: () => Promise<void>;
    outstandingHolds: () => readonly string[];
    dispose(reason?: unknown): void;
}
Serial, cancellable frame work. Repeated requests for the same frame still paint.
createSceneController
({
host: SceneHosthost, fps: numberfps:
const host: {
    source: ExternalFrameSource;
    gate: PromiseFrameGate;
    viewport: {
        width: number;
        height: number;
        fps: number;
        durationInFrames: number;
    };
    resolver: AssetResolver;
    composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    ... 8 more ...;
    dispose(reason?: unknown): void;
}
host
.
viewport: {
    width: number;
    height: number;
    fps: number;
    durationInFrames: number;
}
viewport
.fps: numberfps,
totalFrames: numbertotalFrames:
const host: {
    source: ExternalFrameSource;
    gate: PromiseFrameGate;
    viewport: {
        width: number;
        height: number;
        fps: number;
        durationInFrames: number;
    };
    resolver: AssetResolver;
    composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    ... 8 more ...;
    dispose(reason?: unknown): void;
}
host
.
viewport: {
    width: number;
    height: number;
    fps: number;
    durationInFrames: number;
}
viewport
.durationInFrames: numberdurationInFrames,
}); // One frame per key press. let let frame: numberframe = 0; var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.addEventListener<"keydown">(type: "keydown", listener: (this: Window, ev: KeyboardEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)
The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
("keydown", () => {
let frame: numberframe = (let frame: numberframe + 1) %
const host: {
    source: ExternalFrameSource;
    gate: PromiseFrameGate;
    viewport: {
        width: number;
        height: number;
        fps: number;
        durationInFrames: number;
    };
    resolver: AssetResolver;
    composition: CompositionDefinition<HostInputArgs<{
        readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
    }>, {
        heading: Title;
    }>;
    readonly compiled: CompiledScene<{
        heading: Title;
    }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
    ... 8 more ...;
    dispose(reason?: unknown): void;
}
host
.
viewport: {
    width: number;
    height: number;
    fps: number;
    durationInFrames: number;
}
viewport
.durationInFrames: numberdurationInFrames;
void
const controller: {
    enqueue: (operation: (signal: AbortSignal) => Promise<void>) => Promise<void>;
    withFrame: <T>({ frame, signal: requestedSignal, read }: FrameReadRequest<T>) => Promise<T>;
    seek(frame: number): Promise<void>;
    advance(frame: number): Promise<void>;
    commitFrame(frame: number): Promise<void>;
    settle: () => Promise<void>;
    outstandingHolds: () => readonly string[];
    dispose(reason?: unknown): void;
}
controller
.function seek(frame: number): Promise<void>
Commit `frame` and resolve once it is on screen.
seek
(let frame: numberframe);
});

prepareCompositionHost measures media durations before it mounts. The media adapter decides how video and audio play; createDOMMediaAdapter uses <video> and <audio> elements.

A failed frame fails the controller: every later seek rejects with the same error and reports it to onError. controller.withFrame({ frame, signal, read }) holds a frame on screen while read runs, which is how capture reads pixels without a later seek painting over them.

Was this page helpful?