Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/dmnsgn/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.
https://github.com/dmnsgn/canvas-record
canvas capture download filesystem gif mp4 record video webcodecs
Last synced: 4 days ago
JSON representation
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.
- Host: GitHub
- URL: https://github.com/dmnsgn/canvas-record
- Owner: dmnsgn
- License: mit
- Created: 2019-06-18T10:39:52.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2024-07-07T13:48:13.000Z (5 months ago)
- Last Synced: 2024-12-01T12:16:02.473Z (11 days ago)
- Topics: canvas, capture, download, filesystem, gif, mp4, record, video, webcodecs
- Language: JavaScript
- Homepage: https://dmnsgn.github.io/canvas-record/
- Size: 17.3 MB
- Stars: 328
- Watchers: 7
- Forks: 18
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
- awesome-github-star - canvas-record
README
# canvas-record
[![npm version](https://img.shields.io/npm/v/canvas-record)](https://www.npmjs.com/package/canvas-record)
[![stability-stable](https://img.shields.io/badge/stability-stable-green.svg)](https://www.npmjs.com/package/canvas-record)
[![npm minzipped size](https://img.shields.io/bundlephobia/minzip/canvas-record)](https://bundlephobia.com/package/canvas-record)
[![dependencies](https://img.shields.io/librariesio/release/npm/canvas-record)](https://github.com/dmnsgn/canvas-record/blob/main/package.json)
[![types](https://img.shields.io/npm/types/canvas-record)](https://github.com/microsoft/TypeScript)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-fa6673.svg)](https://conventionalcommits.org)
[![styled with prettier](https://img.shields.io/badge/styled_with-Prettier-f8bc45.svg?logo=prettier)](https://github.com/prettier/prettier)
[![linted with eslint](https://img.shields.io/badge/linted_with-ES_Lint-4B32C3.svg?logo=eslint)](https://github.com/eslint/eslint)
[![license](https://img.shields.io/github/license/dmnsgn/canvas-record)](https://github.com/dmnsgn/canvas-record/blob/main/LICENSE.md)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.
[![paypal](https://img.shields.io/badge/donate-paypal-informational?logo=paypal)](https://paypal.me/dmnsgn)
[![coinbase](https://img.shields.io/badge/donate-coinbase-informational?logo=coinbase)](https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3)
[![twitter](https://img.shields.io/twitter/follow/dmnsgn?style=social)](https://twitter.com/dmnsgn)![](https://raw.githubusercontent.com/dmnsgn/canvas-record/main/screenshot.gif)
## Installation
```bash
npm install canvas-record
```## Usage
```js
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](https://github.com/dmnsgn/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](https://dmnsgn.github.io/media-codecs/).
- `WebCodecs` 5-10x faster than H264MP4Encoder and 20x faster than `FFmpeg` (it needs to mux files after writing png to virtual FS)
- `FFmpeg` (mp4 and webm) and `WebCodecs` (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 with `H264MP4Encoder` (which probably also has the 4GB memory limit) or use Frame encoder and mux them manually with `FFmpeg` 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](https://github.com/mattdesl/mp4-wasm/) for ease of use (`FFmpeg` will require `encoderOptions.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
- MediaCaptureEncoder
- MP4WasmEncoder
- 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
-
MediaCaptureEncoderOptions :object
-
MediaCaptureEncoderEncoderOptions :MediaRecorderOptions
-
MP4WasmEncoderOptions :object
-
MP4WasmEncoderEncoderOptions :VideoEncoderConfig
-
WebCodecsEncoderOptions :object
-
WebCodecsEncoderEncoderOptions :VideoEncoderConfig
-
WebCodecsMuxerOptions :MuxerOptions
## canvas-record
Re-export Recorder, RecorderStatus, all Encoders and utils.
## Recorder
**Kind**: global class
- [Recorder](#Recorder)
- [new Recorder(context, [options])](#new_Recorder_new)
- [.defaultOptions](#Recorder+defaultOptions) : [RecorderOptions
](#RecorderOptions)
- [.mimeTypes](#Recorder+mimeTypes) : object
- [.start([startOptions])](#Recorder+start)
- [.step()](#Recorder+step)
- [.stop()](#Recorder+stop) ⇒ ArrayBuffer
\| Uint8Array
\| Array.<Blob>
\| undefined
- [.dispose()](#Recorder+dispose)
### new Recorder(context, [options])
Create a Recorder instance
| Param | Type | Default |
| --------- | ------------------------------------------------ | --------------- |
| context | RenderingContext
| |
| [options] | [RecorderOptions
](#RecorderOptions) | {}
|
### recorder.defaultOptions : [RecorderOptions
](#RecorderOptions)
Sensible defaults for recording so that the recorder "just works".
**Kind**: instance property of [Recorder
](#Recorder)
### recorder.mimeTypes : object
A mapping of extension to their mime types
**Kind**: instance property of [Recorder
](#Recorder)
### recorder.start([startOptions])
Start the recording by initializing and optionally calling the initial step.
**Kind**: instance method of [Recorder
](#Recorder)
| Param | Type | Default |
| -------------- | ---------------------------------------------------------- | --------------- |
| [startOptions] | [RecorderStartOptions
](#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
](#Recorder)
### recorder.stop() ⇒ ArrayBuffer
\| Uint8Array
\| Array.<Blob>
\| undefined
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)
### recorder.dispose()
Clean up the recorder and encoder
**Kind**: instance method of [Recorder
](#Recorder)
## Encoder
**Kind**: global class
**Properties**
| Name | Type |
| ---------------- | ---------------------------------------------------- |
| target | [EncoderTarget
](#EncoderTarget) |
| extension | [EncoderExtensions
](#EncoderExtensions) |
| [encoderOptions] | object
|
| [muxerOptions] | object
|
- [Encoder](#Encoder)
- [new Encoder(options)](#new_Encoder_new)
- [.supportedExtensions](#Encoder+supportedExtensions) : Array.<Extensions>
- [.supportedTargets](#Encoder+supportedTargets) : [Array.<EncoderTarget>
](#EncoderTarget)
- [.init(options)](#Encoder+init)
- [.encode(frame, [frameNumber])](#Encoder+encode)
- [.stop()](#Encoder+stop) ⇒ ArrayBuffer
\| Uint8Array
\| Array.<Blob>
\| undefined
- [.dispose()](#Encoder+dispose)
### new Encoder(options)
Base Encoder class. All Encoders extend it and its method are called by the Recorder.
| Param | Type |
| ------- | ------------------- |
| options | object
|
### encoder.supportedExtensions : Array.<Extensions>
The extension the encoder supports
**Kind**: instance property of [Encoder
](#Encoder)
### encoder.supportedTargets : [Array.<EncoderTarget>
](#EncoderTarget)
The target to download the file to.
**Kind**: instance property of [Encoder
](#Encoder)
### encoder.init(options)
Setup the encoder: load binary, instantiate muxers, setup file system target...
**Kind**: instance method of [Encoder
](#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
](#Encoder)
| Param | Type |
| ------------- | ------------------- |
| frame | number
|
| [frameNumber] | number
|
### encoder.stop() ⇒ ArrayBuffer
\| Uint8Array
\| Array.<Blob>
\| undefined
Stop the encoding process and cleanup the temporary data.
**Kind**: instance method of [Encoder
](#Encoder)
### encoder.dispose()
Clean up the encoder
**Kind**: instance method of [Encoder
](#Encoder)
## FFmpegEncoder
### new FFmpegEncoder([options])
| Param | Type |
| --------- | ---------------------------------------------------------- |
| [options] | [FFmpegEncoderOptions
](#FFmpegEncoderOptions) |
## FrameEncoder
## GIFEncoder
### new GIFEncoder([options])
| Param | Type |
| --------- | ---------------------------------------------------- |
| [options] | [GIFEncoderOptions
](#GIFEncoderOptions) |
## H264MP4Encoder
### new H264MP4Encoder([options])
| Param | Type |
| --------- | ------------------------------------------------------------ |
| [options] | [H264MP4EncoderOptions
](#H264MP4EncoderOptions) |
## MediaCaptureEncoder
### new MediaCaptureEncoder([options])
| Param | Type |
| --------- | ---------------------------------------------------------------------- |
| [options] | [MediaCaptureEncoderOptions
](#MediaCaptureEncoderOptions) |
## MP4WasmEncoder
### new MP4WasmEncoder([options])
| Param | Type |
| --------- | ------------------------------------------------------------ |
| [options] | [MP4WasmEncoderOptions
](#MP4WasmEncoderOptions) |
## WebCodecsEncoder
### new WebCodecsEncoder([options])
| Param | Type |
| --------- | ---------------------------------------------------------------- |
| [options] | [WebCodecsEncoderOptions
](#WebCodecsEncoderOptions) |
## RecorderStatus : enum
Enum for recorder status
**Kind**: global enum
**Read only**: true
**Example**
```js
// Check recorder status before continuing
if (canvasRecorder.status !== RecorderStatus.Stopped) {
rAFId = requestAnimationFrame(() => tick());
}
```
## isWebCodecsSupported : boolean
Check for WebCodecs support on the current platform.
## 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.
**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**
```js
// Full HD (1080p)
const bitRate = estimateBitRate(1920, 1080, 30, "variable");
const bitRateMbps = bitRate * 1_000_000; // => 13 Mbps
```
## onStatusChangeCb : function
A callback to notify on the status change. To compare with RecorderStatus enum values.
**Kind**: global typedef
| Param | Type | Description |
| -------------- | ------------------- | ----------- |
| RecorderStatus | number
| the status |
## RecorderOptions : object
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
](#onStatusChangeCb) | | |
## RecorderStartOptions : object
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(). |
## EncoderExtensions : "mp4"
\| "webm"
\| "png"
\| "jpg"
\| "gif"
\| "mkv"
## EncoderTarget : "in-browser"
\| "file-system"
## FFmpegEncoderOptions : object
**Kind**: global typedef
**Properties**
| Name | Type | Default |
| ---------------- | ------------------------------------------------------------------------ | --------------- |
| [encoderOptions] | [FFmpegEncoderEncoderOptions
](#FFmpegEncoderEncoderOptions) | {}
|
## FFmpegEncoderEncoderOptions : module:@ffmpeg/ffmpeg/dist/esm/types.js~FFMessageLoadConfig
**Kind**: global typedef
**See**: [FFmpeg#load](https://ffmpegwasm.netlify.app/docs/api/ffmpeg/classes/FFmpeg#load)
## GIFEncoderOptions : object
**Kind**: global typedef
**Properties**
| Name | Type | Default |
| ----------------- | -------------------------------------------------------------------- | ---------------- |
| [maxColors] | number
| 256
|
| [quantizeOptions] | [GIFEncoderQuantizeOptions
](#GIFEncoderQuantizeOptions) | |
| [encoderOptions] | [GIFEncoderEncoderOptions
](#GIFEncoderEncoderOptions) | {}
|
## GIFEncoderQuantizeOptions : object
**Kind**: global typedef
**See**: [QuantizeOptions](https://github.com/mattdesl/gifenc#palette--quantizergba-maxcolors-options--)
**Properties**
| Name | Type | Default |
| --------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------- |
| [format] | "rgb565"
\| "rgb444"
\| "rgba4444"
| "rgb565"
|
| [oneBitAlpha] | boolean
\| number
| false
|
| [clearAlpha] | boolean
| true
|
| [clearAlphaThreshold] | number
| 0
|
| [clearAlphaColor] | number
| 0x00
|
## GIFEncoderEncoderOptions : object
**Kind**: global typedef
**See**: [WriteFrameOpts](https://github.com/mattdesl/gifenc#gifwriteframeindex-width-height-opts--)
**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
|
## H264MP4EncoderOptions : object
**Kind**: global typedef
**Properties**
| Name | Type | Default |
| ---------------- | -------------------------------------------------------------------------- | --------------- |
| [debug] | boolean
| |
| [encoderOptions] | [H264MP4EncoderEncoderOptions
](#H264MP4EncoderEncoderOptions) | {}
|
## H264MP4EncoderEncoderOptions : module:h264-mp4-encoder~H264MP4Encoder
**Kind**: global typedef
**See**: [h264-mp4-encoder#api](https://github.com/TrevorSundberg/h264-mp4-encoder#api)
## MediaCaptureEncoderOptions : object
**Kind**: global typedef
**Properties**
| Name | Type | Default |
| ---------------- | ------------------------------------------------------------------------------------ | --------------- |
| [flushFrequency] | number
| 10
|
| [encoderOptions] | [MediaCaptureEncoderEncoderOptions
](#MediaCaptureEncoderEncoderOptions) | {}
|
## MediaCaptureEncoderEncoderOptions : MediaRecorderOptions
**Kind**: global typedef
**See**: [MediaRecorder#options](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#options)
## MP4WasmEncoderOptions : object
**Kind**: global typedef
**Properties**
| Name | Type | Default |
| ----------------- | -------------------------------------------------------------------------- | --------------- |
| [groupOfPictures] | number
| 20
|
| [flushFrequency] | number
| 10
|
| [encoderOptions] | [MP4WasmEncoderEncoderOptions
](#MP4WasmEncoderEncoderOptions) | {}
|
## MP4WasmEncoderEncoderOptions : VideoEncoderConfig
**Kind**: global typedef
**See**: [VideoEncoder.configure](https://developer.mozilla.org/en-US/docs/Web/API/VideoEncoder/configure#config)
## WebCodecsEncoderOptions : object
**Kind**: global typedef
**Properties**
| Name | Type | Default |
| ----------------- | ------------------------------------------------------------------------------ | --------------- |
| [groupOfPictures] | number
| 20
|
| [flushFrequency] | number
| 10
|
| [encoderOptions] | [WebCodecsEncoderEncoderOptions
](#WebCodecsEncoderEncoderOptions) | {}
|
## WebCodecsEncoderEncoderOptions : VideoEncoderConfig
**Kind**: global typedef
**See**: [VideoEncoder.configure](https://developer.mozilla.org/en-US/docs/Web/API/VideoEncoder/configure#config)
## WebCodecsMuxerOptions : MuxerOptions
**Kind**: global typedef
**See**
- [Mp4.MuxerOptions](https://github.com/Vanilagy/mp4-muxer/#usage)
- [WebM.MuxerOptions](https://github.com/Vanilagy/webm-muxer/#usage)
## License
All MIT:
- [mp4-wasm](https://github.com/mattdesl/mp4-wasm/blob/master/LICENSE.md)
- [h264-mp4-encoder](https://github.com/TrevorSundberg/h264-mp4-encoder/blob/master/LICENSE.md)
- [@ffmpeg/ffmpeg](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/master/LICENSE)
- [gifenc](https://github.com/mattdesl/gifenc/blob/master/LICENSE.md)
- [webm-muxer](https://github.com/Vanilagy/webm-muxer/blob/main/LICENSE)
- [mp4-muxer](https://github.com/Vanilagy/mp4-muxer/blob/main/LICENSE)
MIT. See [license file](https://github.com/dmnsgn/canvas-record/blob/main/LICENSE.md).