I'm using ffmpeg.wasm in my Next.JS app.
Here my specs:
"@ffmpeg/ffmpeg": "^0.12.5",
"@ffmpeg/util": "^0.12.0",
"next": "^13.0.6",
"react": "^18.2.0",
I want to simply record a 5s video from a canvas, so I tried:
'use client'
import React, { useEffect, useRef, useState } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
const CanvasVideoRecorder = () => {
const canvasRef = useRef(null);
const videoChunksRef = useRef([]);
const ffmpegRef = useRef(new FFmpeg({ log: true }));
const [loaded, setLoaded] = useState(false);
const [videoUrl, setVideoUrl] = useState(null);
const load = async () => {
await ffmpegRef.current.load({
coreURL: '/js/ffmpeg-core.js',
wasmURL: '/js/ffmpeg-core.wasm',
});
setLoaded(true);
};
useEffect(() => {
const ctx = canvasRef.current.getContext('2d');
function drawFrame(timestamp) {
ctx.fillStyle = `rgb(${(Math.sin(timestamp / 500) * 128) + 128}, 0, 0)`;
ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
requestAnimationFrame(drawFrame);
}
requestAnimationFrame(drawFrame);
}, []);
const startRecording = async () => {
const videoStream = canvasRef.current.captureStream(30);
const videoRecorder = new MediaRecorder(videoStream, { mimeType: 'video/webm' });
videoRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
videoChunksRef.current.push(event.data);
}
};
videoRecorder.start();
setTimeout(() => videoRecorder.stop(), 5000);
videoRecorder.onstop = async () => {
try {
await ffmpegRef.current.writeFile('recorded.webm', await fetchFile(new Blob(videoChunksRef.current, { type: 'video/webm' })));
await ffmpegRef.current.exec('-y', '-i', 'recorded.webm', '-an', '-c:v', 'copy', 'output_copy.webm');
const data = await ffmpegRef.current.readFile('output_copy.webm');
const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/webm' }));
setVideoUrl(url);
} catch (error) {
console.error("Error during processing:", error);
}
};
};
return (
<div>
<canvas ref={canvasRef} width="640" height="480"></canvas>
{loaded ? (
<>
<button onClick={startRecording}>Start Recording</button>
{videoUrl && <video controls src={videoUrl}></video>}
</>
) : (
<button onClick={load}>Load FFmpeg</button>
)}
</div>
);
};
export default CanvasVideoRecorder;
I don't know why but it catch an error:
ErrnoError: FS error
This error occurs when I do this:
await ffmpegRef.current.exec('-y', '-i', 'recorded.webm', '-an', '-c:v', 'copy', 'output_copy.webm');
const data = await ffmpegRef.current.readFile('output_copy.webm');
The recorded.webm
file is written correctly and I can read it, ffmpegRef.current
is well defined, so what's wrong with my logic, why the exec command doesn't work?
First of all, I forgot the square brackets in the exec
.
Second, I don't know exactly what's wrong with the WebM conversion, but I ended up using mp4
format:
await ffmpeg.exec(['-i', 'recorded.webm', '-i', 'audio.mp3', '-c:v', 'libx264', '-c:a', 'aac', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4');
Now it works well!