When we set out to build a browser-based audio compressor, the obvious path was to write a simple JavaScript encoder. But JS audio encoding is slow, limited in format support, and produces mediocre results compared to native tools.

We wanted FFmpeg quality. We wanted it to run in the browser. So we used WebAssembly.

What is WebAssembly?

WebAssembly (WASM) is a binary instruction format that runs in all modern browsers at near-native speed. Critically, it lets you compile code written in C, C++, or Rust to run inside the browser sandbox — with no plugins and no security exceptions.

FFmpeg is the gold standard of audio/video processing. It's written in C. Compiling it to WASM means we get FFmpeg's full codec library running locally, inside the user's browser tab.

The architecture

Here's how the audio compressor pipeline works under the hood:

1
File read via File API
The user selects a file. The browser reads it into an ArrayBuffer in memory — no network transfer.
2
Pass to Web Worker
We transfer the ArrayBuffer to a Web Worker to avoid blocking the main thread. The UI stays responsive during processing.
3
Write to WASM filesystem
FFmpeg.wasm uses a virtual filesystem. We write the input file into this in-memory FS using ffmpeg.writeFile().
4
Run FFmpeg command
We execute the FFmpeg encode command with the user's chosen bitrate. FFmpeg runs the full encoding pipeline in WASM.
5
Read output and offer download
We read the output file from the WASM FS, create a Blob URL, and trigger a download. No data ever left the browser.

The core code

JavaScript
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';

const ffmpeg = createFFmpeg({ log: false });
await ffmpeg.load(); // loads ~30MB WASM binary once

async function compressAudio(file, bitrateKbps) {
  // Write input to virtual FS
  ffmpeg.writeFile('input', await fetchFile(file));

  // Run FFmpeg: re-encode at target bitrate
  await ffmpeg.run(
    '-i', 'input',
    '-b:a', `${bitrateKbps}k`,
    '-acodec', 'libmp3lame',
    'output.mp3'
  );

  // Read result back from virtual FS
  const data = ffmpeg.readFile('output.mp3');
  return new Blob([data.buffer], { type: 'audio/mp3' });
}

Performance considerations

WASM performance is close to native but not identical. A few things we optimised:

  • SharedArrayBuffer — enables multi-threaded WASM (FFmpeg's internal threading). Requires the page to be served with specific COOP/COEP headers.
  • WASM binary caching — the 30MB FFmpeg WASM binary is cached by the browser after the first load. Subsequent visits load instantly from cache.
  • Chunked progress — FFmpeg emits progress events that we pipe to the UI so users see real-time progress rather than a frozen tab.
On a modern laptop, compressing a 50MB WAV file to 128kbps MP3 takes about 3–5 seconds in WASM. The equivalent native FFmpeg command takes under 1 second. That ~4x overhead is the cost of WASM — acceptable for a browser tool.

What this means for you

The upshot is that you get genuine FFmpeg-quality audio compression — the same engine used by professional media pipelines — running entirely in your browser tab, with no server, no upload, and no waiting.

Try the Audio Compressor
FFmpeg-powered audio compression, running entirely in your browser.
Compress audio now