Browser-based MP4 muxer and demuxer using WebCodecs and mp4box.js.
Encode H.264 video and Opus audio from raw VideoFrame and AudioData objects, then assemble them into a valid MP4 file — entirely in the browser with zero server-side dependencies.
- Push-based encoding — feed in
VideoFrames andAudioData, get back a complete MP4 buffer - Pull-based demuxing — random-access seeking and full sequential reads from existing MP4 files
npm install mp4writerimport { MP4PushMuxerEncoder } from "mp4writer";
const muxer = new MP4PushMuxerEncoder();
await muxer.configureEncoders(1920, 1080);
// Push audio
await muxer.processAudioBuffer(audioBuffer);
// Push video frames
for (const frame of videoFrames) {
await muxer.pushVideoFrame(frame);
}
// Produce the final MP4
const mp4 = await muxer.multiplexToBuffer();
muxer.closeEncoders();import { MP4PullDemuxer } from "mp4writer";
const demuxer = new MP4PullDemuxer(mp4Blob);
const config = await demuxer.getVideoDecoderConfig();
// Read all samples sequentially
const samples = await demuxer.readAllSamples();
// Or seek to a specific timestamp
const { samples: seekSamples, ptsIndex } = await demuxer.seek(5000);import { MP4PullDemuxer, MP4PushMuxerEncoder, isConfigured } from "mp4writer";
import { secToUs } from "mp4writer";
const demuxer = new MP4PullDemuxer(inputBlob);
const muxer = new MP4PushMuxerEncoder();
const config = await demuxer.getVideoDecoderConfig();
await muxer.configureEncoders(config.codedWidth!, config.codedHeight!);
await muxer.processAudioBuffer(audioBuffer);
const decoder = new VideoDecoder({
output: async (frame) => {
// Transform the frame (draw on it, filter, etc.)
const canvas = new OffscreenCanvas(frame.displayWidth, frame.displayHeight);
const ctx = canvas.getContext("2d")!;
ctx.drawImage(frame, 0, 0);
frame.close();
const modified = new VideoFrame(canvas, {
timestamp: frame.timestamp,
duration: frame.duration!,
});
await muxer.pushVideoFrame(modified);
},
error: console.error,
});
decoder.configure(config);
await isConfigured(decoder);
for (const sample of await demuxer.readAllSamples()) {
decoder.decode(
new EncodedVideoChunk({
type: sample.is_sync ? "key" : "delta",
timestamp: secToUs(sample.cts / sample.timescale),
duration: secToUs(sample.duration / sample.timescale),
data: sample.data,
}),
);
}
await decoder.flush();
const mp4 = await muxer.multiplexToBuffer();
muxer.closeEncoders();| Method | Description |
|---|---|
configureEncoders(width, height) |
Configure the H.264 video encoder and Opus audio encoder |
pushVideoFrame(frame) |
Encode a VideoFrame (closed automatically after encoding) |
pushAudioData(data) |
Encode an AudioData chunk (closed automatically after encoding) |
processAudioBuffer(buffer) |
Convert an AudioBuffer to AudioData and encode it |
multiplexToBuffer() |
Flush encoders and produce the final MP4 buffer |
closeEncoders() |
Release encoder resources |
checkBrowserSupport() |
Static — check if the browser supports the required APIs |
Constructor accepts an optional MP4PushMuxerEncoderConfig:
| Option | Default | Description |
|---|---|---|
videoBitrate |
2_000_000 |
Video bitrate in bits/s |
videoChunkSec |
~0.033 (30fps) |
Duration of each video chunk in seconds |
keyframeIntevalSec |
2 |
Keyframe interval in seconds |
latencyMode |
"realtime" |
"realtime" or "quality" |
audioBitrate |
64_000 |
Audio bitrate in bits/s |
audioSampleRate |
48_000 |
Audio sample rate in Hz |
audioOutputChannels |
2 |
Number of audio output channels |
| Method | Description |
|---|---|
getVideoDecoderConfig() |
Get a VideoDecoderConfig for the first video track (times out after 10s) |
seek(timestampMs) |
Seek to a timestamp and return a decoder-ready window of samples |
readAllSamples() |
Read all video samples with data, in DTS order |
dtsIndexToPtsIndex(dtsIndex) |
Convert a DTS index to a PTS index |
timestampMsToPtsIndex(timestampMs) |
Find the PTS index for a given timestamp |
Returns a Promise that resolves when a VideoEncoder, AudioEncoder, VideoDecoder, or AudioDecoder reaches the "configured" state. Times out after timeoutMs (default 10s).
Requires the WebCodecs API with H.264 encoding support. Use MP4PushMuxerEncoder.checkBrowserSupport() to verify at runtime.
MIT — Nicholas Waltz