· 5 min read

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 chunks
  • VideoDecoder — takes encoded chunks, outputs raw frames (used for playback, not export)

For MP4 export, we use VideoEncoder. The workflow:

  1. Create a VideoEncoder configured for H.264 at the desired resolution and bitrate
  2. For each animation frame: capture the canvas as a VideoFrame, feed it to the encoder
  3. Collect the encoded chunks (raw H.264 NAL units)
  4. Mux the chunks into an MP4 container
  5. 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:

  • ftyp box: file type declaration (isom, iso2, mp41)
  • moov box: movie metadata, track descriptions, timing tables
  • mdat box: 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