How We Built Browser-Native MP4 Export — No Server Required
PinePaper exports MP4 video entirely in the browser using WebCodecs. Here's how the H.264 pipeline works — from canvas frames to a playable video file, without uploading anything.
The Problem
Traditional web-based animation tools export video by sending frames to a server, encoding them with ffmpeg, and returning the result. This has three costs: upload bandwidth (sending megabytes of frame data), server compute (CPU/GPU time for encoding), and privacy (your creative work transits a third party's infrastructure).
PinePaper does none of this. The MP4 file is created entirely in your browser. No frames leave your device. The encoding happens on your machine's hardware video encoder. This article explains how.
WebCodecs: Hardware Video Encoding in the Browser
The WebCodecs API [W3C, 2021] provides low-level access to the browser's built-in video encoder and decoder. It was shipped in Chrome 94 (September 2021) and is now available in Chrome, Edge, and Opera. Safari and Firefox have partial or experimental support [Can I Use, 2026].
WebCodecs exposes two key interfaces:
VideoEncoder— takes raw video frames, outputs encoded H.264 (or VP8/VP9/AV1) bitstream chunksVideoDecoder— takes encoded chunks, outputs raw frames (used for playback, not export)
For MP4 export, we use VideoEncoder. The workflow:
- Create a
VideoEncoderconfigured for H.264 at the desired resolution and bitrate - For each animation frame: capture the canvas as a
VideoFrame, feed it to the encoder - Collect the encoded chunks (raw H.264 NAL units)
- Mux the chunks into an MP4 container
- Offer the MP4 file for download
Step 1: Encoder Configuration
const encoder = new VideoEncoder({
output: (chunk, metadata) => {
// chunk.data contains encoded H.264 bytes
// metadata.decoderConfig contains SPS/PPS on first chunk
muxer.addVideoChunk(chunk, metadata);
},
error: (e) => console.error('Encoder error:', e)
});
encoder.configure({
codec: 'avc1.42E01E', // H.264 Baseline Profile Level 3.0
width: 1080,
height: 1080,
bitrate: 5_000_000, // 5 Mbps
framerate: 30
});
The codec string avc1.42E01E specifies H.264 Baseline Profile. This is the most widely compatible H.264 profile — it plays on every phone, browser, and social media platform. Higher profiles (Main, High) offer better compression but less compatibility [Richardson, 2010].
The bitrate controls quality vs file size. At 5 Mbps for a 3-second clip: 5,000,000 bits/s × 3s = 15,000,000 bits = ~1.8 MB. Actual size varies based on content complexity — static scenes compress better than rapid motion.
Step 2: Frame Capture and Encoding
For each frame in the animation timeline:
for (let frameNum = 0; frameNum < totalFrames; frameNum++) {
const frameTime = frameNum / fps;
// Advance animation to this time
app.setPlaybackTime(frameTime);
paper.view.draw(); // Full render (not view.update!)
await new Promise(r => requestAnimationFrame(r)); // Wait for paint
// Create VideoFrame from canvas
const frame = new VideoFrame(canvas, {
timestamp: frameNum * (1_000_000 / fps) // microseconds
});
// Encode (keyframe every 2 seconds)
encoder.encode(frame, {
keyFrame: frameNum % (fps * 2) === 0
});
frame.close(); // Release GPU memory
}
The critical detail: after advancing the animation and rendering the canvas, you must wait for requestAnimationFrame before capturing. This ensures the GPU has finished compositing pixels. Without this synchronization, you capture a stale buffer and produce frames that are blank or duplicated.
Step 3: MP4 Muxing
The VideoEncoder outputs raw H.264 chunks (NAL units). These are not a playable file — they need to be wrapped in an MP4 container that stores timing, metadata, and the codec configuration.
PinePaper uses the mp4-muxer library [Vani, mp4-muxer] for this step. The muxer receives encoded chunks with timestamps and produces a valid MP4 file with:
ftypbox: file type declaration (isom, iso2, mp41)moovbox: movie metadata, track descriptions, timing tablesmdatbox: the actual encoded video data
The result is a standards-compliant MP4 that plays in every video player, browser, and social media platform.
Step 4: Download
The completed MP4 is assembled as a Blob, converted to a URL via URL.createObjectURL(), and triggered as a download:
await encoder.flush(); // Wait for all frames to encode
const mp4Blob = muxer.finalize();
const url = URL.createObjectURL(new Blob([mp4Blob], { type: 'video/mp4' }));
const a = document.createElement('a');
a.href = url;
a.download = 'animation.mp4';
a.click();
URL.revokeObjectURL(url);
The entire pipeline — frame capture, H.264 encoding, MP4 muxing, and download — happens in the browser. No server. No upload. No third-party processing.
Performance
On a 2023 MacBook Pro (M2), encoding a 1080×1080 animation at 30 FPS:
| Duration | Frames | Encode Time | File Size |
|---|---|---|---|
| 3 seconds | 90 | ~2 seconds | ~1.8 MB |
| 10 seconds | 300 | ~6 seconds | ~5 MB |
| 30 seconds | 900 | ~18 seconds | ~14 MB |
Encoding is faster than real-time because hardware H.264 encoders (Intel Quick Sync, Apple VideoToolbox, NVIDIA NVENC) handle the heavy computation. The browser's WebCodecs API delegates to these hardware accelerators transparently [McIlroy, 2021].
Browser Support
| Browser | WebCodecs | H.264 Encode | Status |
|---|---|---|---|
| Chrome 94+ | Yes | Yes | Full support |
| Edge 94+ | Yes | Yes | Full support (Chromium) |
| Opera 80+ | Yes | Yes | Full support (Chromium) |
| Safari 16.4+ | Partial | Decode only | Encode not available |
| Firefox | Behind flag | No | Experimental |
For browsers without WebCodecs, PinePaper falls back to GIF export, which uses a pure JavaScript encoder (gif.js) and works everywhere.
References
- Can I Use (2026). WebCodecs API browser support table. caniuse.com/webcodecs.
- McIlroy, D. (2021). WebCodecs Explainer. W3C WICG. github.com/nicerobot/nicerobot.github.io/blob/master/nicerobot-explainer-webcodecs.md.
- Richardson, I.E.G. (2010). The H.264 Advanced Video Compression Standard (2nd ed.). Wiley.
- Vani, A. mp4-muxer — JavaScript MP4 muxer for WebCodecs. github.com/nicerobot/nicerobot.github.io.
- W3C (2021). WebCodecs API Specification. w3c.github.io/webcodecs/.
Export MP4 video for free — no server, no signup, no watermark. Open pinepaper.studio/editor and try it.
Ready to create?
Start making animated GIFs, videos, and graphics — free, no signup.
Open PinePaper Editor