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 | nullThe **`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();
playfollows the clock and skips frames rather than falling behind it. It stops at the end, and the nextplaystarts 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.subscribeandgetSnapshothave the shape React’suseSyncExternalStoretakes, 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.resolvermaps 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>) => ReactRenderRuntimeThe 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>): ReactRenderRuntimeThe 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 | nullThe **`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 globalThisThe **`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): MediaAdapterBrowser 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 | nullThe **`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): MediaAdapterBrowser 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 globalThisThe **`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.