DEV Community

Elevyxa
Elevyxa

Posted on Fully Autonomous

Compressing video in the browser with canvas + MediaRecorder: what works and what doesn't

I run a small free tool site, CorvexsDigital, and wanted a video compressor that never uploads the user's file. No server, no FFmpeg build shipped to the browser — just the APIs every modern browser already has. Here's how it works, and where it falls short.

The basic pipeline

The idea: play the video into a <canvas> at the target size, capture that canvas as a stream, and record it with MediaRecorder at a lower bitrate.

const video = document.createElement('video');
video.src = URL.createObjectURL(file);
video.muted = !keepAudio;
await video.play();

const canvas = Object.assign(document.createElement('canvas'), { width, height });
const ctx = canvas.getContext('2d');
const stream = canvas.captureStream(30); // up to 30 fps

const recorder = new MediaRecorder(stream, {
  mimeType: 'video/webm;codecs=vp8,opus',
  videoBitsPerSecond,
  audioBitsPerSecond,
});

// draw frames while the video plays
const timer = setInterval(() => ctx.drawImage(video, 0, 0, width, height), 1000 / 30);
Enter fullscreen mode Exit fullscreen mode

Audio comes along through Web Audio: createMediaElementSource(video) → createMediaStreamDestination(), then add that audio track to the canvas stream.

Choosing a bitrate that actually shrinks the file

A fixed bitrate is a trap: a 2 Mbps preset makes an already-small phone clip bigger. So the budget is derived from the source:

const sourceBps = file.size * 8 / duration;
const budget = sourceBps * ratio * 0.9; // ratio 0.5 / 0.65 / 0.8 by preset, 10% for container overhead
const audio = keepAudio ? clamp(budget * 0.15, 16_000, 96_000) : 0;
const video = Math.min(presetCap, budget - audio);
Enter fullscreen mode Exit fullscreen mode

If the budget drops below ~32 kbps there's no point trying — the tool says so up front.

Guard: never hand back a bigger file

Even with a budget, encoders sometimes lose. Counting bytes as chunks arrive lets you stop early:

recorder.ondataavailable = e => {
  bytes += e.data.size;
  if (bytes >= file.size) fail(new Error('Could not make this video smaller.'));
};
Enter fullscreen mode Exit fullscreen mode

The user keeps their original and gets a suggestion (lower resolution, smaller preset, remove audio) instead of a useless download.

Gotcha 1: recordings with no duration

Files recorded by MediaRecorder (screen recordings, many web apps) often report duration === Infinity. Seeking far past the end forces the browser to work it out:

if (!Number.isFinite(video.duration)) {
  video.currentTime = 1e10;
  await once(video, 'seeked');
  const duration = video.duration; // now finite
  video.currentTime = 0;
  await once(video, 'seeked');
}
Enter fullscreen mode Exit fullscreen mode

And the WebM that you produce has the same problem — players show no length and seeking breaks. I patch the WebM Info header with the real duration afterwards, leaving the recorded clusters untouched.

Gotcha 2: it's real time, and the tab must stay visible

MediaRecorder records as the video plays, so a 60-second clip takes about 60 seconds. Background tabs get throttled, which ruins the recording, so the tool listens for visibilitychange and stops with a clear message instead of producing a broken file.

Gotcha 3: output format depends on the browser

MediaRecorder.isTypeSupported('video/mp4;codecs=avc1.42E01E,mp4a.40.2') is true in current Chromium, but not everywhere. Only offer formats the browser can actually record — and never rename a WebM to .mp4.

Results (honest version)

On synthetic 720p test clips: 7.95 MB → 5.54 MB (WebM, about 30% smaller) and 6.28 MB → 5.40 MB (MP4, about 14% smaller). Real-world savings depend entirely on the source; an already well-compressed phone video may not shrink at all, and the tool tells you when that happens.

What this approach can't do

  • No trimming, subtitles, multiple audio tracks or HDR
  • Not faster than real time
  • Quality presets, not an exact "make it 8 MB" target

For short clips you want to email or post, it's been good enough — and nothing leaves the device. If you want to try it or poke at the limits: corvexsdigital.com/video-compressor. I also wrote up how bitrate and resolution affect video file size.

Questions or edge cases that break it? I'd genuinely like to hear them.

This post was written with AI assistance (Claude), based on how the CorvexsDigital compressor is actually built and tested.

Top comments (0)