Alok
Alok

Reputation: 10534

Video capture of canvas element by MediaStream Recording API is not working

I am trying to record and download video of canvas element using official MediaStream Recording API

<!DOCTYPE html>
<html>

<body>
    <h1>Lets test mediaRecorder</h1>

    <canvas id="myCanvas" width="200" height="100" style="border:1px solid #d3d3d3;">
        Your browser does not support the HTML canvas tag.
    </canvas>

    <script>
        var c = document.getElementById("myCanvas");
        var ctx = c.getContext("2d");
        ctx.font = "30px Arial";
        ctx.fillText("Hello World", 10, 50);

        const stream = c.captureStream(25);
        var recordedChunks = [];
        console.log(stream);
        var options = { mimeType: "video/webm; codecs=vp9" };
        mediaRecorder = new MediaRecorder(stream, options);

        mediaRecorder.ondataavailable = handleDataAvailable;
        mediaRecorder.start();

        function handleDataAvailable(event) {
            console.log("data-available");
            if (event.data.size > 0) {
                recordedChunks.push(event.data);
                console.log(recordedChunks);
                download();
            } else {
                // ...
            }
        }

        function download() {
            var blob = new Blob(recordedChunks, {
                type: "video/webm"
            });
            var url = URL.createObjectURL(blob);
            var a = document.createElement("a");
            document.body.appendChild(a);
            a.style = "display: none";
            a.href = url;
            a.download = "test.webm";
            a.click();
            window.URL.revokeObjectURL(url);
        }

        // demo: to download after 10 sec
        setTimeout(event => {
            console.log("stopping");
            mediaRecorder.stop();
        }, 10000);
    </script>

</body>

</html>

code is working and I am able to download test.webm but I guess that does not have any data as I am not seeing any content while playing this file in VLC Media Player

What I am missing to make it working?

Upvotes: 2

Views: 2303

Answers (1)

Kaiido
Kaiido

Reputation: 136618

You are facing a few bugs here.

First one is a bit excusable, Chrome doesn't generate seekable webm files. This is because of how media files are built and how the MediaRecorder API works. For them to be able to add this information they'd have to keep the chunk where the metadata is in order to add this information when the recording is done.

I'm not too sure what Firefox does differently here, but VLC prefers their file.

An other Chrome bug, a lot less excusable, is that they don't pass a new frame to the MediaRecorder until we draw on the source canvas again.
So since in your case you are not drawing anything after you started the MediaRecorder, you'll get nothing in the output...

To workaround that, simply drawing a frame right before we stop the recorder should have been enough, except that there is nothing letting us know exactly when the browser will push that frame to the recorder...

So the only working workaround here is to draw on the canvas continuously while we record it. The good thing is that it doesn't need to be painting anything new: we can trick the browser in thinking something new was painted by drawing a transparent rectangle.

A final note, while Chrome does support exporting the canvas with transparency, not all browsers can and even when supported most players have a default black background. So be sure to draw yourself a background in an other color when you record it.

All that said, here is a fixed demo:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// draw a white background
ctx.fillStyle = "white";
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = "black";
ctx.font = "30px Arial";
ctx.fillText("Hello World", 10, 50);

const stream = c.captureStream(25);
var recordedChunks = [];
var options = {};
mediaRecorder = new MediaRecorder(stream, options);

mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
// Chrome requires we draw on the canvas while recording
mediaRecorder.onstart = animationLoop;

function animationLoop() {
  // draw nothing, but still draw
  ctx.globalAlpha = 0;
  ctx.fillRect(0, 0, 1, 1);
  // while we're recording
  if (mediaRecorder.state !== "inactive") {
    requestAnimationFrame(animationLoop);
  }
}
// wait for the stop event to export the final video
// the dataavailable can fire before
mediaRecorder.onstop = (evt) => download();

function handleDataAvailable(event) {
  recordedChunks.push(event.data);
}

function download() {
  var blob = new Blob(recordedChunks, {
    type: "video/webm"
  });
  var url = URL.createObjectURL(blob);
  // exporting to a video element for that demo
  // the downloaded video will still not work in some programs
  // For this one would need to fix the markers using something like ffmpeg.
  var video = document.getElementById('video');
  video.src = url;
  // hack to make the video seekable in the browser
  // see https://stackoverflow.com/questions/38443084/
  video.onloadedmetadata = (evt) => {
    video.currentTime = 10e6;
    video.addEventListener("seeked", () => video.currentTime = 0, {
      once: true
    })
  }
}

setTimeout(() => {
  console.clear();
  mediaRecorder.stop();
}, 10000);
console.log("please wait while recording (10s)");
<h1>Lets test mediaRecorder</h1>

<canvas id="myCanvas" width="200" height="100" style="border:1px solid #d3d3d3;">
  Your browser does not support the HTML canvas tag.
</canvas>
<video controls id="video"></video>

Upvotes: 4

Related Questions