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 | ✅ (embed) | Fast |
H264MP4 |
mp4 |
✅ (embed) | Medium | |
FFmpeg |
mp4 / webm
|
SharedArrayBuffer | ✅ (need binary path) | 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
- canvas-record
-
Re-export Recorder, RecorderStatus, all Encoders and utils.
Classes
- Recorder
- Encoder
- FFmpegEncoder
- FrameEncoder
- GIFEncoder
- H264MP4Encoder
- MP4WasmEncoder
- MediaCaptureEncoder
- WebCodecsEncoder
Constants
-
isWebCodecsSupported :
boolean
-
Check for WebCodecs support on the current platform.
Functions
-
estimateBitRate(width, height, frameRate, motionRank, bitrateMode) ⇒
number
-
Estimate the bit rate of a video rounded to nearest megabit. Based on "H.264 for the rest of us" by Kush Amerasinghe.
Typedefs
-
onStatusChangeCb :
function
-
A callback to notify on the status change. To compare with RecorderStatus enum values.
-
RecorderOptions :
object
-
Options for recording. All optional.
-
RecorderStartOptions :
object
-
Options for recording initialisation. All optional.
-
EncoderExtensions :
"mp4"
|"webm"
|"png"
|"jpg"
|"gif"
|"mkv"
-
EncoderTarget :
"in-browser"
|"file-system"
-
FFmpegEncoderOptions :
object
-
FFmpegEncoderEncoderOptions :
module:@ffmpeg/ffmpeg/dist/esm/types.js~FFMessageLoadConfig
-
GIFEncoderOptions :
object
-
GIFEncoderQuantizeOptions :
object
-
GIFEncoderEncoderOptions :
object
-
H264MP4EncoderOptions :
object
-
H264MP4EncoderEncoderOptions :
module:h264-mp4-encoder~H264MP4Encoder
-
MP4WasmEncoderOptions :
object
-
MP4WasmEncoderEncoderOptions :
VideoEncoderConfig
-
MediaCaptureEncoderOptions :
object
-
MediaCaptureEncoderEncoderOptions :
MediaRecorderOptions
-
WebCodecsEncoderOptions :
object
-
WebCodecsEncoderEncoderOptions :
VideoEncoderConfig
-
WebCodecsMuxerOptions :
MuxerOptions
canvas-record
Re-export Recorder, RecorderStatus, all Encoders and utils.
Recorder
Kind: global class
-
Recorder
- new Recorder(context, [options])
-
.defaultOptions :
RecorderOptions
-
.mimeTypes :
object
- .start([startOptions])
- .step()
-
.stop() ⇒
ArrayBuffer
|Uint8Array
|Array.<Blob>
|undefined
- .dispose()
new Recorder(context, [options])
Create a Recorder instance
Param | Type | Default |
---|---|---|
context | RenderingContext |
|
[options] | RecorderOptions |
{} |
RecorderOptions
recorder.defaultOptions : Sensible defaults for recording so that the recorder "just works".
Kind: instance property of Recorder
object
recorder.mimeTypes : A mapping of extension to their mime types
Kind: instance property of Recorder
recorder.start([startOptions])
Start the recording by initializing and optionally calling the initial step.
Kind: instance method of Recorder
Param | Type | Default |
---|---|---|
[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 the recorder and encoder
Kind: instance method of Recorder
Encoder
Kind: global class Properties
Name | Type |
---|---|
target | EncoderTarget |
extension | EncoderExtensions |
[encoderOptions] | object |
[muxerOptions] | object |
-
Encoder
- new Encoder(options)
-
.supportedExtensions :
Array.<Extensions>
-
.supportedTargets :
Array.<EncoderTarget>
- .init(options)
- .encode(frame, [frameNumber])
-
.stop() ⇒
ArrayBuffer
|Uint8Array
|Array.<Blob>
|undefined
- .dispose()
new Encoder(options)
Base Encoder class. All Encoders extend it and its method are called by the Recorder.
Param | Type |
---|---|
options | object |
Array.<Extensions>
encoder.supportedExtensions : The extension the encoder supports
Kind: instance property of Encoder
Array.<EncoderTarget>
encoder.supportedTargets : The target to download the file to.
Kind: instance property of Encoder
encoder.init(options)
Setup the encoder: load binary, instantiate muxers, setup file system target...
Kind: instance method of Encoder
Param | Type |
---|---|
options | object |
encoder.encode(frame, [frameNumber])
Encode a single frame. The frameNumber is usually used for GOP (Group Of Pictures).
Kind: instance method of Encoder
Param | Type |
---|---|
frame | number |
[frameNumber] | number |
ArrayBuffer
| Uint8Array
| Array.<Blob>
| undefined
encoder.stop() ⇒ Stop the encoding process and cleanup the temporary data.
Kind: instance method of Encoder
encoder.dispose()
Clean up the encoder
Kind: instance method of Encoder
FFmpegEncoder
new FFmpegEncoder([options])
Param | Type |
---|---|
[options] | FFmpegEncoderOptions |
FrameEncoder
GIFEncoder
new GIFEncoder([options])
Param | Type |
---|---|
[options] | GIFEncoderOptions |
H264MP4Encoder
new H264MP4Encoder([options])
Param | Type |
---|---|
[options] | H264MP4EncoderOptions |
MP4WasmEncoder
new MP4WasmEncoder([options])
Param | Type |
---|---|
[options] | MP4WasmEncoderOptions |
MediaCaptureEncoder
new MediaCaptureEncoder([options])
Param | Type |
---|---|
[options] | MediaCaptureEncoderOptions |
WebCodecsEncoder
new WebCodecsEncoder([options])
Param | Type |
---|---|
[options] | WebCodecsEncoderOptions |
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());
}
boolean
isWebCodecsSupported : Check for WebCodecs support on the current platform.
number
estimateBitRate(width, height, frameRate, motionRank, bitrateMode) ⇒ Estimate the bit rate of a video rounded to nearest megabit. Based on "H.264 for the rest of us" by Kush Amerasinghe.
Kind: global function
Returns: number
- A bitrate value in bits per second
Param | Type | Default | Description |
---|---|---|---|
width | number |
||
height | number |
||
frameRate | number |
30 |
|
motionRank | number |
4 |
A factor of 1, 2 or 4 |
bitrateMode |
"variable" | "constant"
|
variable |
Example
// Full HD (1080p)
const bitRate = estimateBitRate(1920, 1080, 30, "variable");
const bitRateMbps = bitRate * 1_000_000; // => 13 Mbps
function
onStatusChangeCb : A callback to notify on the status change. To compare with RecorderStatus enum values.
Kind: global typedef
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] | string |
""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 initialisation. 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(). |
"mp4"
| "webm"
| "png"
| "jpg"
| "gif"
| "mkv"
EncoderExtensions :
"in-browser"
| "file-system"
EncoderTarget :
object
FFmpegEncoderOptions : Kind: global typedef Properties
Name | Type | Default |
---|---|---|
[encoderOptions] | FFmpegEncoderEncoderOptions |
{} |
module:@ffmpeg/ffmpeg/dist/esm/types.js~FFMessageLoadConfig
FFmpegEncoderEncoderOptions : Kind: global typedef See: FFmpeg#load
object
GIFEncoderOptions : Kind: global typedef Properties
Name | Type | Default |
---|---|---|
[maxColors] | number |
256 |
[quantizeOptions] | GIFEncoderQuantizeOptions |
|
[encoderOptions] | GIFEncoderEncoderOptions |
{} |
object
GIFEncoderQuantizeOptions : Kind: global typedef See: QuantizeOptions Properties
Name | Type | Default |
---|---|---|
[format] |
"rgb565" | "rgb444" | "rgba4444"
|
"rgb565" |
[oneBitAlpha] |
boolean | number
|
false |
[clearAlpha] | boolean |
true |
[clearAlphaThreshold] | number |
0 |
[clearAlphaColor] | number |
0x00 |
object
GIFEncoderEncoderOptions : Kind: global typedef See: WriteFrameOpts Properties
Name | Type | Default |
---|---|---|
[palette] | Array.<Array.<number>> |
|
[first] | boolean |
false |
[transparent] | boolean |
0 |
[transparentIndex] | number |
0 |
[delay] | number |
0 |
[repeat] | number |
0 |
[dispose] | number |
-1 |
object
H264MP4EncoderOptions : Kind: global typedef Properties
Name | Type | Default |
---|---|---|
[debug] | boolean |
|
[encoderOptions] | H264MP4EncoderEncoderOptions |
{} |
module:h264-mp4-encoder~H264MP4Encoder
H264MP4EncoderEncoderOptions : Kind: global typedef See: h264-mp4-encoder#api
object
MP4WasmEncoderOptions : Kind: global typedef Properties
Name | Type | Default |
---|---|---|
[groupOfPictures] | number |
20 |
[flushFrequency] | number |
10 |
[encoderOptions] | MP4WasmEncoderEncoderOptions |
{} |
VideoEncoderConfig
MP4WasmEncoderEncoderOptions : Kind: global typedef See: VideoEncoder.configure
object
MediaCaptureEncoderOptions : Kind: global typedef Properties
Name | Type | Default |
---|---|---|
[flushFrequency] | number |
10 |
[encoderOptions] | MediaCaptureEncoderEncoderOptions |
{} |
MediaRecorderOptions
MediaCaptureEncoderEncoderOptions : Kind: global typedef See: MediaRecorder#options
object
WebCodecsEncoderOptions : Kind: global typedef Properties
Name | Type | Default |
---|---|---|
[groupOfPictures] | number |
20 |
[flushFrequency] | number |
10 |
[encoderOptions] | WebCodecsEncoderEncoderOptions |
{} |
VideoEncoderConfig
WebCodecsEncoderEncoderOptions : Kind: global typedef See: VideoEncoder.configure
MuxerOptions
WebCodecsMuxerOptions : Kind: global typedef See
License
All MIT:
MIT. See license file.