DEV Community

ThatToolSite
ThatToolSite

Posted on

How we convert MP4 to MP3 entirely in the browser (WebCodecs + LAME)

Most "MP4 to MP3" sites upload your video to a server, convert it there and hand back a file. For a phone video of your kid's school concert, that's a lot of trust for a two-second job. So when I built ThatToolSite, I wanted the whole conversion to happen in the browser tab, with the file never leaving the device.

It turned out to be very doable in 2026, with one catch: browsers can decode almost everything, but they can't encode MP3. Here's how the pieces fit together.

The stack

  • Mediabunny reads and writes containers (MP4, MOV, WebM, MKV, M4A, OGG, MP3) and drives the browser's WebCodecs API for decoding and encoding.
  • WebCodecs (AudioDecoder, AudioEncoder) does the actual codec work, natively and fast.
  • @mediabunny/mp3-encoder, an MP3 encoder based on LAME, compiled to WebAssembly and plugged into Mediabunny as a custom encoder. (MPL-2.0; LAME itself is LGPL, credited on our About page.)

Problem 1: WebCodecs has no MP3 encoder

AudioEncoder.isConfigSupported({ codec: "mp3" }) returns false in every major browser. AAC and Opus are fine; MP3 isn't there. The fix is to register LAME as an encoder that Mediabunny can use like a built-in one. We only load it when someone actually wants MP3, so it never weighs down the page:

let mp3Ready: Promise<boolean> | null = null;

export function ensureMp3Encoder(): Promise<boolean> {
  mp3Ready ??= import("@mediabunny/mp3-encoder")
    .then((m) => {
      m.registerMp3Encoder();
      return true;
    })
    .catch(() => false);
  return mp3Ready;
}
Enter fullscreen mode Exit fullscreen mode

Before offering MP3 in the UI, we check it end to end:

const canMp3 = (await ensureMp3Encoder())
  && (await canEncodeAudio("mp3", { sampleRate: 44100, numberOfChannels: 2, bitrate: 128000 }));
Enter fullscreen mode Exit fullscreen mode

If anything fails (an old browser, a blocked chunk), the MP3 option is simply disabled and M4A, WAV and OGG still work.

Problem 2: doing it without freezing the page

Decoding a 10-minute video and re-encoding its audio is real work, so it runs in a Web Worker. The page posts the File to the worker (that shares a reference to the file's data rather than copying it) and gets progress messages back. With Mediabunny the conversion itself is short:

const conv = await Conversion.init({
  input,                       // the video, opened from the File
  output,                      // an MP3 output writing to a Blob
  tracks: "primary",
  video: { discard: true },    // we only want the sound
  audio: { codec: "mp3", quality: bitrateQuality(192_000) }, // our helper: kbps to a Mediabunny quality
  trim,                        // optional start/end
});
conv.onProgress = (p) => postMessage({ type: "progress", p });
await conv.execute();
Enter fullscreen mode Exit fullscreen mode

Cancelling is an AbortController passed into the job; the worker checks it between chunks and the UI shows "Cancelled" instead of a half-written file.

Problem 3: not making every visitor pay for it

The media engine and the MP3 encoder are hundreds of kilobytes. Shipping them with every page would hurt the people who just wanted to read about the tool. Two changes fixed that:

  1. Load the engine on first use, not at startup. Even the "does this browser support MP3?" check imports the engine lazily, after the page is idle.
  2. Turn off link prefetching on pages that list many tools. Next.js prefetches linked routes in the viewport, so the homepage was quietly downloading every tool's engine.

Together these took the tool page's phone Lighthouse score from 86 to 99.

How we test it

Unit tests can't tell you whether an MP3 actually plays, so the browser test suite does the real thing in Playwright:

  1. It generates a short test video in the browser (moving shapes plus a 440 Hz tone), encoded with Mediabunny itself.
  2. It drops the video on the tool and downloads the MP3.
  3. It checks the file starts with an ID3 tag or an MP3 frame sync, that the decoded duration matches the video, and that the dominant pitch is still about 440 Hz.

That last check catches the bugs that matter: wrong sample rate, mangled channels, silence.

Limits worth knowing

  • WebCodecs support: Chrome and Edge support everything here. Safari and Firefox support depends on the version and the codec, so the tool checks what the browser can do and says so instead of failing. Some video codecs (HEVC from iPhones) can only be decoded where the operating system has a decoder.
  • It's still your device doing the work. A long 4K video takes longer on an old phone than on a server farm. For a privacy-first tool that's the right trade.

If you want to see it working, the tool is here: MP4 to MP3, in your browser. It's free, and nothing is uploaded.

Top comments (0)