Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

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.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.