5

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.

Adnan Afzal
  • 137
  • 3
  • 8
  • And the question/problem is? – Andreas Jul 12 '20 at 16:23
  • You will need to encode/transcode it on the server side using something like ffmpeg, or use a WebAssembly solution like ffmpeg.js to encode it client side. This looks to me like something that should be closed as "seeking recommendations for ..." – user120242 Jul 12 '20 at 16:47
  • Also codec and container support varies across platforms. I'm not sure why you opted to use a data url, but using a dataurl is a hack you usually see when supporting mobile ios webview, which will further complicate things. It is way outside the scope of what we can help with here. It's not a simple task and you will have to put in the work to make it work, and do sufficient testing to make sure it works reliably across platforms. And the best way to implement it varies on use case. If you have specific problems with implementing it, those can be posted here. – user120242 Jul 12 '20 at 17:21

1 Answers1

15

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>
user120242
  • 14,185
  • 3
  • 35
  • 51
  • I tried this, and it worked well. However, I'm only able to use it once per page load. Would there be a way to use this more than once per page load, or is that just a problem on my end? – Chris Aug 08 '20 at 22:12
  • @Chris did you find a solution to this? – Crashalot Dec 13 '20 at 01:57
  • 2
    Yes, I used the FFMPEG library above to convert the file. – Chris Jan 16 '21 at 16:58
  • Can anyone help me with this [question](https://stackoverflow.com/questions/71861711/media-stream-and-capture-api-for-creating-mp4-from-canvas-elements) and this [question](https://stackoverflow.com/questions/71833373/ffmpeg-js-to-create-mp4-from-canavas-frames-and-transcode-it) please? @user120242 – Curious Apr 13 '22 at 18:26
  • Great answer @user120242! Would you have an idea for https://stackoverflow.com/questions/72043099/insert-text-on-top-of-video-canvas-and-export-as-mp4 which is a bit similar? – Basj Apr 28 '22 at 11:47