Use ffmpeg.wasm in React for Client-Side Video Transcoding
This article provides a step-by-step guide on how to integrate the
ffmpeg.wasm library into a React application to perform
client-side video transcoding. You will learn how to set up the
environment, configure the required security headers, and write the
React code necessary to load FFmpeg, process a video file, and download
the transcoded output directly from the browser.
Step 1: Install the Dependencies
To get started, you need to install the latest versions of the FFmpeg WebAssembly library and its utility package. Run the following command in your React project directory:
npm install @ffmpeg/ffmpeg @ffmpeg/utilStep 2: Configure SharedArrayBuffer Headers
ffmpeg.wasm relies on SharedArrayBuffer,
which requires your server to serve documents with specific HTTP headers
for security reasons (cross-origin isolation). If these headers are
missing, the browser will block the WebAssembly execution.
If you are using Vite, add the following to your
vite.config.js:
export default {
server: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
},
},
};If you are using Create React App (Webpack), you can configure these headers in your local development server or set them up on your hosting provider (e.g., Netlify, Vercel, or Cloudflare Pages) for production.
Step 3: Implement the Transcoding Component
Create a React component to handle loading FFmpeg, accepting a video file input, running the transcoding process, and generating a downloadable preview of the output.
Here is the complete implementation using modern functional React and
the @ffmpeg/ffmpeg v0.12+ API:
import React, { useState, useRef } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';
function VideoTranscoder() {
const [loaded, setLoaded] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [videoFile, setVideoFile] = useState(null);
const [transcodedUrl, setTranscodedUrl] = useState('');
const [status, setStatus] = useState('');
const ffmpegRef = useRef(new FFmpeg());
// Load FFmpeg from a CDN
const loadFFmpeg = async () => {
setIsLoading(true);
setStatus('Loading FFmpeg core...');
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ffmpeg = ffmpegRef.current;
try {
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
setLoaded(true);
setStatus('FFmpeg loaded successfully!');
} catch (error) {
console.error(error);
setStatus('Failed to load FFmpeg.');
} finally {
setIsLoading(false);
}
};
// Handle video transcoding (e.g., MP4 to WebM)
const transcodeVideo = async () => {
if (!videoFile) return;
setStatus('Transcoding started...');
const ffmpeg = ffmpegRef.current;
// Write the file to FFmpeg's virtual file system
await ffmpeg.writeFile('input.mp4', await fetchFile(videoFile));
// Execute the FFmpeg CLI command to transcode the video
// This example converts input.mp4 to output.webm
await ffmpeg.exec(['-i', 'input.mp4', 'output.webm']);
setStatus('Transcoding complete!');
// Read the result from the virtual file system
const data = await ffmpeg.readFile('output.webm');
// Create a URL for the generated WebM file
const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/webm' }));
setTranscodedUrl(url);
};
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h2>Client-Side Video Transcoder</h2>
{!loaded ? (
<button onClick={loadFFmpeg} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Load FFmpeg (WASM)'}
</button>
) : (
<div>
<p>FFmpeg is ready.</p>
<input
type="file"
accept="video/*"
onChange={(e) => setVideoFile(e.target.files[0])}
/>
<button onClick={transcodeVideo} disabled={!videoFile}>
Transcode to WebM
</button>
</div>
)}
<p><strong>Status:</strong> {status}</p>
{transcodedUrl && (
<div style={{ marginTop: '20px' }}>
<h3>Transcoded Video Preview:</h3>
<video src={transcodedUrl} controls width="500"></video>
<br />
<a href={transcodedUrl} download="transcoded.webm">
Download Transcoded Video
</a>
</div>
)}
</div>
);
}
export default VideoTranscoder;Step 4: How the Code Works
- Initialization (
loadFFmpeg): We load the WebAssembly binary from a public CDN (unpkg.com) to avoid bundling large WebAssembly files inside our main bundle. - Virtual File System (
writeFile): FFmpeg operates on a virtual in-memory file system. We must usefetchFileto convert our local React file state into a Uint8Array and write it into the virtual environment asinput.mp4. - Execution (
exec): We callffmpeg.exec()with standard command-line arguments. In this scenario,['-i', 'input.mp4', 'output.webm']takes the input file and transcodes it into WebM format. - Retrieval (
readFile): Once execution finishes, we read the output fileoutput.webmfrom the virtual file system, wrap it in a JavaScriptBlob, and generate a local URL usingURL.createObjectURLfor downloading or displaying inside an HTML5<video>element.