Skip to content

React

@alpha2-sh/react puts the browser player on a canvas in a React tree. After reading this page you can show a project at any time from React state, recover from a lost GPU without remaking the player, and do the same wiring without React.

The package draws nothing itself. You make an Alpha2Player (from alpha2/player); the component gives it a canvas and presents the time you pass.

Terminal window
npm install @alpha2-sh/react alpha2
  1. Write a render worker that runs the engine, as in Preview in a worker.
  2. Write a createPlayer callback that makes an Alpha2Player over a WorkerRenderBackend, and pass it the replaceSurface argument the component gives you.
  3. Render <Alpha2Canvas> with the project’s size and the time to show, in ticks.
import { useCallback, useState } from "react";
import { Alpha2Canvas } from "@alpha2-sh/react";
import { Alpha2Player } from "alpha2/player";
import { WorkerRenderBackend } from "alpha2/worker";
function Preview({ workerUrl, refill }) {
const [time, setTime] = useState(0n);
// A new function makes a new player: keep it stable.
const createPlayer = useCallback(
(replaceSurface) =>
new Alpha2Player(new WorkerRenderBackend(new Worker(workerUrl, { type: "module" })), {
onRefill: refill,
replaceSurface,
}),
[workerUrl, refill],
);
return (
<>
<Alpha2Canvas
createPlayer={createPlayer}
timeTicks={time}
width={1280}
height={720}
onError={(error) => console.error(error)}
/>
<input
type="range"
min={0}
max={120_000 * 10}
onChange={(event) => setTime(BigInt(event.target.value))}
/>
</>
);
}

refill receives the DecoderRefillRequests for the frames a presentation lacks. It decodes them and uploads each with player.uploadVideoFrame; see Decode with WebCodecs.

Prop Type
createPlayer (replaceSurface) => player Makes the player for a canvas. Called each time the component makes a canvas. Keep it stable with useCallback.
timeTicks bigint The timeline time to show, in ticks.
width, height number The project’s size: what the player draws at, and the element’s aspect ratio.
className, style Styles the element.
onReady () => void Called once the player has its canvas.
onPresented (frame) => void Called with each presentation that was not superseded.
onError (error) => void Called when initialization or a presentation fails. Without it, the error goes to the page’s reportError.
  • The element keeps the project’s aspect ratio and fills its container’s width.
  • It presents timeTicks whenever it changes. Times given while a presentation runs coalesce into one presentation of the latest.
  • While the page is hidden it presents nothing. When the page is visible again, it shows the latest time.
  • A player belongs to one canvas. Each mount, each new createPlayer and each new size makes a new canvas and a new player, and the old player is disposed when its canvas goes. React may set the component up more than once (<StrictMode>, <Activity>); each time gets its own canvas and player.

When the GPU is lost, Alpha2Player recovers on the next presentation. Some recoveries need a fresh canvas: a lost WebGL2 context, or the CPU renderer after the browser withdrew GPU access. The replaceSurface function the component passes to createPlayer handles this. It swaps a new canvas element in for the old one, and the same player draws on it.

bindCanvas(player, canvas, options?) is the same wiring for any framework. It transfers the canvas to the player and presents the times you show. It neither makes nor disposes the player.

import { bindCanvas } from "@alpha2-sh/react";
import { Alpha2Player } from "alpha2/player";
import { WorkerRenderBackend } from "alpha2/worker";
const canvas = document.querySelector("canvas");
canvas.width = 1280; // the drawing-buffer size cannot change after transfer
canvas.height = 720;
let binding;
let current = canvas;
const player = new Alpha2Player(new WorkerRenderBackend(worker), {
onRefill: refill,
replaceSurface: () => binding.replaceSurface(),
});
binding = bindCanvas(player, canvas, {
// Put a same-size canvas where the bound one is.
replaceCanvas: async () => {
const fresh = document.createElement("canvas");
fresh.width = current.width;
fresh.height = current.height;
current.replaceWith(fresh);
current = fresh;
return fresh;
},
onPresented: (frame) => console.log(frame.presentedTimeTicks),
onError: (error) => console.error(error),
});
await binding.ready;
binding.show(60_000n);
// Later:
binding.dispose();
player.dispose();
  • binding.ready resolves once the player has its canvas, and rejects if it cannot have it. bindCanvas itself never throws.
  • binding.show(ticks) shows a time now, once initialized, or when the page is next visible. Showing the same time again presents it again.
  • binding.replaceSurface() returns a new canvas from replaceCanvas, already transferred to an OffscreenCanvas, which is what the player’s replaceSurface option needs. Without replaceCanvas it rejects.
  • options.visibility replaces document as the source of the page’s visibility.
  • Browser: the player, the render worker, frame supply and export.
  • Editor: EditorPreview binds an editor session’s preview to a canvas.
  • Project model: what the player renders.