Playing in the browser
SceneMedia plays a compiled scene behind Video.js controls.
@motionactor/player plays a compiled scene on a page. SceneMedia is a
Video.js media component: it goes where <Video src> would, and the
Video.js player around it supplies the controls, skin, keyboard shortcuts and fullscreen. Every
figure on this site plays through it.
import { function createPlayer(config: CreatePlayerConfig<VideoFeatures>): CreatePlayerResult<VideoPlayerStore> (+2 overloads)Create a player instance with a typed Player component and hooks.createPlayer, function Hotkey({ keys, action, value, disabled, target }: HotkeyProps$1): React.ReactNodeHotkey } from "@videojs/react";
import { function VideoSkin(props: VideoSkinProps): import("react").JSX.ElementVideoSkin, const videoFeatures: VideoFeaturesvideoFeatures } from "@videojs/react/video";
import { function SceneMedia(props: SceneMediaProps): JSX.ElementA Video.js media component that plays a compiled scene. Put it where
`<Video src>` would go; the player's controls, skin and hotkeys drive it.
```tsx
<Player>
<VideoSkin>
<SceneMedia compiled={compiled} width={1280} height={720}>
{({ frame }) => <SceneRenderer compiled={compiled} frame={frame} width={1280} height={720} />}
</SceneMedia>
</VideoSkin>
</Player>
```
A recompiled scene of another length or rate replaces the media: the player
keeps the frame on screen (clamped to the new length) and keeps playing.SceneMedia } from "@motionactor/player";
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 { 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 Player: React.FC<PlayerProps<{
title: MediaContentValue;
poster: MediaContentValue;
}>>
Provides a new player store to its descendants without adding a layout element.Player } = function createPlayer(config: CreatePlayerConfig<VideoFeatures>): CreatePlayerResult<VideoPlayerStore> (+2 overloads)Create a player instance with a typed Player component and hooks.createPlayer({ CreatePlayerConfig<VideoFeatures>.features: VideoFeaturesFeatures combined into the player's store, state, actions, and configuration props.features: const videoFeatures: VideoFeaturesvideoFeatures });
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 IntroPlayer(): JSX.ElementIntroPlayer() {
return (
<const Player: React.FC<PlayerProps<{
title: MediaContentValue;
poster: MediaContentValue;
}>>
Provides a new player store to its descendants without adding a layout element.Player>
<function VideoSkin(props: VideoSkinProps): import("react").JSX.ElementVideoSkin>
<function SceneMedia(props: SceneMediaProps): JSX.ElementA Video.js media component that plays a compiled scene. Put it where
`<Video src>` would go; the player's controls, skin and hotkeys drive it.
```tsx
<Player>
<VideoSkin>
<SceneMedia compiled={compiled} width={1280} height={720}>
{({ frame }) => <SceneRenderer compiled={compiled} frame={frame} width={1280} height={720} />}
</SceneMedia>
</VideoSkin>
</Player>
```
A recompiled scene of another length or rate replaces the media: the player
keeps the frame on screen (clamped to the new length) and keeps playing.SceneMedia SceneMediaProps.compiled: Pick<CompiledScene<void | SceneExportShape, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>, "fps" | "totalDuration">The scene's length and rate.compiled={const compiled: CompiledScene<{
heading: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled} SceneMediaProps.width: numberStage size in scene pixels. The stage scales to the player's width.width={1920} SceneMediaProps.height: numberheight={1080} SceneMediaProps.loop?: boolean | undefinedloop>
{({ frame: numberframe }) => (
<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={frame: numberframe} width: numberwidth={1920} height: numberheight={1080} runtime?: ReactRenderRuntime | undefinedruntime={const runtime: ReactRenderRuntimeruntime} />
)}
</function SceneMedia(props: SceneMediaProps): JSX.ElementA Video.js media component that plays a compiled scene. Put it where
`<Video src>` would go; the player's controls, skin and hotkeys drive it.
```tsx
<Player>
<VideoSkin>
<SceneMedia compiled={compiled} width={1280} height={720}>
{({ frame }) => <SceneRenderer compiled={compiled} frame={frame} width={1280} height={720} />}
</SceneMedia>
</VideoSkin>
</Player>
```
A recompiled scene of another length or rate replaces the media: the player
keeps the frame on screen (clamped to the new length) and keeps playing.SceneMedia>
<function Hotkey({ keys, action, value, disabled, target }: HotkeyProps$1): React.ReactNodeHotkey HotkeyProps.keys: stringKey pattern to match, such as `Space`, `ArrowRight`, or `Mod+k`.keys="." HotkeyProps.action: HotkeyActionNamePlayer action to run when the key pattern matches.action="seekStep" HotkeyProps.value?: number | undefinedNumeric value passed to actions such as `seekStep`, `volumeStep`, and `seekToPercent`. Arrow-key seek and volume
actions use their shared input-action step when omitted.value={1 / const compiled: CompiledScene<{
heading: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ heading: Title; }, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>.fps: numberfps} />
<function Hotkey({ keys, action, value, disabled, target }: HotkeyProps$1): React.ReactNodeHotkey HotkeyProps.keys: stringKey pattern to match, such as `Space`, `ArrowRight`, or `Mod+k`.keys="," HotkeyProps.action: HotkeyActionNamePlayer action to run when the key pattern matches.action="seekStep" HotkeyProps.value?: number | undefinedNumeric value passed to actions such as `seekStep`, `volumeStep`, and `seekToPercent`. Arrow-key seek and volume
actions use their shared input-action step when omitted.value={-1 / const compiled: CompiledScene<{
heading: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ heading: Title; }, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>.fps: numberfps} />
</function VideoSkin(props: VideoSkinProps): import("react").JSX.ElementVideoSkin>
</const Player: React.FC<PlayerProps<{
title: MediaContentValue;
poster: MediaContentValue;
}>>
Provides a new player store to its descendants without adding a layout element.Player>
);
}
- Import the skin’s styles once with
import "@videojs/react/video/skin.css". Video.js themes it through CSS custom properties such as--media-accent-colorand--media-border-radius. - The stage is
widthbyheightscene pixels, scaled to the player’s width. Canvas backends receive that scale as the preview scale, so they draw at the density the viewer sees. SceneMediaprovides the frame source and gate. A hold on the gate, from a font, an image or auseHold, keeps the frame on screen and puts the player in its waiting state until the hold is released.- A seek lands on a whole frame. The player’s time is rounded to the nearest frame, so a
seekStepof1 / fpsmoves exactly one frame. - The scene is silent.
SceneMediareports no volume, so the skin leaves out its mute button. A scene withVideoorAudioactors plays on the native preview.
Reading the frame beside the player
useSceneFrame returns the frame on screen and a seek by frame. It works anywhere under the
Video.js Player, including beside the skin, so a readout or a timeline can sit under the video.
import { function useSceneFrame(): {
frame: number;
seek(frame: number): void;
}
The frame on screen in the nearest player's scene, and a seek by frame.
Works anywhere under the Video.js `Player`, beside the skin as well as inside it.useSceneFrame } from "@motionactor/player";
export function function FrameReadout(): JSX.ElementFrameReadout() {
const { const frame: numberframe, const seek: (frame: number) => voidseek } = function useSceneFrame(): {
frame: number;
seek(frame: number): void;
}
The frame on screen in the nearest player's scene, and a seek by frame.
Works anywhere under the Video.js `Player`, beside the skin as well as inside it.useSceneFrame();
return <React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const seek: (frame: number) => voidseek(0)}>frame {const frame: numberframe}, back to the start</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>;
}