FFmpeg WebAssembly guide

What Is FFmpeg.wasm?

FFmpeg.wasm is a port of the well-known open-source media tool FFmpeg, compiled to WebAssembly via Emscripten. It allows browsers to perform complex video decoding, transcoding, and merging operations entirely on the client side — no backend server required.

Project: github.com/ffmpegwasm/ffmpeg.wasm

Traditional Architecture vs. FFmpeg.wasm Architecture

Traditional (server-side transcoding):
User → Upload → Server FFmpeg → Output → User downloads
Problem: High server CPU cost, bandwidth costs, privacy concerns

FFmpeg.wasm architecture:
User → In-browser FFmpeg.wasm → Output Blob → User downloads
Advantage: Zero server cost, complete privacy, works offline

The Technical Core: What Is WebAssembly?

WebAssembly (Wasm) is a low-level binary format designed to let compiled languages (C, C++, Rust, etc.) run in the browser at near-native speed.

FFmpeg's source code is written in C. The Emscripten toolchain compiles C code into a .wasm binary module, which runs alongside JavaScript glue code (ffmpeg-core.js) in the browser environment.

Two Execution Modes

Single-Threaded Mode

import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';

const ffmpeg = new FFmpeg();
await ffmpeg.load({
  coreURL: await toBlobURL(
    'https://unpkg.com/@ffmpeg/[email protected]/dist/esm/ffmpeg-core.js',
    'text/javascript'
  ),
  wasmURL: await toBlobURL(
    'https://unpkg.com/@ffmpeg/[email protected]/dist/esm/ffmpeg-core.wasm',
    'application/wasm'
  ),
});
  • No SharedArrayBuffer required
  • No special HTTP headers needed (COOP/COEP)
  • All operations run in a single JS thread (Worker)
  • Compatible with most static site deployments (Cloudflare Pages, GitHub Pages, etc.)

Multi-Threaded Mode

await ffmpeg.load({
  coreURL: 'ffmpeg-core-mt.js',   // multi-threaded core
  wasmURL: 'ffmpeg-core-mt.wasm',
  workerURL: 'ffmpeg-core-mt.worker.js',
});
  • Uses SharedArrayBuffer to share memory across Web Workers
  • Requires server to send these HTTP headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
  • Significantly faster (can use multi-core CPU)
  • More complex setup; some CDNs don't support it

Virtual File System (Emscripten FS)

FFmpeg.wasm cannot access the local disk directly. It uses Emscripten's Virtual File System (VFS) to simulate disk operations:

// Write an ArrayBuffer to the virtual FS
await ffmpeg.writeFile('input.ts', new Uint8Array(buffer));

// Run an FFmpeg command (operates on virtual FS)
await ffmpeg.exec(['-i', 'input.ts', '-c', 'copy', 'output.mp4']);

// Read the output
const data = await ffmpeg.readFile('output.mp4');

// Clean up to free memory — critical!
await ffmpeg.deleteFile('input.ts');
await ffmpeg.deleteFile('output.mp4');

Key point: VFS data lives in the JavaScript heap. You must manually delete files after use — otherwise they consume browser memory until the page is closed.

Memory Limits & Best Practices

This is FFmpeg.wasm's most significant inherent limitation:

Item Limit
JS Heap ceiling ~1–2 GB (varies by browser and OS)
Wasm memory model 32-bit addressing, 4 GB per module max
Practical available memory Usually 500 MB – 1.5 GB

Recommended Use Cases

  • ✅ Video under 720p, under 30 minutes
  • ✅ Primarily concat (segment merging) rather than re-encoding
  • ✅ Technical research and testing with public streams
  • ❌ 4K/1080p long videos (memory overflow)
  • ❌ Complex filtering and full re-encoding (too slow)

Memory Management Best Practices

// 1. Download in batches to avoid holding all ArrayBuffers simultaneously
const BATCH = 5;
for (let i = 0; i < segments.length; i += BATCH) {
  const batch = await downloadBatch(segments.slice(i, i + BATCH));
  // Write immediately to VFS, allowing JS-side ArrayBuffers to be GC'd
  for (const [idx, buf] of batch.entries()) {
    await ffmpeg.writeFile(`seg${i + idx}.ts`, new Uint8Array(buf));
  }
}

// 2. Delete VFS files immediately after use
await ffmpeg.deleteFile('output.mp4');

FFmpeg.wasm vs. Backend API

Aspect FFmpeg.wasm Backend API
Server cost Zero High (CPU-intensive)
Privacy 100% (local processing) Requires trusting server
Processing speed Slow (single-core Wasm) Fast (multi-core + GPU)
Large file support Limited by browser memory Virtually unlimited
Deployment complexity Minimal (static assets) Requires server maintenance
Best for Small conversions, research tools Production batch processing

How This Site Uses It

This site's HLS Downloader uses the following stack:

  • @ffmpeg/ffmpeg 0.12.x (single-threaded, no COOP/COEP required)
  • @ffmpeg/core 0.12.x (matching Wasm core)
  • Loaded dynamically via unpkg CDN — first load takes ~15–30 seconds (Wasm module is ~30MB)
  • Executes ffmpeg -f concat -safe 0 -i concat.txt -c copy output.mp4 to merge TS segments

Further Reading

Ready to test your M3U8 stream?

🚀 Try the M3U8 Online Player