By Li Tao
If you build small web tools that create video in the browser (a canvas animation, a slideshow maker, a screen-recording helper), sooner or later a user asks: "Why did I get a WebM? I need an MP4." Exporting video client-side is surprisingly doable, but the format you get depends on the browser, and that catches a lot of indie projects off guard.
Here are the pitfalls I ran into and how to design around them.
The core API: MediaRecorder
The common approach is to draw frames to a <canvas>, capture it as a stream, and record that stream:
const stream = canvas.captureStream(30); // 30 fps
const recorder = new MediaRecorder(stream, { mimeType });
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: mimeType });
// offer blob for download
};
recorder.start();
The catch is the mimeType. Not every browser can record every container and codec.
Pitfall 1: Assuming MP4 is available
Support for recording MP4 directly differs between browsers and versions, while WebM recording has historically been widely available in Chromium-based browsers and Firefox. Never hard-code a format. Feature-detect instead:
const candidates = [
'video/mp4;codecs=avc1',
'video/mp4',
'video/webm;codecs=vp9',
'video/webm;codecs=vp8',
'video/webm',
];
const mimeType = candidates.find((t) => MediaRecorder.isTypeSupported(t));
Prefer MP4 when it is supported, because most social platforms and editing apps accept it without fuss, and fall back to WebM otherwise. Then tell the user which one they got, and name the file with the correct extension.
Pitfall 2: Recording in real time
MediaRecorder records as the canvas plays. A 15-second animation takes about 15 seconds to export, and if the tab is throttled (backgrounded, low-power mode), frames get dropped. Tips:
- Show a progress bar and ask users to keep the tab in front.
- Keep the animation deterministic so a re-export produces the same result.
- For frame-perfect output, look at the WebCodecs API, which lets you encode frames yourself rather than depending on real-time playback, where available.
Pitfall 3: Resolution versus device limits
A 9:16 export at 1080×1920 is fine on most laptops. At 2160×3840 (4K vertical), weaker devices may run out of memory or fail to keep up. Offer resolution as a choice, default to something sensible like 1080p, and catch errors so a failed high-resolution export doesn't just hang.
Pitfall 4: Broken duration metadata
Some recorded WebM files have missing or wrong duration metadata, which shows up as an unseekable video or a strange length in players. If users report this, post-process the file (there are small libraries that fix WebM duration) or move to an encoding pipeline that writes the container properly.
Pitfall 5: Forgetting the vertical safe area
For TikTok, Reels, and Shorts, the platform overlays UI on the edges and bottom of the frame. If your tool generates text or logos, keep them inside a central safe area, or users will find their content hidden behind buttons and captions. Showing a safe-area guide in the preview costs little and prevents a lot of support questions.
Pitfall 6: Audio
Canvas streams carry no audio. If you need sound, mix it in with the Web Audio API and add the audio track to the stream before recording:
const dest = audioCtx.createMediaStreamDestination();
source.connect(dest);
const mixed = new MediaStream([
...canvas.captureStream(30).getVideoTracks(),
...dest.stream.getAudioTracks(),
]);
Check that the chosen mimeType includes an audio codec your target browsers support.
A small checklist
- Feature-detect
mimeType; prefer MP4, fall back to WebM - Label the output format and extension honestly
- Progress UI; warn about background tabs
- Sensible default resolution; handle failures
- Fix or avoid broken WebM durations
- Safe-area guides for vertical video
- Explicit audio mixing if needed
Browser-side video export is a great fit for small tools: no server costs, no uploads, and users keep their files. You just have to design for the fact that every browser is a slightly different encoder.
Top comments (0)