Transcode Videos in React with ffmpeg.wasm
Client-side video transcoding reduces server infrastructure costs,
minimizes network bandwidth, and keeps sensitive media securely within
the user's browser. This guide provides a direct implementation path for
integrating ffmpeg.wasm into a React project, configuring
required browser security headers, and converting media files using
WebAssembly.
1. Configure Cross-Origin Isolation
ffmpeg.wasm uses SharedArrayBuffer for
multi-threading and high-performance memory operations. Modern web
browsers require explicit HTTP response headers to enable this API.
Add the following headers to your local development server and production deployment:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
If you are using Vite, add this to your
vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
});2. Install Required Dependencies
Install the core @ffmpeg/ffmpeg package and its helper
utilities:
npm install @ffmpeg/ffmpeg @ffmpeg/util3. Implement the React Component
The modern version of ffmpeg.wasm (v0.12+) separates the
execution logic from the core WebAssembly binary. You load the core
binaries dynamically via Blob URLs to prevent cross-origin issues with
standard CDNs.
Below is a complete, minimal React component for transcoding a video to MP4:
import React, { useState, useRef } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';
export default function VideoTranscoder() {
const [loaded, setLoaded] = useState(false);
const [transcoding, setTranscoding] = useState(false);
const [videoSrc, setVideoSrc] = useState('');
const [progress, setProgress] = useState(0);
const ffmpegRef = useRef(new FFmpeg());
const messageRef = useRef(null);
const loadFFmpeg = async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
const ffmpeg = ffmpegRef.current;
ffmpeg.on('log', ({ message }) => {
if (messageRef.current) messageRef.current.innerText = message;
});
ffmpeg.on('progress', ({ progress: prog }) => {
setProgress(Math.round(prog * 100));
});
// Load WebAssembly binaries
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
setLoaded(true);
};
const transcode = async (event) => {
const file = event.target.files?.[0];
if (!file) return;
if (!loaded) {
await loadFFmpeg();
}
const ffmpeg = ffmpegRef.current;
setTranscoding(true);
const inputName = 'input_file';
const outputName = 'output.mp4';
// Write file into FFmpeg's virtual filesystem
await ffmpeg.writeFile(inputName, await fetchFile(file));
// Execute the standard FFmpeg command
await ffmpeg.exec(['-i', inputName, outputName]);
// Read the result from the virtual filesystem
const data = await ffmpeg.readFile(outputName);
// Convert output buffer to a downloadable/playable URL
const url = URL.createObjectURL(
new Blob([data.buffer], { type: 'video/mp4' })
);
setVideoSrc(url);
setTranscoding(false);
};
return (
<div style={{ padding: '2rem' }}>
{!loaded && (
<button onClick={loadFFmpeg}>Initialize FFmpeg</button>
)}
{loaded && (
<div>
<label>Select a video to transcode to MP4: </label>
<input type="file" onChange={transcode} disabled={transcoding} />
</div>
)}
{transcoding && <p>Transcoding: {progress}%</p>}
{videoSrc && (
<div style={{ marginTop: '1rem' }}>
<h3>Output Video:</h3>
<video src={videoSrc} controls width="400" />
<br />
<a href={videoSrc} download="transcoded.mp4">Download Transcoded File</a>
</div>
)}
<p ref={messageRef} style={{ fontSize: '0.8rem', color: '#666' }}></p>
</div>
);
}4. Technical Considerations and Limitations
- Memory Usage: Browser tabs typically have a memory limit (usually between 2GB and 4GB depending on the OS and architecture). Processing large files (over 500MB) can cause the tab to crash.
- Execution Speed: While WebAssembly performs near-native speeds for basic computations, client-side transcoding is CPU-bound and lacks full hardware acceleration (GPU encoding). Transcoding long or 4K videos will cause noticeable device battery drain and thermal throttling.
- Storage Clean-up: Always clear unused virtual files
using
ffmpeg.deleteFile('filename')when processing multiple files in the same session to avoid memory leaks within the WebAssembly heap.