next.jsffmpegnext.js13ffmpeg-wasm

FFmpeg Wasm, error while creating video from canvas


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?


Solution

  • 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!