Screen helpers · interface

FrameRecorderOptions

Recording what is on a canvas — or on a prepared export target — to a file.

Explained in Recording and clips.

interface FrameRecorderOptions
import type { FrameRecorderOptions } from '@driftengine/core';

In depth

A convenience on top of the replay, not the feature. The replay itself is what makes a run worth posting; this saves the trouble of reaching for a screen recorder — and where the browser cannot do it, saying so plainly beats producing a file that will not play.

Deliberately not a server-side render. That is the "proper" answer and it is a backend, a queue, a storage bill and a moderation problem, for something the browser already does.

The honest limit, and it belongs here rather than in a commit message: MediaRecorder timestamps every frame by wall clock, and that is measured rather than assumed — a probe recording requested frames at gaps of 8.6 / 24.5 / 8.1 / 28.6 ms and ffprobe read the MP4's own timestamps back as 8.8 / 24.2 / 8.1 / 28.5 ms, avg_frame_rate=7110000/120161. Two things follow.

An export has to run in real time: there is no in-browser way to render a clip slower than real time and have the audio line up, short of shipping our own muxer, which is a dependency and the standing rule says no. So the export can be paced (see ExportTarget) and measured, but it cannot be given more time.

And a constant-rate file is not on offer either. What is on offer is a file whose frames are evenly spaced and hold moments the same distance apart, which is what a viewer reads as smooth — so the schedule captures every n-th animation frame and the content follows the wall clock. A device that cannot hold the target rate then produces a slower clip rather than a juddering one, and the measurement in pacing says which it was.

Properties

NameTypeDescription
canvasreadonlyoptionalHTMLCanvasElementRecord the canvas directly, at fps. Simple and appropriate for a debug capture; for a clip anybody else will see, prefer stream from an ExportTarget, which fixes the aspect, stamps the mark and paces the frames.
streamreadonlyoptionalMediaStreamA prepared video stream, e.g. ExportTarget.acquire(). Wins over canvas.
audioreadonlyoptionalMediaStream | nullMixed into the clip. Omit for a silent recording.
fpsreadonlyoptionalnumberFrames per second wanted — a floor, not a promise.
More

The schedule counts animation frames (see FramePacer), so what comes out is the loop's own rate divided by a whole number. Asking for 60 on a 144 Hz panel records 72; asking for it on a loop that can only deliver 40 records 40.

videoBitsPerSecondreadonlyoptionalnumberBits per second for video. Defaults to a figure scaled from the frame's pixel count and rate — a constant is generous at one size and starved at another, and a starved encoder on high-motion footage looks like stutter.
targetreadonlyoptional{ /** The canvas the scene is drawn into. */ readonly source: HTMLCanvasElement; /** Whoever owns that canvas's drawing-buffer size — normally the renderer. */ readonly surface: LockableSurface; readonly width: number; readonly height: number; readonly overlay?: FrameOverlay | ((width: number, height: number) => FrameOverlay); /** * Drawn live into every composed frame, after the scene and the baked mark. * * For anything that changes frame to frame — a speedometer, a clock — which the * baked `overlay` cannot express at all. See `ExportTargetOptions.drawFrame`. */ readonly drawFrame?: (ctx: CanvasRenderingContext2D, width: number, height: number) => void; /** * What the renderer is currently managing, frames per second. A device below * the target rate records at a smaller frame instead: a slightly softer clip * that holds its rate beats a full-size one that judders. Omit for no * measurement, which is treated as no evidence rather than as bad news. */ readonly measuredFps?: number; /** Forwarded to `ExportTargetOptions.presentedFrames`; see `duplicateFrame` below. */ readonly presentedFrames?: () => number; }Record at a fixed frame size rather than at whatever the canvas happens to be, compositing the scene and an optional mark into it.
More

This is what makes an export a product rather than a screen capture: the frame is the requested shape whatever the window is, the mark is inside the video where a DOM overlay could never be, and frames are handed over one at a time so none is ever recorded twice.