key*_*rai 0 html javascript google-chrome web-mediarecorder
我有一个画布元素内的工作完美的演示动画,我可以与MediaRecorder和WebM视频文件记录captureStream()从<canvas>元素。
来自 2d 上下文 api 的动画在生成的视频中表现得很好,但是当我尝试使用drawImage()以将图像添加到画布时,我似乎无法使其正常工作。在后一种情况下,MediaRecorder.ondataavailable处理程序没有接收到有效数据并且生成的视频文件是一个 0 字节的文件。
我什至实现了一个演示,我可以在其中切换是否drawImage()执行呼叫。在下面的代码中,如果drawImage = false视频生成没有问题,但如果drawImage切换到true,它将生成一个 0 字节的文件。
为了演示,我把这个jsfiddle放在一起https://jsfiddle.net/keyboardsamurai/3tkm0dp6/16/
我在 MacOS 上的“Chrome 版本 75.0.3770.100(官方版本)(64 位)”上运行此代码 - 甚至不确定它是否应该在 Firefox 等上运行,因为 MediaRecorder API 在 FF 上抛出看似无关的错误。
另请参阅此处的完整代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<canvas id="drawing_canvas" width="1280" height="720"></canvas>
<script>
const image = new Image();
image.src = 'https://66.media.tumblr.com/84d332cafeb1052c477c979281e5713b/tumblr_owe3l0tkCj1wxdq3zo1_1280.jpg';
window.requestAnimationFrame(animation);
const drawImage = false; // toggle to 'true' to make this example fail
const canvas = document.getElementById('drawing_canvas');
const allChunks = [];
const recorder = initMediaRecorder(canvas);
recorder.start();
setTimeout(function (e) {
console.log("Video ended");
recorder.stop();
}, 5000);
function initMediaRecorder(canvasElement) {
const stream = canvasElement.captureStream(60);
const recorder = new MediaRecorder(stream, {mimeType: 'video/webm'});
recorder.ondataavailable = function (e) {
console.log("data handler called");
if (e.data) {
console.log("data available: " + e.data.size)
if (e.data.size > 0) {
console.log("data added");
allChunks.push(e.data);
}
} else {
console.error("Data handler received no data in event: " + JSON.stringify(e))
}
};
recorder.onstop = function (e) {
const fullBlob = new Blob(allChunks);
const link = document.createElement('a');
link.style.display = 'none';
link.href = window.URL.createObjectURL(fullBlob);
link.download = 'media.webm';
document.body.appendChild(link);
link.click();
link.remove();
};
return recorder;
}
function animation() {
const now = new Date();
const ctx = document.getElementById('drawing_canvas').getContext('2d');
if (drawImage) {
ctx.drawImage(image, 0, 0);
}
ctx.clearRect(0, 0, 150, 150);
ctx.strokeStyle = 'white';
ctx.fillStyle = 'white';
ctx.rect(0, 0, 1280, 720);
ctx.stroke();
ctx.save();
ctx.translate(75, 75);
ctx.scale(0.4, 0.4);
ctx.rotate(-Math.PI / 2);
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
ctx.lineWidth = 8;
ctx.lineCap = 'round';
// Hour marks
ctx.save();
for (var i = 0; i < 12; i++) {
ctx.beginPath();
ctx.rotate(Math.PI / 6);
ctx.moveTo(100, 0);
ctx.lineTo(120, 0);
ctx.stroke();
}
ctx.restore();
// Minute marks
ctx.save();
ctx.lineWidth = 5;
for (i = 0; i < 60; i++) {
if (i % 5 != 0) {
ctx.beginPath();
ctx.moveTo(117, 0);
ctx.lineTo(120, 0);
ctx.stroke();
}
ctx.rotate(Math.PI / 30);
}
ctx.restore();
const sec = now.getSeconds();
const min = now.getMinutes();
let hr = now.getHours();
hr = hr >= 12 ? hr - 12 : hr;
ctx.fillStyle = 'black';
// write Hours
ctx.save();
ctx.rotate(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) * sec);
ctx.lineWidth = 14;
ctx.beginPath();
ctx.moveTo(-20, 0);
ctx.lineTo(80, 0);
ctx.stroke();
ctx.restore();
// write Minutes
ctx.save();
ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(-28, 0);
ctx.lineTo(112, 0);
ctx.stroke();
ctx.restore();
// Write seconds
ctx.save();
ctx.rotate(sec * Math.PI / 30);
ctx.strokeStyle = '#D40000';
ctx.fillStyle = '#D40000';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(-30, 0);
ctx.lineTo(83, 0);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.arc(95, 0, 10, 0, Math.PI * 2, true);
ctx.stroke();
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
ctx.arc(0, 0, 3, 0, Math.PI * 2, true);
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.lineWidth = 14;
ctx.strokeStyle = '#325FA2';
ctx.arc(0, 0, 142, 0, Math.PI * 2, true);
ctx.stroke();
ctx.restore();
window.requestAnimationFrame(animation);
}
</script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
更新:确认上述行为至少可以在这些 Chrom(e/ium) 版本上复制:
MacOS Mojave 10.14.5 版本 75.0.3770.100(官方构建)(64 位)
MacOS Mojave 10.14.5 版本 77.0.3849.0(官方构建)canary(64 位)
Ubuntu 19.04 Disco Dingo 上的版本 77.0.3770.100(官方构建)快照(64 位)
发生这种情况是因为您的图像来自跨域资源并污染了您的画布。
污染从中捕获 MediaStream 的画布将阻止所述 MediaStream 捕获任何新图像。
此外,尝试从此类受污染的画布中捕获 MediaStream 将引发 SecurityError。
const ctx = canvas.getContext('2d');
const stream = canvas.captureStream();
vid.srcObject = stream;
const img = new Image();
img.onload = e => {
console.log('will taint the canvas')
ctx.drawImage(img, 0, 0);
// and if we try now to capture a new stream, we have a clear error
const stream2 = canvas.captureStream();
}
img.src = "https://66.media.tumblr.com/84d332cafeb1052c477c979281e5713b/tumblr_owe3l0tkCj1wxdq3zo1_1280.jpg";
ctx.fillRect(0,0,20,20);Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
<video id="vid" controls autoplay muted></video>Run Code Online (Sandbox Code Playgroud)
为了规避它,您需要服务器以跨域兼容的方式发送图像,通过正确设置 Access-control-origin 标头以接受您自己的域,然后使用crossorigin属性请求此图像。您从中加载此特定图像的服务器确实允许任何人以这种跨域兼容的方式访问他们的数据,因此我们可以演示前端部分:
const ctx = canvas.getContext('2d');
const stream = canvas.captureStream();
vid.srcObject = stream;
const img = new Image();
img.crossOrigin = 'anonymous'; // add this to request the image as cross-origin allowed
img.onload = e => {
console.log('will not taint the canvas anymore')
ctx.drawImage(img, 0, 0);
// and if we try now to capture a new stream, we have a clear error
const stream2 = canvas.captureStream();
}
img.src = "https://66.media.tumblr.com/84d332cafeb1052c477c979281e5713b/tumblr_owe3l0tkCj1wxdq3zo1_1280.jpg";
ctx.fillRect(0,0,20,20);Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
<video id="vid" controls autoplay muted></video>Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3112 次 |
| 最近记录: |