CanvasCaptureMediaStream / MediaRecorder Кадровая синхронизация
При использовании CanvasCaptureMediaStream и MediaRecorder, есть ли способ получить событие для каждого кадра?
Что мне нужно, мало чем отличается requestAnimationFrame()
, но мне это нужно для CanvasCaptureMediaStream (и / или MediaRecorder), а не окна. MediaRecorder может работать с частотой кадров, отличной от окна (возможно, с нерегулярно делимой скоростью, например, 25 кадров в секунду против 60 кадров в секунду), поэтому я хочу обновить холст с частотой кадров, а не с окном.
1 ответ
Этот пример в настоящее время полностью работает только с FireFox, поскольку chrome просто останавливает поток холста, когда вкладка размыта... (возможно, это связано с этой ошибкой, но, похоже, мой таймер работает, но не запись...)
[Редактировать]: на самом деле теперь он работает только в Chrome, так как они исправили эту ошибку, но больше не в FF из-за этой (вызванной e10s).
Похоже, что в MediaStream не было никакого события, сообщающего, когда ему был визуализирован кадр, ни в MediaRecorder.
Даже currentTime
свойство MediaStream (в настоящее время доступно только в FF), похоже, не изменяется соответствующим образом с аргументом fps, переданным в captureStream()
метод.
Но вам нужен надежный таймер, который не потеряет свою частоту, когда текущая вкладка не сфокусирована (что происходит для rAF).
К счастью, API WebAudio также имеет высокоточный таймер, основанный на аппаратных часах, а не на частоте обновления экрана.
Таким образом, мы можем предложить альтернативный временной цикл, способный сохранять частоту, даже когда вкладка размыта.
/*
An alternative timing loop, based on AudioContext's clock
@arg callback : a callback function
with the audioContext's currentTime passed as unique argument
@arg frequency : float in ms;
@returns : a stop function
*/
function audioTimerLoop(callback, frequency) {
// AudioContext time parameters are in seconds
var freq = frequency / 1000;
var aCtx = new AudioContext();
// Chrome needs our oscillator node to be attached to the destination
// So we create a silent Gain Node
var silence = aCtx.createGain();
silence.gain.value = 0;
silence.connect(aCtx.destination);
onOSCend();
var stopped = false;
function onOSCend() {
osc = aCtx.createOscillator();
osc.onended = onOSCend;
osc.connect(silence);
osc.start(0);
osc.stop(aCtx.currentTime + freq);
callback(aCtx.currentTime);
if (stopped) {
osc.onended = function() {
return;
};
}
};
// return a function to stop our loop
return function() {
stopped = true;
};
}
function start() {
// start our loop @25fps
var stopAnim = audioTimerLoop(anim, 1000 / 25);
// maximum stream rate set as 25 fps
cStream = canvas.captureStream(25);
let chunks = [];
var recorder = new MediaRecorder(cStream);
recorder.ondataavailable = e => chunks.push(e.data);
recorder.onstop = e => {
// we can stop our loop
stopAnim();
var url = URL.createObjectURL(new Blob(chunks));
var v = document.createElement('video');
v.src = url;
v.controls = true;
document.body.appendChild(v);
}
recorder.start();
// stops the recorder in 20s, try to change tab during this time
setTimeout(function() {
recorder.stop();
}, 20000)
}
// make something move on the canvas
var ctx = canvas.getContext('2d');
var x = 0;
function anim() {
x = (x + 2) % (canvas.width + 100);
ctx.fillStyle = 'ivory';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.fillRect(x - 50, 20, 50, 50)
};
start();
<canvas id="canvas" width="500" height="200"></canvas>
Nota Bene:
В этом примере я установил частоту 25 кадров в секунду, но мы можем установить частоту 60 кадров в секунду, и она, кажется, работает правильно даже на моем старом ноутбуке, по крайней мере, с такой простой анимацией.