CanvasCaptureMediaStream / MediaRecorder Frame Synchronization

Quando usi CanvasCaptureMediaStream e MediaRecorder , c’è un modo per ottenere un evento su ciascun frame?

Ciò di cui ho bisogno non è diverso da requestAnimationFrame() , ma ne ho bisogno per CanvasCaptureMediaStream (e / o MediaRecorder) e non per la finestra. MediaRecorder potrebbe essere in esecuzione a una frequenza fotogrammi diversa rispetto alla finestra (probabilmente con una frequenza non regolarmente divisibile, come 25 FPS vs 60 FPS), quindi voglio aggiornare la canvas alla frequenza fotogrammi piuttosto che alla finestra.

Questo esempio al momento funziona solo su FireFox , dato che chrome blocca semplicemente il stream del canvas quando la scheda è sfocata … (probabilmente correlato a questo bug , ma beh, il mio timer sembra funzionare ma non la registrazione …)

[Modifica] : in realtà ora funziona solo in chrome, poiché hanno corretto questo bug , ma non più in FF a causa di questo (causato da e10s).


Non sembra esserci alcun evento su MediaStream che ti permette di sapere quando un frame è stato reso ad esso, né sul MediaRecorder.

Anche la proprietà currentTime di MediaStream (attualmente disponibile solo in FF) non sembra cambiare di conseguenza con l’argomento fps passato nel metodo captureStream() .

Ma quello che sembri voler è un timer affidabile, che non perderà la sua frequenza quando cioè la scheda corrente non è focalizzata (cosa che accade per rAF).
Fortunatamente, l’API WebAudio ha anche un timer di alta precisione , basato sull’orologio hardware, piuttosto che sulla frequenza di aggiornamento dello schermo.

Quindi possiamo venire con un ciclo temporizzato alternativo, in grado di mantenere la sua frequenza anche quando la scheda è sfocata.

 /* 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();