javascriptcanvasmp4

Save canvas data as mp4 - Javascript


I have an animated canvas, I want to convert that into mp4. I am using MediaRecorder to capture the screen and then converting that Blob. I learned that MediaRecorder does not allow recording in mp4, so I am forced to get the canvas in webm. Here is what I have tried:

<canvas id="canvas"></canvas>

var recordedChunks = [];

var time = 0;
var canvas = document.getElementById("canvas");

return new Promise(function (res, rej) {
    var stream = canvas.captureStream(60);

    mediaRecorder = new MediaRecorder(stream, {
        mimeType: "video/webm; codecs=vp9"
    });

    mediaRecorder.start(time);

    mediaRecorder.ondataavailable = function (e) {
        recordedChunks.push(event.data);
        if (mediaRecorder.state === 'recording') {
            mediaRecorder.stop();
        }
    }

    mediaRecorder.onstop = function (event) {
        var blob = new Blob(recordedChunks, {
            "type": "video/webm"
        });
        var url = URL.createObjectURL(blob);
        res(url);

        var xhr = new XMLHttpRequest;
        xhr.responseType = 'blob';

        xhr.onload = function() {
            var recoveredBlob = xhr.response;
            var reader = new FileReader;

            reader.onload = function() {
                var blobAsDataUrl = reader.result;
                document.getElementById("my-video").setAttribute("src", blobAsDataUrl);
            };

            reader.readAsDataURL(recoveredBlob);
        };

        xhr.open('GET', url);
        xhr.send();
    }
});

Any solution is highly appreciated.


Solution

  • Quick demo of transcoding using ffmpeg.wasm:

    const { createFFmpeg } = FFmpeg;
    const ffmpeg = createFFmpeg({
      log: true
    });
    
    const transcode = async (webcamData) => {
      const message = document.getElementById('message');
      const name = 'record.webm';
      await ffmpeg.load();
      message.innerHTML = 'Start transcoding';
      await ffmpeg.write(name, webcamData);
      await ffmpeg.transcode(name,  'output.mp4');
      message.innerHTML = 'Complete transcoding';
      const data = ffmpeg.read('output.mp4');
    
      const video = document.getElementById('output-video');
      video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
      dl.href = video.src;
      dl.innerHTML = "download mp4"
    }
    
    fn().then(async ({url, blob})=>{
        transcode(new Uint8Array(await (blob).arrayBuffer()));
    })
    
    function fn() {
    var recordedChunks = [];
    
    var time = 0;
    var canvas = document.getElementById("canvas");
    
    return new Promise(function (res, rej) {
        var stream = canvas.captureStream(60);
    
        mediaRecorder = new MediaRecorder(stream, {
            mimeType: "video/webm; codecs=vp9"
        });
    
        mediaRecorder.start(time);
    
        mediaRecorder.ondataavailable = function (e) {
            recordedChunks.push(event.data);
            // for demo, removed stop() call to capture more than one frame
        }
    
        mediaRecorder.onstop = function (event) {
            var blob = new Blob(recordedChunks, {
                "type": "video/webm"
            });
            var url = URL.createObjectURL(blob);
            res({url, blob}); // resolve both blob and url in an object
    
            myVideo.src = url;
            // removed data url conversion for brevity
        }
    
    // for demo, draw random lines and then stop recording
    var i = 0,
    tid = setInterval(()=>{
      if(i++ > 20) { // draw 20 lines
        clearInterval(tid);
        mediaRecorder.stop();
      }
      let canvas = document.querySelector("canvas");
      let cx = canvas.getContext("2d");
      cx.beginPath();
      cx.strokeStyle = 'green';
      cx.moveTo(Math.random()*100, Math.random()*100);
      cx.lineTo(Math.random()*100, Math.random()*100);
      cx.stroke();
    },200)
    
    });
    }
    <script src="https://unpkg.com/@ffmpeg/ffmpeg@0.8.1/dist/ffmpeg.min.js"></script>
    
    <canvas id="canvas" style="height:100px;width:100px"></canvas>
    
    <video id="myVideo" controls="controls"></video>
    <video id="output-video" controls="controls"></video>
    
    <a id="dl" href="" download="download.mp4"></a>
    
    <div id="message"></div>