
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
SharedArrayBufferrequired - 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
SharedArrayBufferto 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.mp4to merge TS segments
Further Reading
Ready to test your M3U8 stream?
🚀 Try the M3U8 Online Player