Skip to content
Esc
navigateopen⌘Jpreview
On this page

Authoring API

The public React and TypeScript surface for Odori videos, layouts, inputs, preparation, and assets.

defineVideoMetadata()

Declares discovery-time metadata separately from the React component. The id defaults to the entry’s directory path under videos/ and is validated when the module loads. Pass one only to override the path.

defineVideoMetadata({
  id: "launch", // optional: defaults to the directory path
  title: "Product launch",
  description: "A concise introduction.",
  duration: "12s",
  layout: productLayout,
  schema: launchInput,
  defaultProps: {headline: "Ship the story."},
  tags: ["product", "launch"],
  thumbnailFrame: 45,
});

<Video>

Provides the resolved layout, input, prepared data, asset registry, and timeline context. It adds no duration of its own. Scene children are laid out in order; any other child renders for the whole video.

<Scene>

<Scene id="demo" duration="8s" name="Product demo">
  <ProductDemo />
</Scene>

Each scene receives a local frame clock starting at zero and is mounted only while it is on screen. Durations accept seconds (8), "8s", "500ms", or "45f".

<Stagger>

An offset window inside a scene, for staggered layers.

<Stagger from="1s" duration="2s">
  <Caption text="Preview needs no encode." />
</Stagger>

<Audio>

Places a sound on the timeline. Cues declared inside a scene are offset by that scene’s start.

<Audio src="/audio/bed.m4a" gain={0.75} fadeIn="1s" fadeOut="1.5s" duckUnder />

See Audio for placement, ducking, preview, and mixing.

<Fill> and <SafeArea>

Layout primitives. SafeArea insets its children by the inherited layout safe area.

defineVideoLayout() and defineBrand()

Define inherited format and presentation policy. Layouts cannot perform async work or add frames. See Layouts and brands.

defineInputSchema()

A serializable input contract with parse(), safeParse(), defaults(), and describe(). Studio generates controls from the description. Any zod-compatible schema also works.

definePrepare()

Defines the sole asynchronous data boundary. Its result must be serializable and is frozen into the render manifest.

Frame hooks

const frame = useFrame();
const {fps, width, height, durationInFrames} = useVideo();
const scale = useDesignScale();
const brand = useBrand();
const layout = useLayout();
const scene = useScene();
const assets = useAssets();

Motion must derive from the current frame. Wall-clock timers, CSS keyframes, and requestAnimationFrame are not deterministic render primitives.

Motion helpers

interpolate(frame, [0, 20], [0, 1], {easing: Easing.standard});
spring({frame, fps, from: 0.96, to: 1, damping: 20});

interpolate clamps by default, blends matching numeric segments inside strings, and accepts extrapolateLeft and extrapolateRight. spring solves a damped spring from the frame index alone.

Playback

const playback = usePlayback({fps: 30, durationInFrames: 360, autoPlay: true});
playback.toggle();
playback.step(1);
playback.seek(120);

<Player> wraps this hook with a canvas and controls. usePlayback is exported so a custom surface, such as Studio, can own its own transport.

Rendering surfaces

Export Purpose
OdoriRuntime Mount one video at one frame
Player Seekable player with optional controls
RenderSurface The render worker target, with a frame setter and readiness handshake
createRenderManifest Freeze inputs, format, scenes, audio, assets, and fonts
useAudioPlayback Drive cue playback from a frame clock

Was this page helpful?