MediaStream Recording API 对画布元素的视频捕获不起作用

问题描述

我正在尝试使用官方 MediaStream Recording API

录制和下载 canvas 元素的视频
<!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>

代码正在运行,我可以下载 test.webm,但我想它没有任何数据,因为在 VLC 媒体播放器中播放此文件时我没有看到任何内容

我缺少什么才能让它工作?

解决方法

您在这里遇到了一些错误。

第一个有点情有可原,Chrome doesn't generate seekable webm files. 这是因为媒体文件的构建方式以及 MediaRecorder API 的工作方式。为了能够添加这些信息,他们必须保留元数据所在的块,以便在录制完成时添加这些信息。

我不太确定 Firefox 在这里做了什么不同的事情,但 VLC 更喜欢他们的文件。

另一个 Chrome 错误,更不可原谅的是,在我们再次在源画布上绘制之前,它们不会将新帧传递给 MediaRecorder。
因此,由于在您的情况下,您在启动 MediaRecorder 后没有绘制任何内容,因此输出中将一无所获...

要解决这个问题,只需在我们停止记录器之前绘制一个框架就足够了,只是没有什么可以让我们确切知道浏览器何时将该框架推送到记录器......

所以这里唯一的解决方法是在我们记录画布的同时在画布上连续绘制。好消息是它不需要绘制任何新内容:我们可以通过绘制透明矩形来欺骗浏览器,使其认为新内容是绘制的。

最后一点,虽然 Chrome 确实支持导出具有透明度的画布,但并非所有浏览器都可以,即使在支持的情况下,大多数播放器都有默认的黑色背景。所以录制的时候一定要给自己画一个其他颜色的背景。

说了这么多,这里是一个固定的演示:

    final Handler handler = new Handler();
    final int delay = 1000;
 handler.postDelayed(new Runnable() {
            public void run() {
                updatelist();
                handler.postDelayed(this,delay);
            }
        },delay);
        
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// draw a white background
ctx.fillStyle = "white";
ctx.fillRect(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,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)");