filetity

Chrome can play an MP3 and cannot make one

Every browser on your machine plays MP3 and has done for twenty years. WebCodecs then gave the browser a real encoder API. Converting audio to MP3 in a tab still does not work, and it is not an oversight.

Measured in Chrome on macOS on 9 September 2026, asking the encoder what it supports:

await AudioEncoder.isConfigSupported({
  codec: "mp3", sampleRate: 44100, numberOfChannels: 2,
})
// supported: false

// same call with codec: "mp4a.40.2"  -> supported: true
// same call with codec: "opus"       -> supported: true

AAC yes. Opus yes. MP3 no. The same browser that will happily decode an MP3 you drop on it will not write one, and the API tells you so politely rather than failing halfway through.

Why the codec is only half there

Decoding and encoding are different pieces of software with different histories. Browsers needed an MP3 decoder because the web is full of MP3s, and the patents that made MP3 expensive to ship expired in 2017. Nobody needed an MP3 encoder in a browser, because until WebCodecs a browser could not produce media files at all.

When WebCodecs did arrive, the codecs it shipped with were the ones the platform already had encoders for and the ones the web is moving towards: AAC, because every phone records it, and Opus, because it is the codec WebRTC uses. MP3 is a format nothing new is written in. It is also the format almost every non-technical person means by "audio file", and the one their car stereo, their voice recorder and their university upload form will accept.

So the browser can make two formats a normal person has never heard of, and not the one they will be asked for.

The obvious fix, and the problem with it

The obvious fix is ffmpeg compiled to WebAssembly. It works, and two things make it the wrong choice here. It is tens of megabytes, against the 169,114 bytes of the encoder this site actually ships, and this repo has a rule that a PDF engine must never be downloaded by somebody using a JSON formatter. And an ffmpeg build with the interesting codecs in it is GPL, which for a website is not a footnote: shipping a WebAssembly binary to a browser is distribution, and distribution is what the licence attaches to.

A competitor shipped Ghostscript in their PDF compressor. Artifex sent them an AGPL notice, and nine days later the engine was gone, replaced with a hand-written JavaScript one and announced as a rewrite. Their own founder confirmed the notice publicly.

LAME, and the line the LGPL actually draws

The MP3 encoder everybody uses is LAME. The JavaScript port that runs in a browser is @breezystack/lamejs, and it is LGPL-3.0, which is a different bargain from the GPL. You can use an LGPL library in software that is not itself LGPL, on one condition: the user has to be able to swap the library out for their own version.

In C that condition is met by dynamic linking. In a browser it is met the same way, and it is easier: the encoder is served as its own file at /lamejs-1.2.7.js, unmodified, and the worker loads it at runtime with importScripts. It never goes through the bundler, so it never gets inlined into the application, minified, tree shaken or otherwise fused into something a user cannot replace. Anyone who wants a different LAME can serve one at that path.

The rule is easy to break by accident. One import of the package anywhere in the application, added by someone who does not know why the fetch exists, and the bundler helpfully does exactly the thing the licence forbids. So the build checks: every source file is scanned, and a single import of the LAME package or the HEIC decoder fails it. 501 files scanned on the last build, none of them importing either.

Nine seconds of frozen page, or nine seconds of work

LAME in Chrome on macOS runs at about 34 times realtime, measured on the same day. A five minute song is roughly nine seconds of solid CPU. On the main thread that is nine seconds during which the page does not scroll, buttons do not depress and the tab looks crashed.

So it runs in a worker, with one chunk of audio in flight at a time. The main thread sends the next chunk only after the worker says it finished the last one. That backpressure keeps memory flat on a long file, and it makes the progress bar count samples that have been encoded rather than samples that have been posted into a queue. A progress bar built on the second number reaches 100% and then sits there.

A browser will convert audio to MP3 on your own machine with nothing uploaded. It takes a 169 KB file fetched once, a worker, and a deliberate decision not to import the thing you are using.

The tools this came out of

Convert audio to MP3 on your own device, with nothing uploaded and no queue.

filetity is built by Adarsh Mishra. The codec support was read from Chrome on macOS on 9 September 2026 and browsers change, so if isConfigSupported says something else on yours, that is worth knowing: support@filetity.com.