canvas-record
Record a video in the browser or directly on the File System from a canvas (2D/WebGL/WebGPU) as MP4, WebM, MKV, GIF, PNG/JPG Sequence using WebCodecs and Wasm when available.
Installation
npm install canvas-record
Usage
import { Recorder, RecorderStatus, Encoders } from "canvas-record";
import createCanvasContext from "canvas-context";
import { AVC } from "media-codecs";
// Setup
const pixelRatio = devicePixelRatio;
const width = 512;
const height = 512;
const { context, canvas } = createCanvasContext("2d", {
width: width * pixelRatio,
height: height * pixelRatio,
contextAttributes: { willReadFrequently: true },
});
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` });
const mainElement = document.querySelector("main");
mainElement.appendChild(canvas);
// Animation
let canvasRecorder;
function render() {
const width = canvas.width;
const height = canvas.height;
const t = canvasRecorder.frame / canvasRecorder.frameTotal || Number.EPSILON;
context.clearRect(0, 0, width, height);
context.fillStyle = "red";
context.fillRect(0, 0, t * width, height);
}
const tick = async () => {
render();
if (canvasRecorder.status !== RecorderStatus.Recording) return;
await canvasRecorder.step();
if (canvasRecorder.status !== RecorderStatus.Stopped) {
requestAnimationFrame(() => tick());
}
};
canvasRecorder = new Recorder(context, {
name: "canvas-record-example",
encoderOptions: {
codec: AVC.getCodec({ profile: "Main", level: "5.2" }),
},
});
// Start and encode frame 0
await canvasRecorder.start();
// Animate to encode the rest
tick(canvasRecorder);
API
Encoder comparison:
Encoder | Extension | Required Web API | WASM | Speed |
---|---|---|---|---|
WebCodecs |
mp4 / webm / mkv |
WebCodecs | Fast | |
MP4Wasm |
mp4 |
WebCodecs | Fast | |
H264MP4 |
mp4 |
Medium | ||
FFmpeg |
mp4 / webm |
SharedArrayBuffer | Slow | |
GIF |
gif |
WebWorkers (wip) | Fast | |
Frame |
png / jpg |
File System Access | Fast | |
MediaCapture |
mkv / webm |
MediaStream | Realtime |
Note:
WebCodecs
encoderOptions allow different codecs to be used: VP8/VP9/AV1/HEVC. See media-codecs to get a codec string from human readable options and check which ones are supported in your browser with github.io/media-codecs.WebCodecs
5-10x faster than H264MP4Encoder and 20x faster thanFFmpeg
(it needs to mux files after writing png to virtual FS)FFmpeg
(mp4 and webm) andWebCodecs
(mp4) have a AVC maximum frame size of 9437184 pixels. That's fine until a bit more than 4K 16:9 @ 30fps. So if you need 4K Square or 8K exports, be patient withH264MP4Encoder
(which probably also has the 4GB memory limit) or use Frame encoder and mux them manually withFFmpeg
CLI (ffmpeg -framerate 30 -i "%05d.jpg" -b:v 60M -r 30 -profile:v baseline -pix_fmt yuv420p -movflags +faststart output.mp4
)MP4Wasm
is embedded from mp4-wasm for ease of use (FFmpeg
will requireencoderOptions.corePath
)
Roadmap:
- add debug logging
- use WebWorkers for gifenc
Modules
- index
Re-export Recorder, RecorderStatus, all Encoders and utils.
Classes
- Recorder
Base Recorder class.
Functions
- onStatusChangeCb(RecorderStatus)
A callback to notify on the status change. To compare with RecorderStatus enum values.
Typedefs
- RecorderOptions :
object
Options for recording. All optional.
- RecorderStartOptions :
object
Options for recording. All optional.
index
Re-export Recorder, RecorderStatus, all Encoders and utils.
Recorder
Base Recorder class.
Kind: global class Properties
Name | Type | Default | Description |
---|---|---|---|
[enabled] | boolean |
true |
Enable/disable pointer interaction and drawing. |
- Recorder
- new Recorder(context, options)
- .start(startOptions)
- .step()
- .stop() β
ArrayBuffer
|Uint8Array
|Array.<Blob>
|undefined
- .dispose()
new Recorder(context, options)
Param | Type |
---|---|
context | RenderingContext |
options | RecorderOptions |
recorder.start(startOptions)
Start the recording by initializing and optionally calling the initial step.
Kind: instance method of Recorder
Param | Type |
---|---|
startOptions | RecorderStartOptions |
recorder.step()
Encode a frame and increment the time and the playhead.
Calls await canvasRecorder.stop()
when duration is reached.
Kind: instance method of Recorder
ArrayBuffer
| Uint8Array
| Array.<Blob>
| undefined
recorder.stop() β Stop the recording and return the recorded buffer. If options.download is set, automatically start downloading the resulting file. Is called when duration is reached or manually.
Kind: instance method of Recorder
recorder.dispose()
Clean up
Kind: instance method of Recorder
enum
RecorderStatus : Enum for recorder status
Kind: global enum Read only: true Example
// Check recorder status before continuing
if (canvasRecorder.status !== RecorderStatus.Stopped) {
rAFId = requestAnimationFrame(() => tick());
}
onStatusChangeCb(RecorderStatus)
A callback to notify on the status change. To compare with RecorderStatus enum values.
Kind: global function
Param | Type | Description |
---|---|---|
RecorderStatus | number |
the status |
object
RecorderOptions : Options for recording. All optional.
Kind: global typedef Properties
Name | Type | Default | Description |
---|---|---|---|
[name] | string |
"""" |
A name for the recorder, used as prefix for the default file name. |
[duration] | number |
10 |
The recording duration in seconds. If set to Infinity, await canvasRecorder.stop() needs to be called manually. |
[frameRate] | number |
30 |
The frame rate in frame per seconds. Use await canvasRecorder.step(); to go to the next frame. |
[download] | boolean |
true |
Automatically download the recording when duration is reached or when await canvasRecorder.stop() is manually called. |
[extension] | boolean |
"mp4" |
Default file extension: infers which Encoder is selected. |
[target] | string |
""in-browser"" |
Default writing target: in-browser or file-system when available. |
[encoder] | object |
A specific encoder. Default encoder based on options.extension: GIF > WebCodecs > H264MP4. | |
[encoderOptions] | object |
See src/encoders or individual packages for a list of options. |
|
[muxerOptions] | object |
See "mp4-muxer" and "webm-muxer" for a list of options. | |
[onStatusChange] | onStatusChangeCb |
object
RecorderStartOptions : Options for recording. All optional.
Kind: global typedef Properties
Name | Type | Description |
---|---|---|
[filename] | string |
Overwrite the file name completely. |
[initOnly] | boolean |
Only initialised the recorder and don't call the first await recorder.step(). |
License
All MIT:
MIT. See license file.