Canvas 使用 captureStream 和 mediaRecorder 进行录制
Canvas recording using captureStream and mediaRecorder
如何录制来自多个 canvas 的流?
即,当我将一个 canvas 更改为另一个时,它必须记录活动 canvas 继续到第一个。
我这样做过:
stream = canvas.captureStream();
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(10);
function handleDataAvailable(event) {
recordedBlobs.push(event.data);
}
但是当添加另一个流时,只记录了第一部分。我正在将记录的数据推送到全局数组。
在当前的实现中,您无法切换 MediaRecorder 流的录制轨道。
当您尝试这样做时,Firefox 会将您带到
的控制台
MediaRecorder does not support recording multiple tracks of the same type at this time.
而Chrome保持沉默并记录黑帧而不是第二轨...
var canvases = Array.prototype.slice.call(document.querySelectorAll('canvas')),
recordingStream,
current = 0,
chunks = [],
recorder,
switchInterval;
function startRecording() {
// first gather both canvases streams & extract the videoTracks
let streams = canvases.map((c) => {
return c.captureStream(30)
});
let tracks = streams.map((s) => {
return s.getVideoTracks()[0]
});
// create a new MediaStream with both tracks in it
// we don't use addTrack because of https://bugzilla.mozilla.org/show_bug.cgi?id=1296531
recordingStream = 'MediaStream' in window && new MediaStream(tracks) || new webkitMediaStream(tracks);
// init the MediaRecorder
recorder = new MediaRecorder(recordingStream);
recorder.ondataavailable = saveChunks;
recorder.onstop = exportVideo;
recorder.onerror = (e) => {
console.log(e.name)
};
recorder.start();
stopRec.disabled = false;
// switch the canvas to be recorder every 200ms
switchInterval = setInterval(switchStream, 200);
}
// switch mute one of the tracks, then the other
function switchStream() {
current = +!current;
var tracks = recordingStream.getVideoTracks();
tracks[current].enabled = true;
// commented because it seems FF doesn't support canvasTrack's method yet
// doesn't work in chrome even when there anyway
// tracks[current].requestFrame();
tracks[+!current].enabled = false;
}
function saveChunks(evt) {
// store our video's chunks
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
stopRec.onclick = function stopRecording() {
if (recorder.state !== 'recording') {
this.disabled = true;
return;
}
// stop everything
recorder.stop(); // this will trigger exportVideo
clearInterval(switchInterval);
stopCanvasAnim();
a.style.display = b.style.display = 'none';
this.parentNode.innerHTML = "";
}
function exportVideo() {
// we've got everything
vid.src = URL.createObjectURL(new Blob(chunks));
}
var stopCanvasAnim = (function initCanvasDrawing() {
// some fancy drawings
var aCtx = canvases[0].getContext('2d'),
bCtx = canvases[1].getContext('2d');
var objects = [],
w = canvases[0].width,
h = canvases[0].height;
aCtx.fillStyle = bCtx.fillStyle = 'ivory';
for (var i = 0; i < 100; i++) {
objects.push({
angle: Math.random() * 360,
x: 100 + (Math.random() * w / 2),
y: 100 + (Math.random() * h / 2),
radius: 10 + (Math.random() * 40),
speed: 1 + Math.random() * 20
});
}
var stop = false;
var draw = function() {
aCtx.fillRect(0, 0, w, h);
bCtx.fillRect(0, 0, w, h);
for (var n = 0; n < 100; n++) {
var entity = objects[n],
velY = Math.cos(entity.angle * Math.PI / 180) * entity.speed,
velX = Math.sin(entity.angle * Math.PI / 180) * entity.speed;
entity.x += velX;
entity.y -= velY;
aCtx.drawImage(imgA, entity.x, entity.y, entity.radius, entity.radius);
bCtx.drawImage(imgB, entity.x, entity.y, entity.radius, entity.radius);
entity.angle++;
}
if (!stop) {
requestAnimationFrame(draw);
}
}
var imgA = new Image();
var imgB = new Image();
imgA.onload = function() {
draw();
startRecording();
};
imgA.crossOrigin = imgB.crossOrigin = 'anonymous';
imgA.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
imgB.src = "https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png";
return function() {
stop = true;
};
})();
<p>
<button id="stopRec" disabled>stop recording</button>
</p>
<canvas id="a"></canvas>
<canvas id="b"></canvas>
<video id="vid" controls></video>
请注意,目前有一个关于此的 open issue on the w3c github project mediacapture-record。
但是,这个问题有一个简单的解决方法:
- 使用另一个
offscreen [hidden]* offscreen (chrome bug 已在最新的 58 canary 中修复) canvas,仅用于记录器,
- 在上面画出想要的canvas的边框
这样,没问题;-)
同样的解决方法也可以用于在同一个 MediaRecorder 上保存不同的视频。
var canvases = document.querySelectorAll('canvas'),
recordingCtx,
current = 0,
chunks = [],
recorder,
switchInterval;
// draw one of our canvas on a third one
function recordingAnim() {
recordingCtx.drawImage(canvases[current], 0, 0);
// if recorder is stopped, stop the animation
if (!recorder || recorder.state === 'recording') {
requestAnimationFrame(recordingAnim);
}
}
function startRecording() {
var recordingCanvas = canvases[0].cloneNode();
recordingCtx = recordingCanvas.getContext('2d');
recordingCanvas.id = "";
// chrome forces us to display the canvas in doc so it can be recorded,
// This bug has been fixed in chrome 58.0.3014.0
recordingCtx.canvas.style.height = 0;
document.body.appendChild(recordingCtx.canvas);
// draw one of the canvases on our recording one
recordingAnim();
// init the MediaRecorder
recorder = new MediaRecorder(recordingCtx.canvas.captureStream(30));
recorder.ondataavailable = saveChunks;
recorder.onstop = exportVideo;
recorder.start();
stopRec.onclick = stopRecording;
// switch the canvas to be recorder every 200ms
switchInterval = setInterval(switchStream, 200);
}
function saveChunks(evt) {
// store our final video's chunks
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
function stopRecording() {
// stop everything, this will trigger recorder.onstop
recorder.stop();
clearInterval(switchInterval);
stopCanvasAnim();
a.style.display = b.style.display = 'none';
this.parentNode.innerHTML = "";
recordingCtx.canvas.parentNode.removeChild(recordingCtx.canvas)
}
// when we've got everything
function exportVideo() {
vid.src = URL.createObjectURL(new Blob(chunks));
}
// switch between 1 and 0
function switchStream() {
current = +!current;
}
// some fancy drawings
var stopCanvasAnim = (function initCanvasDrawing() {
var aCtx = canvases[0].getContext('2d'),
bCtx = canvases[1].getContext('2d');
var objects = [],
w = canvases[0].width,
h = canvases[0].height;
aCtx.fillStyle = bCtx.fillStyle = 'ivory';
// taken from
for (var i = 0; i < 100; i++) {
objects.push({
angle: Math.random() * 360,
x: 100 + (Math.random() * w / 2),
y: 100 + (Math.random() * h / 2),
radius: 10 + (Math.random() * 40),
speed: 1 + Math.random() * 20
});
}
var stop = false;
var draw = function() {
aCtx.fillRect(0, 0, w, h);
bCtx.fillRect(0, 0, w, h);
for (var n = 0; n < 100; n++) {
var entity = objects[n],
velY = Math.cos(entity.angle * Math.PI / 180) * entity.speed,
velX = Math.sin(entity.angle * Math.PI / 180) * entity.speed;
entity.x += velX;
entity.y -= velY;
aCtx.drawImage(imgA, entity.x, entity.y, entity.radius, entity.radius);
bCtx.drawImage(imgB, entity.x, entity.y, entity.radius, entity.radius);
entity.angle++;
}
if (!stop) {
requestAnimationFrame(draw);
}
}
var imgA = new Image();
var imgB = new Image();
imgA.onload = function() {
draw();
startRecording();
};
imgA.crossOrigin = imgB.crossOrigin = 'anonymous';
imgA.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
imgB.src = "https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png";
return function() {
stop = true;
};
})();
<p>
<button id="stopRec">stop recording</button>
</p>
<canvas id="a"></canvas>
<canvas id="b"></canvas>
<video id="vid" controls></video>
如何录制来自多个 canvas 的流? 即,当我将一个 canvas 更改为另一个时,它必须记录活动 canvas 继续到第一个。
我这样做过:
stream = canvas.captureStream();
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(10);
function handleDataAvailable(event) {
recordedBlobs.push(event.data);
}
但是当添加另一个流时,只记录了第一部分。我正在将记录的数据推送到全局数组。
在当前的实现中,您无法切换 MediaRecorder 流的录制轨道。
当您尝试这样做时,Firefox 会将您带到
的控制台MediaRecorder does not support recording multiple tracks of the same type at this time.
而Chrome保持沉默并记录黑帧而不是第二轨...
var canvases = Array.prototype.slice.call(document.querySelectorAll('canvas')),
recordingStream,
current = 0,
chunks = [],
recorder,
switchInterval;
function startRecording() {
// first gather both canvases streams & extract the videoTracks
let streams = canvases.map((c) => {
return c.captureStream(30)
});
let tracks = streams.map((s) => {
return s.getVideoTracks()[0]
});
// create a new MediaStream with both tracks in it
// we don't use addTrack because of https://bugzilla.mozilla.org/show_bug.cgi?id=1296531
recordingStream = 'MediaStream' in window && new MediaStream(tracks) || new webkitMediaStream(tracks);
// init the MediaRecorder
recorder = new MediaRecorder(recordingStream);
recorder.ondataavailable = saveChunks;
recorder.onstop = exportVideo;
recorder.onerror = (e) => {
console.log(e.name)
};
recorder.start();
stopRec.disabled = false;
// switch the canvas to be recorder every 200ms
switchInterval = setInterval(switchStream, 200);
}
// switch mute one of the tracks, then the other
function switchStream() {
current = +!current;
var tracks = recordingStream.getVideoTracks();
tracks[current].enabled = true;
// commented because it seems FF doesn't support canvasTrack's method yet
// doesn't work in chrome even when there anyway
// tracks[current].requestFrame();
tracks[+!current].enabled = false;
}
function saveChunks(evt) {
// store our video's chunks
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
stopRec.onclick = function stopRecording() {
if (recorder.state !== 'recording') {
this.disabled = true;
return;
}
// stop everything
recorder.stop(); // this will trigger exportVideo
clearInterval(switchInterval);
stopCanvasAnim();
a.style.display = b.style.display = 'none';
this.parentNode.innerHTML = "";
}
function exportVideo() {
// we've got everything
vid.src = URL.createObjectURL(new Blob(chunks));
}
var stopCanvasAnim = (function initCanvasDrawing() {
// some fancy drawings
var aCtx = canvases[0].getContext('2d'),
bCtx = canvases[1].getContext('2d');
var objects = [],
w = canvases[0].width,
h = canvases[0].height;
aCtx.fillStyle = bCtx.fillStyle = 'ivory';
for (var i = 0; i < 100; i++) {
objects.push({
angle: Math.random() * 360,
x: 100 + (Math.random() * w / 2),
y: 100 + (Math.random() * h / 2),
radius: 10 + (Math.random() * 40),
speed: 1 + Math.random() * 20
});
}
var stop = false;
var draw = function() {
aCtx.fillRect(0, 0, w, h);
bCtx.fillRect(0, 0, w, h);
for (var n = 0; n < 100; n++) {
var entity = objects[n],
velY = Math.cos(entity.angle * Math.PI / 180) * entity.speed,
velX = Math.sin(entity.angle * Math.PI / 180) * entity.speed;
entity.x += velX;
entity.y -= velY;
aCtx.drawImage(imgA, entity.x, entity.y, entity.radius, entity.radius);
bCtx.drawImage(imgB, entity.x, entity.y, entity.radius, entity.radius);
entity.angle++;
}
if (!stop) {
requestAnimationFrame(draw);
}
}
var imgA = new Image();
var imgB = new Image();
imgA.onload = function() {
draw();
startRecording();
};
imgA.crossOrigin = imgB.crossOrigin = 'anonymous';
imgA.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
imgB.src = "https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png";
return function() {
stop = true;
};
})();
<p>
<button id="stopRec" disabled>stop recording</button>
</p>
<canvas id="a"></canvas>
<canvas id="b"></canvas>
<video id="vid" controls></video>
请注意,目前有一个关于此的 open issue on the w3c github project mediacapture-record。
但是,这个问题有一个简单的解决方法:
- 使用另一个
offscreen[hidden]*offscreen (chrome bug 已在最新的 58 canary 中修复) canvas,仅用于记录器, - 在上面画出想要的canvas的边框
这样,没问题;-)
同样的解决方法也可以用于在同一个 MediaRecorder 上保存不同的视频。
var canvases = document.querySelectorAll('canvas'),
recordingCtx,
current = 0,
chunks = [],
recorder,
switchInterval;
// draw one of our canvas on a third one
function recordingAnim() {
recordingCtx.drawImage(canvases[current], 0, 0);
// if recorder is stopped, stop the animation
if (!recorder || recorder.state === 'recording') {
requestAnimationFrame(recordingAnim);
}
}
function startRecording() {
var recordingCanvas = canvases[0].cloneNode();
recordingCtx = recordingCanvas.getContext('2d');
recordingCanvas.id = "";
// chrome forces us to display the canvas in doc so it can be recorded,
// This bug has been fixed in chrome 58.0.3014.0
recordingCtx.canvas.style.height = 0;
document.body.appendChild(recordingCtx.canvas);
// draw one of the canvases on our recording one
recordingAnim();
// init the MediaRecorder
recorder = new MediaRecorder(recordingCtx.canvas.captureStream(30));
recorder.ondataavailable = saveChunks;
recorder.onstop = exportVideo;
recorder.start();
stopRec.onclick = stopRecording;
// switch the canvas to be recorder every 200ms
switchInterval = setInterval(switchStream, 200);
}
function saveChunks(evt) {
// store our final video's chunks
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
function stopRecording() {
// stop everything, this will trigger recorder.onstop
recorder.stop();
clearInterval(switchInterval);
stopCanvasAnim();
a.style.display = b.style.display = 'none';
this.parentNode.innerHTML = "";
recordingCtx.canvas.parentNode.removeChild(recordingCtx.canvas)
}
// when we've got everything
function exportVideo() {
vid.src = URL.createObjectURL(new Blob(chunks));
}
// switch between 1 and 0
function switchStream() {
current = +!current;
}
// some fancy drawings
var stopCanvasAnim = (function initCanvasDrawing() {
var aCtx = canvases[0].getContext('2d'),
bCtx = canvases[1].getContext('2d');
var objects = [],
w = canvases[0].width,
h = canvases[0].height;
aCtx.fillStyle = bCtx.fillStyle = 'ivory';
// taken from
for (var i = 0; i < 100; i++) {
objects.push({
angle: Math.random() * 360,
x: 100 + (Math.random() * w / 2),
y: 100 + (Math.random() * h / 2),
radius: 10 + (Math.random() * 40),
speed: 1 + Math.random() * 20
});
}
var stop = false;
var draw = function() {
aCtx.fillRect(0, 0, w, h);
bCtx.fillRect(0, 0, w, h);
for (var n = 0; n < 100; n++) {
var entity = objects[n],
velY = Math.cos(entity.angle * Math.PI / 180) * entity.speed,
velX = Math.sin(entity.angle * Math.PI / 180) * entity.speed;
entity.x += velX;
entity.y -= velY;
aCtx.drawImage(imgA, entity.x, entity.y, entity.radius, entity.radius);
bCtx.drawImage(imgB, entity.x, entity.y, entity.radius, entity.radius);
entity.angle++;
}
if (!stop) {
requestAnimationFrame(draw);
}
}
var imgA = new Image();
var imgB = new Image();
imgA.onload = function() {
draw();
startRecording();
};
imgA.crossOrigin = imgB.crossOrigin = 'anonymous';
imgA.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
imgB.src = "https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png";
return function() {
stop = true;
};
})();
<p>
<button id="stopRec">stop recording</button>
</p>
<canvas id="a"></canvas>
<canvas id="b"></canvas>
<video id="vid" controls></video>