Can You Transcode in the Browser? How WebAssembly FFmpeg Works

2026-08-20

Have you ever wondered: without installing any software, you open a web page and turn an mp4 into an mp3 — what is actually doing the work?

A lot of people assume "the website's backend server is quietly converting it." Not really. On the tools.beer web app, the conversion happens inside your own browser — by compiling ffmpeg, that classic transcoder, into a browser-runnable format called WebAssembly.

Today, in the simplest terms, we explain how it runs, why it sometimes "won't start", why it is slower than desktop software, and how large a file it can actually handle.

1. The one-line definition

WebAssembly (wasm for short) is a binary instruction format that runs in the browser at near-native speed. FFmpeg.wasm is the whole ffmpeg, originally written in C, compiled into wasm and dropped into the browser to run.

An analogy: take that long-established factory built in C (ffmpeg) and "fold" it into a portable blueprint (wasm) that fits inside the browser sandbox. The browser reads the blueprint and builds the factory on its own turf, then starts working. It is the same factory, just relocated from "built in the industrial zone (the OS)" to "assembled inside the browser."

So the first-hand fact about web transcoding is: the engine is the same ffmpeg, only the runtime changed.

2. How it actually runs in the browser

The file stays in browser memory the whole time, never touching your diskYou pickmp4JS reads to RAMfile.arrayBufferWeb Worker sandboxVirtual FS (MEMFS)bytes read into RAMffmpeg coreRead outputnew fileDownload/ previewDecode to encode is the heaviest CPU stage, done in the Worker, no UI freezeKey: the file lives in browser memory, never written to disk - private, but bounded by RAM

Step by step:

  1. You pick an mp4; the browser reads the whole file into memory with JS (as a blob of bytes);
  2. Those bytes are written into wasm's "virtual file system" (MEMFS); ffmpeg thinks it is reading a local disk, but it is reading memory;
  3. The conversion runs in a Web Worker, so it does not freeze the page;
  4. When done, the output file is read back out of the virtual file system and handed to you to download or preview.

The crucial point here: the file lives in browser memory the whole time and never lands on your disk. It has two consequences — the upside is "no upload, better privacy"; the cost is that file size is bounded by memory (more in section 5).

3. A real pit we fell into: why it "won't start"

Running FFmpeg.wasm on the tools.beer web app, the pit we spent the longest time in is worth telling.

To run fast, FFmpeg.wasm ships a multithreaded build that depends on a browser capability called SharedArrayBuffer (shared memory across threads). But browsers have a rule: they only expose this capability when the page is in a crossOriginIsolated state. And to make a page crossOriginIsolated, the server must send both of these response headers:

  • Cross-Origin-Opener-Policy: same-origin
  • Cross-Origin-Embedder-Policy: require-corp

Miss one, and crossOriginIsolated is false, and ffmpeg core throws on load. We debugged for a long time before pinning it on those two missing headers.

The nested pit: COEP applies to the whole page. It refuses to load any cross-origin resource that lacks the right response header — including external images. The result: an image you can curl down just will not show on the page (DevTools labels it blocked by COEP). So all our site illustrations are localized under public/ or inlined as SVG; we never hot-link a bare external image.

4. Why the browser version is slower than the desktop version (physical reasons)

Recall the pipeline from How FFmpeg works: decode to encode is the heaviest CPU stage. The in-browser wasm runs the same pipeline but lacks two things the desktop has:

  • Multicore: native desktop ffmpeg spins up a dozen threads in parallel; the in-browser wasm, even multithreaded, is held back by SharedArrayBuffer. On tools.beer we use the single-threaded core for faster first load, which is slower but loads quicker.
  • Hardware acceleration: the desktop can use Intel QSV, NVIDIA NVENC and similar hardware encoders; wasm runs on the general-purpose CPU and cannot.

So turning an mp4 into an mp3 may take seconds natively but longer in the browser — not magic, just a hardware gap.

5. File size limit: why big files belong on the desktop

Same ffmpeg engine, different runtimeBrowser FFmpeg.wasmRAM limit: ~2GB (32-bit heap)Threads: single / limited MTHardware accel: noneInstall: none, instant useBest for: quick small filesDesktop native ffmpeg (Tauri)RAM limit: your disk spaceThreads: multicoreHardware accel: QSV / NVENCInstall: desktop appBest for: huge files / speedBig files or need speed - desktop; quick small jobs - browser

Because of "the file lives in memory" from section 2, and wasm is a 32-bit address space with a heap ceiling around 2GB, while both input and output must reside at once, so:

  • Web version: around 2GB in theory, most stable from a few hundred MB up to 1GB;
  • Desktop native: streams from disk, limit is roughly your free disk space, tens of GB are easy.

So a 5GB meeting recording whose audio you want to extract will not convert in the browser; only the desktop app (native ffmpeg) can do it locally.

6. How we choose: browser vs desktop

tools.beer's answer is straightforward - we build both and split the work:

  • Web (FFmpeg.wasm): no install, instant use, good for quick small-file jobs;
  • Desktop (Tauri driving native ffmpeg): local, private, huge files, hardware acceleration, for heavy needs.

Technically we share one UI and branch on isTauri() - web goes to wasm, desktop to the native binary. That is exactly our "local / private / huge files" differentiator, and something upload-based online converters cannot match.

Summary

  • FFmpeg.wasm = ffmpeg compiled to WebAssembly, running the same engine in the browser sandbox;
  • Files are read into an in-memory virtual file system and never hit disk (private, but RAM-bounded);
  • The multithreaded build depends on SharedArrayBuffer - it needs COOP/COEP headers, or it fails to load;
  • Slower than desktop native because it lacks multicore and hardware acceleration;
  • The web version caps around 2GB; hand big files to the desktop app.

Further reading

Want to try it now? You can turn an MP4 into an MP3 right in the browser.

References (authoritative sources)

FFmpeg official site
Emscripten documentation (C to WebAssembly toolchain)
MDN: SharedArrayBuffer
MDN: Cross-Origin-Opener-Policy

Written by the tools.beer team, based on our real debugging experience with FFmpeg.wasm in the browser and native ffmpeg on the desktop. Last updated 2026-08-20.