Converting Video and Audio Entirely in the Browser With WebAssembly: Lessons From Building a Free Converter
Online file converters traditionally upload your file to a server, convert it there, and send it back. That costs the operator bandwidth and compute, and it means users hand over their files to a stranger. Videozify tak
Online file converters traditionally upload your file to a server, convert it there, and send it back. That costs the operator bandwidth and compute, and it means users hand over their files to a stranger.
Videozify takes the other route: conversion runs in the browser via WebAssembly. MP4 to MP3, MOV to MP4, WAV to MP3, MKV to MP4, MP4 to GIF, plus trim, compress, mute, resize and join — all processed on the user's device. Here's what I learned building it.
The engine: FFmpeg compiled to WebAssembly
FFmpeg is the Swiss Army knife of media, and it runs in the browser via WebAssembly builds such as ffmpeg.wasm. A basic conversion looks like this:
import { FFmpeg } from "@ffmpeg/ffmpeg";
import { fetchFile } from "@ffmpeg/util";
const ffmpeg = new FFmpeg();
await ffmpeg.load(); // downloads the wasm core (~25 MB) on first use
export async function mp4ToMp3(file) {
await ffmpeg.writeFile("in.mp4", await fetchFile(file));
await ffmpeg.exec(["-i", "in.mp4", "-vn", "-b:a", "192k", "out.mp3"]);
const data = await ffmpeg.readFile("out.mp3");
await ffmpeg.deleteFile("in.mp4");
await ffmpeg.deleteFile("out.mp3");
return new Blob([data], { type: "audio/mpeg" });
}
Lesson 1: The first load is the real UX problem
The core is about 25 MB. On a phone that's a noticeable wait, so:
- Load lazily — only when a user drops a file, not on page load (keeps Core Web Vitals healthy).
- Show honest progress — "Loading converter engine (first time only)" is far better than a frozen spinner.
- Cache it — after the first download, a service worker lets later conversions (and even offline processing) skip the fetch.
Lesson 2: Don't re-encode when you can remux
Some "conversions" are just changing the container. MKV → MP4 with H.264 video and AAC audio doesn't need re-encoding — just copying the streams, which is dramatically faster and lossless:
// remux: copy streams into a new container
await ffmpeg.exec(["-i", "in.mkv", "-c", "copy", "out.mp4"]);
Detecting when a stream copy is possible and falling back to re-encoding only when needed is one of the best performance wins available.
Lesson 3: Memory is the real file-size limit
There's no artificial size cap, but the browser has one: the WebAssembly memory space. In practice heavy video jobs top out around roughly 500 MB on many phones and about 2 GB on a typical desktop. Tell users that up front rather than letting the tab crash, and clean up virtual FS files after each job (see deleteFile above).
Lesson 4: Report progress from FFmpeg's own logs
Users need to see progress on long jobs. FFmpeg emits progress events you can map to a progress bar:
ffmpeg.on("progress", ({ progress }) => {
bar.value = Math.round(progress * 100);
});
Lesson 5: Browser support is broad — with WebAssembly
Chrome, Edge, Firefox and Safari 15+ (and their mobile versions) all work. The main requirement is WebAssembly; multithreaded builds additionally need cross-origin isolation headers, so a single-threaded fallback keeps things working everywhere:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Lesson 6: One page per format pair
Users search for exact tasks — "MOV to MP4", "WAV to MP3". Each popular pair gets its own page with a short description of what the conversion does and when you'd need it, all backed by the same engine and a small table of FFmpeg argument presets.
Why client-side conversion is worth it
- Privacy: files never leave the device.
- Cost: no server CPU or bandwidth per conversion, so it can stay free with no watermark.
- Offline: after the engine is cached, processing works without a connection.
Try it at videozify.com. Have you shipped WebAssembly-heavy features? I'd like to hear how you handled the initial download size.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.