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

Media, assets and fonts

How a scene names files, how hosts load them, and how video and audio play frame by frame.

A scene never holds bytes. Its signals hold asset references, small JSON values that name a file. A host turns each reference into a URL through a resolver, loads what the composition uses before showing frame 0, and holds every later frame until its images are decoded and its videos show the right picture.

Asset references

image, video, audio and font from @motionactor/schema each make references of their type:

import { function assetKey(ref: AssetRef): string
What a ref loads, independent of its type: equal keys load the same bytes.
assetKey
, const image: AssetType<"image", "image">image, const video: AssetType<"video", "video">video } from "@motionactor/schema";
const const hero: AssetRef<"image">hero = const image: AssetType<"image", "image">image.
AssetType<"image", "image">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"image">
file
("photos/hero.jpg");
// { type: "image", kind: "file", src: "photos/hero.jpg" } const const trailer: AssetRef<"video">trailer = const video: AssetType<"video", "video">video.
AssetType<"video", "video">.url(src: string, options?: {
    mimeType?: string;
}): AssetRef<"video">
url
("https://cdn.example.com/trailer.mp4");
function assetKey(ref: AssetRef): string
What a ref loads, independent of its type: equal keys load the same bytes.
assetKey
(const trailer: AssetRef<"video">trailer); // "url:https://cdn.example.com/trailer.mp4"
Constructor Names Resolves to
.file(src) A file the host serves src under the resolver’s publicBase
.url(src) An absolute URL src itself
.bytes(data, { mimeType }) Bytes the scene carries An object URL made from the bytes
.blob(hash, { mimeType }) Bytes a store keeps by SHA-256 blobBase followed by the hash

A signal declared with signal.of(image) accepts only image references. A reference with a mimeType outside its type’s range throws when it is made: image.file("a.png", { mimeType: "video/mp4" }) fails with “Invalid image ref: mimeType must match image/*”.

Resolvers

An AssetResolver has url(ref), which must answer synchronously because views render synchronously, and bytes(ref) for decoders. An optional prepare(refs) runs once with every reference the composition uses, and a promise it returns holds the first frame. That includes each reference a derived or bound field resolves to over the timeline. A reference a host will publish on a channel later is not known yet, so prepare cannot see it.

import { function createDefaultResolver(options?: DefaultResolverOptions): AssetResolver
`file` → `publicBase` + src (an absolute src, such as a bundled import's URL, stays as is), `url` → src, `blob` → a blob URL of its payload, else `blobBase` + hash. `prepare` registers payloads of `blob` refs by hash, so a ref that crossed a serialization boundary still resolves once the scene that made it ran here.
createDefaultResolver
} from "@motionactor/engine/react";
import { const image: AssetType<"image", "image">image } from "@motionactor/schema"; const const resolver: AssetResolverresolver = function createDefaultResolver(options?: DefaultResolverOptions): AssetResolver
`file` → `publicBase` + src (an absolute src, such as a bundled import's URL, stays as is), `url` → src, `blob` → a blob URL of its payload, else `blobBase` + hash. `prepare` registers payloads of `blob` refs by hash, so a ref that crossed a serialization boundary still resolves once the scene that made it ran here.
createDefaultResolver
({
DefaultResolverOptions.publicBase?: string | URL | ((path: string) => string) | undefined
Where `file` refs are served from: an absolute base, or a host's own mapping of a normalized public path (Remotion's `staticFile`). Defaults to the origin root.
publicBase
: "https://assets.example.com/reel/",
DefaultResolverOptions.blobBase?: string | URL | ((hash: string) => string) | undefined
Where stored `blob` refs are served from, as `blobBase` + hash: an absolute base, or a host's own mapping of the hash. A `blob` whose payload this process holds loads from that payload instead.
blobBase
: "https://store.example.com/blobs/",
}); const resolver: AssetResolverresolver.AssetResolver.url(ref: AssetRef): string
A loadable URL for any ref; sync because views render sync.
url
(const image: AssetType<"image", "image">image.
AssetType<"image", "image">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"image">
file
("photos/hero.jpg"));
// "https://assets.example.com/reel/photos/hero.jpg"

locate(ref) comes first when you supply it: return a URL to take over a reference, such as one in a cache, or undefined to fall through to the rules above. Each host has a default for publicBase. The native preview resolves against the page’s base URL, Remotion uses staticFile, and HyperFrames pages use their generated assets directory.

Video and audio

Video, Image, Audio, AudioTrack and AudioManager come from @motionactor/media. Video and audio play from one signal: sourceTime, the seconds into the source at each frame. The commands are tweens on it, so playback seeks, scrubs and exports exactly.

import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
,
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
,
const input: {
    of: {
        <T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
        <T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
        <T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
        <TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
    };
}
input
} from "@motionactor/core";
import { class AudioAudio, class VideoVideo } from "@motionactor/media"; import { const audio: AssetType<"audio", "audio">audio, const video: AssetType<"video", "video">video } from "@motionactor/schema"; 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 defineComposition: DefineComposition<ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
A composition of a `createScene` definition (`scene`), painted by `render`, or by default by `SceneRenderer` over `background`.
defineComposition
,
} from "@motionactor/renderers/react"; 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";
const
const reelScene: SceneDefinition<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    clip: Video;
}>
reelScene
=
createScene<{
    clip: Video;
}, {
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<...>, {
    clip: Video;
}>, options?: SceneDefinitionOptions<...> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
(
function* (ctx: SceneContextctx, { footage: AssetRef<"video">footage, score: AssetRef<"audio">score }) { const const clip: Videoclip = ctx: SceneContextctx.
CompileContext.spawn<Video>(definition: ActorClass<Video>, overrides: {
    content: {
        source: SignalInputValue<AssetRef<"video">>;
        posterSource?: SignalInputValue<AssetRef<"image"> | null> | undefined;
        fit?: SignalInputValue<MediaFit> | undefined;
        focalPointX?: SignalInputValue<number> | undefined;
        focalPointY?: SignalInputValue<number> | undefined;
    };
    id?: undefined;
    localLayout?: undefined;
    worldLayout?: undefined;
    compositing?: {
        ...;
    } | undefined;
    ... 31 more ...;
    rampTo?: undefined;
}, options?: {
    ...;
} | undefined): Video (+1 overload)
spawn
(class VideoVideo, {
content: {
    source: SignalInputValue<AssetRef<"video">>;
    posterSource?: SignalInputValue<AssetRef<"image"> | null> | undefined;
    fit?: SignalInputValue<MediaFit> | undefined;
    focalPointX?: SignalInputValue<number> | undefined;
    focalPointY?: SignalInputValue<number> | undefined;
}
content
: { source: SignalInputValue<AssetRef<"video">>source: footage: AssetRef<"video">footage },
layout?: {
    width?: Readonly<{
        kind: "layout.percent";
        value: number;
    }> | SignalInputValue<number | `${number}%` | Readonly<{
        kind: "layout.percent";
        value: number;
    }>> | undefined;
    height?: Readonly<{
        kind: "layout.percent";
        value: number;
    }> | SignalInputValue<number | `${number}%` | Readonly<{
        kind: "layout.percent";
        value: number;
    }>> | undefined;
    x?: SignalInputValue<LayoutValue> | undefined;
    y?: SignalInputValue<LayoutValue> | undefined;
} | undefined
layout
: {
width?: Readonly<{
    kind: "layout.percent";
    value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
    kind: "layout.percent";
    value: number;
}>> | undefined
width
: 1280,
height?: Readonly<{
    kind: "layout.percent";
    value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
    kind: "layout.percent";
    value: number;
}>> | undefined
height
: 720 },
}); const const music: Audiomusic = ctx: SceneContextctx.
CompileContext.spawn<Audio>(definition: ActorClass<Audio>, overrides: {
    content: {
        source: SignalInputValue<AssetRef<"audio">>;
    };
    id?: undefined;
    cursor?: undefined;
    cursorSeconds?: undefined;
    sceneFps?: undefined;
    onInit?: undefined;
    onEnter?: undefined;
    onExit?: undefined;
    enter?: undefined;
    exit?: undefined;
    parent?: undefined;
    play?: undefined;
    timing?: {
        start?: SignalInputValue<number> | undefined;
        end?: SignalInputValue<number> | undefined;
    } | undefined;
    sourceTime?: SignalInputValue<number> | undefined;
    ... 9 more ...;
    fade?: {
        ...;
    } | undefined;
}, options?: {
    ...;
} | undefined): Audio (+1 overload)
spawn
(class AudioAudio, {
content: {
    source: SignalInputValue<AssetRef<"audio">>;
}
content
: { source: SignalInputValue<AssetRef<"audio">>source: score: AssetRef<"audio">score },
volume?: SignalInputValue<number> | undefinedvolume: 0.6,
fade?: {
    out?: SignalInputValue<number> | undefined;
    in?: SignalInputValue<number> | undefined;
} | undefined
fade
: { in?: SignalInputValue<number> | undefinedin: 0.5, out?: SignalInputValue<number> | undefinedout: 1 },
}); const music: Audiomusic.Audio.loop(params?: MediaLoopParams): voidloop(); yield* const clip: Videoclip.Video.play(params?: MediaPlayParams): Generator<number, void, unknown>play({ from?: number | undefinedfrom: 4, to?: number | undefinedto: 6 }); yield* const clip: Videoclip.Video.hold(duration: number): Generator<number, void, unknown>hold(0.5); yield* const clip: Videoclip.Video.rewind(params: MediaScrubParams): Generator<number, void, unknown>rewind({ to: numberto: 4, duration: numberduration: 1, easing?: EasingFunction | undefinedeasing:
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
.inOutCubic: EasingFunctioninOutCubic });
yield* const clip: Videoclip.Video.play(params?: MediaPlayParams): Generator<number, void, unknown>play({ rate?: number | undefinedrate: 2 }); return { clip: Videoclip }; }, {
SceneDefinitionOptions<{ readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>; readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<...>, true>; }, Readonly<...>>.inputs?: {
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
} | undefined
inputs
: { footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>footage:
const input: {
    of: {
        <T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
        <T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
        <T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
        <TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
    };
}
input
.of: <AssetType<"video", "video">>(type: AssetType<"video", "video">) => ValueInput<AssetType<"video", "video">, AssetRef<"video">, true> (+3 overloads)
Declare a scene input. Over a named type from `@motionactor/schema`, `input.of(image)` is required; `input.of(text, "Launch day")` and `input.of(image, null)` are not. Over an actor blueprint, `input.of(Narrator)` takes a `Narrator` from the scene that stages it, and has no default.
of
(const video: AssetType<"video", "video">video), score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>score:
const input: {
    of: {
        <T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
        <T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
        <T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
        <TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
    };
}
input
.of: <AssetType<"audio", "audio">>(type: AssetType<"audio", "audio">) => ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true> (+3 overloads)
Declare a scene input. Over a named type from `@motionactor/schema`, `input.of(image)` is required; `input.of(text, "Launch day")` and `input.of(image, null)` are not. Over an actor blueprint, `input.of(Narrator)` takes a `Narrator` from the scene that stages it, and has no default.
of
(const audio: AssetType<"audio", "audio">audio) } },
); export const
const reel: CompositionDefinition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
reel
=
defineComposition<SceneDefinition<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    clip: Video;
}>>(definition: SceneCompositionInput<SceneDefinition<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<...>, {
    clip: Video;
}>, ReactBackendHost, React.ReactNode, React.ReactElement<...>>): CompositionDefinition<...> (+1 overload)
`scene`'s inputs are the props.
defineComposition
({
id: stringid: "Reel", width: numberwidth: 1920, height: numberheight: 1080, fps: numberfps: 30,
defaultProps: {
    footage: AssetRef<"video">;
    score: AssetRef<"audio">;
} & {}
defaultProps
: {
footage: AssetRef<"video">footage: const video: AssetType<"video", "video">video.
AssetType<"video", "video">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"video">
file
("media/surf.mp4"),
score: AssetRef<"audio">score: const audio: AssetType<"audio", "audio">audio.
AssetType<"audio", "audio">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"audio">
file
("media/theme.mp3"),
}, runtime: RenderRuntime<ReactBackendHost, React.ReactNode>runtime: 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
] }),
scene: SceneDefinition<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    clip: Video;
}>
scene
:
const reelScene: SceneDefinition<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    clip: Video;
}>
reelScene
,
});
Command sourceTime does Scene waits
play({ from?, to?, rate? }) Runs from from (or where it is) to to (or the end) at rate Yes
loop({ window?, rate? }) Repeats a window until another command takes over No
hold(seconds) Stays put: a freeze frame Yes
seekTo(time) Jumps Yes, zero frames
fastForward / rewind({ to, duration, easing? }) Tweens to to, in either direction Yes
rampTo({ to, rate, duration, fromRate? }) Eases its rate from fromRate to rate, then plays on to to Yes

timing: { start, end } selects a window of the source, with end: -1 meaning its end, and sourceTime counts from start. Video is muted by default, and its sound joins the mix when you set muted: false. Audio has volume, muted and fade: { in, out } in seconds. Timed sound cues and sound packs are on Audio.

Before playback, the host turns each clip’s sourceTime curve into segments. A run of frames with a steady positive slope becomes one segment played at that rate. A hold, a rewind or the wrap of a loop becomes a seek on each frame. In this reel, frames 0 to 60 are one segment at rate 1 and frames 105 to 224 one segment at rate 2; the hold and the rewind between them are seeks.

Lengths a scene needs

play() with no to and loop() with no window need the source’s length, and so does any timing.end of -1. The scene cannot read a file, so the first build stops with a MediaDurationRequired naming the reference. A host catches it, measures the file in the browser, and builds again with the length known. Outside a host, pass the lengths yourself, keyed by assetKey:

import { function buildComposition<P extends Record<string, unknown>, T, THost, TNode, TRender>(composition: CompositionDefinition<P, T, THost, TNode, TRender>, props: P, options?: CompositionBuildOptions): CompositionBuild<T>buildComposition } from "@motionactor/renderers";
import { function assetKey(ref: AssetRef): string
What a ref loads, independent of its type: equal keys load the same bytes.
assetKey
} from "@motionactor/schema";
import {
const reel: CompositionDefinition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
reel
} from "./reel";
const { const footage: AssetRef<"video">footage, const score: AssetRef<"audio">score } =
const reel: CompositionDefinition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
reel
.
CompositionDefinition<HostInputArgs<{ readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>; readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<...>, true>; }>, { ...; }, ReactBackendHost, React.ReactNode, React.ReactElement<...>>.defaultProps: HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>
The props a host does not pass. Always present on a defined composition.
defaultProps
;
using
using build: CompositionBuild<{
    clip: Video;
}>
build
=
buildComposition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>(composition: CompositionDefinition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<...>>, props: HostInputArgs<...>, options?: CompositionBuildOptions): CompositionBuild<...>
buildComposition
(
const reel: CompositionDefinition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
reel
,
const reel: CompositionDefinition<HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>, {
    clip: Video;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
reel
.
CompositionDefinition<HostInputArgs<{ readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>; readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<...>, true>; }>, { ...; }, ReactBackendHost, React.ReactNode, React.ReactElement<...>>.defaultProps: HostInputArgs<{
    readonly footage: ValueInput<AssetType<"video", "video">, AssetRef<"video">, true>;
    readonly score: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>
The props a host does not pass. Always present on a defined composition.
defaultProps
, {
CompositionBuildOptions.mediaDurations?: Readonly<Record<string, number>> | undefinedmediaDurations: { [function assetKey(ref: AssetRef): string
What a ref loads, independent of its type: equal keys load the same bytes.
assetKey
(const footage: AssetRef<"video">footage)]: 12, [function assetKey(ref: AssetRef): string
What a ref loads, independent of its type: equal keys load the same bytes.
assetKey
(const score: AssetRef<"audio">score)]: 30 },
});
using build: CompositionBuild<{
    clip: Video;
}>
build
.
SceneBuild<{ ...; }>.compiled: CompiledScene<{
    clip: Video;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled
.CompiledScene<TSceneExports = void | SceneExportShape, TLayers extends CompiledLayers = Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>.totalDuration: numbertotalDuration; // 225
using build: CompositionBuild<{
    clip: Video;
}>
build
.
SceneBuild<{ ...; }>.compiled: CompiledScene<{
    clip: Video;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled
.
CompiledScene<{ ...; }, Readonly<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
    clip: Video;
}>
at
(90).
FrameState<{ ...; }>.scene: {
    clip: ResolvedActorOfInstance<Video> | undefined;
}
scene
.clip: ResolvedActorOfInstance<Video> | undefinedclip?.sourceTime: number | undefined
Seconds into the selected source window.
sourceTime
; // 5, halfway back

A host can only catch the error during a build it runs. That is why reelScene takes its media as inputs, so the host builds it. A scene built when its module loads asks for the length before any host is there to measure it.

Drawing media

mediaRendererRegistry from @motionactor/media/react holds the DOM renderers for the media actors and the collector that finds every clip a scene plays. Every host (the native preview, HyperFrames and Remotion) adds it after the composition’s own renderers with withMediaFamily, so reel lists none. The views are DOM renderers, so Image and Video actors belong in a DOM layer.

For a designed variant, extend the actor and reuse its view. style is merged over the view’s own box:

import { 
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
} from "@motionactor/core";
import { class ImageImage } from "@motionactor/media"; import { const ImageView: React.FC<ImageViewProps>ImageView } from "@motionactor/media/react"; 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";
export class class PhotoPhoto extends class ImageImage { Photo.radius: ActorFieldSignal<number>radius = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
(24);
} export const const photo: BackendRendererDefinition<readonly [typeof Photo], "dom">photo = 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
.render: <typeof Photo>(blueprint: typeof Photo, render: ActorRendererForBlueprints<readonly [typeof Photo], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Photo], "dom"> (+1 overload)render(class PhotoPhoto, ({
actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
    compositing: {
        blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
        isolate: boolean;
        clip: number | null;
    };
    measuredBox: Rect | undefined;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 8 more ...;
    objectPosition: string;
} & {
    ...;
}
The actor. Reading it makes the renderer depend on all of it.
actor
}) => (
<const ImageView: React.FC<ImageViewProps>ImageView
actor: Omit<ResolvedActor, "signals" | "channels"> & {
    compositing: {
        blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
        isolate: boolean;
        clip: number | null;
    };
    measuredBox: Rect | undefined;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 7 more ...;
    objectPosition: string;
} & {
    ...;
}
actor
={
actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
    compositing: {
        blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
        isolate: boolean;
        clip: number | null;
    };
    measuredBox: Rect | undefined;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 8 more ...;
    objectPosition: string;
} & {
    ...;
}
The actor. Reading it makes the renderer depend on all of it.
actor
}
style?: React.CSSProperties | undefined
Merged over the frame's own box: how a designed subclass adds chrome.
style
={{ StandardShorthandProperties<string | number, string & {}>.borderRadius?: Property.BorderRadius<string | number> | undefined
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015. **Syntax**: `<length-percentage [0,∞]>{1,4} [ / <length-percentage [0,∞]>{1,4} ]?` | Chrome | Firefox | Safari | Edge | IE | | :-----: | :-----: | :-----: | :----: | :---: | | **4** | **4** | **5** | **12** | **9** | | 1 _-x-_ | | 3 _-x-_ | | |
@seehttps://developer.mozilla.org/docs/Web/CSS/Reference/Properties/border-radius
borderRadius
:
actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
    compositing: {
        blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
        isolate: boolean;
        clip: number | null;
    };
    measuredBox: Rect | undefined;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 8 more ...;
    objectPosition: string;
} & {
    ...;
}
The actor. Reading it makes the renderer depend on all of it.
actor
.
signals: Readonly<Record<string, unknown>> & {
    compositing: {
        blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
        isolate: boolean;
        clip: number | null;
    };
    measuredBox: Rect | undefined;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 8 more ...;
    objectPosition: string;
}
signals
.radius: numberradius }}
/> ));

List photo in the runtime’s renderers. The media family comes after them, so Photo gets this renderer and a plain Image still gets the built-in one.

An image holds its frame until the browser has decoded it, so the first frame that shows it already shows all of it.

Fonts

A composition lists the font faces it draws with. The host loads each one through the FontFace API and holds the first frame until all of them are ready, so text measures and paints in the right face from frame 0.

import { const font: AssetType<"font", "font">font } from "@motionactor/schema";
import { const defineComposition: DefineComposition<ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
A composition of a `createScene` definition (`scene`), painted by `render`, or by default by `SceneRenderer` over `background`.
defineComposition
} from "@motionactor/renderers/react";
export const const titleCard: CompositionDefinition<HostInputArgs<Readonly<Record<never, SceneInput>>>, void, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>titleCard = defineComposition<SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>>(definition: SceneCompositionInput<SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>): CompositionDefinition<...> (+1 overload)
`scene`'s inputs are the props.
defineComposition
({
id: stringid: "TitleCard", width: numberwidth: 1920, height: numberheight: 1080, fps: numberfps: 30, runtime: RenderRuntime<ReactBackendHost, React.ReactNode>runtime, fonts?: readonly CompositionFont[] | undefinedfonts: [ { CompositionFont.family: stringfamily: "Inter", CompositionFont.source: AssetRef<"font">source: const font: AssetType<"font", "font">font.
AssetType<"font", "font">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"font">
file
("fonts/Inter.woff2") },
{ CompositionFont.family: stringfamily: "Inter", CompositionFont.source: AssetRef<"font">source: const font: AssetType<"font", "font">font.
AssetType<"font", "font">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"font">
file
("fonts/Inter-Bold.woff2"),
CompositionFont.weight?: string | undefinedweight: "700", }, ], scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>scene: const titleScene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>titleScene, render?(args: CompositionRenderArgs<HostInputArgs<Readonly<Record<never, SceneInput>>>, void>): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>render: ({ compiled: CompiledScene<void, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled, frame: numberframe, width: numberwidth, height: numberheight }) => ( <
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<void, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled={compiled: CompiledScene<void, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled} frame: numberframe={frame: numberframe} width: numberwidth={width: numberwidth} height: numberheight={height: numberheight} /> ), });

Each face takes the @font-face descriptors: weight, style, stretch, unicodeRange and featureSettings. A face that has not loaded after 15 seconds fails the frame. Loaded faces are cached per document, so remounting a composition does not fetch them again.

Was this page helpful?