将Canvas数据保存为MP4 - Javascript

8

我有一个动画画布,想要将它转换成mp4格式。我正在使用MediaRecorder来捕获屏幕,然后转换Blob。我了解到MediaRecorder不允许录制mp4,因此我只能以webm的形式获取画布。这是我尝试过的方法:

<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();
    }
});

非常感激任何解决方案。


问题是什么? - Andreas
你需要在服务器端使用类似ffmpeg的编码/转码工具进行编码/转码,或者使用像ffmpeg.js这样的WebAssembly解决方案在客户端进行编码。对我来说,这似乎应该被视为“寻求...的建议”而关闭。 - user120242
此外,各平台对编解码器和容器的支持也有所不同。我不确定您为什么选择使用数据URL,但通常在支持移动iOS Webview时会看到使用数据URL的hack,这将进一步复杂化事情。这超出了我们在此处可以帮助的范围。这不是一个简单的任务,您将不得不付出努力使其正常工作,并进行充分的测试以确保它在各个平台上可靠地工作。而要实现它的最佳方法因用例而异。如果您在实现它方面遇到具体问题,可以在此处发布。 - user120242
1个回答

21

使用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>


2
是的,我使用了上面提到的FFMPEG库来转换文件。 - Chris
有人能帮我解决这个问题和这个问题吗?@user120242 - Curious
很棒的回答@user120242!你对https://dev59.com/R3MOtIcB2Jgan1znbt9H 有什么想法吗?这个问题有点类似。 - Basj
1
代码片段不起作用。CDN没有设置所需的标头,无法使用SharedArrayBuffer - gre_gor
只有一些浏览器支持SharedArrayBuffer。 https://caniuse.com/sharedarraybuffer - Pål Thingbø
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接