Синхронизация кадров CanvasCaptureMediaStream / MediaRecorder

Есть ли способ получить событие на каждом кадре?

То, что мне нужно, мало чем отличается от requestAnimationFrame(), но мне нужно это для CanvasCaptureMediaStream (и / или MediaRecorder), а не для окна. MediaRecorder может работать с другой частотой кадров, чем окно (возможно, с нерегулярно делимой частотой, например 25 кадров в секунду против 60 кадров в секунду), поэтому я хочу обновить холст с его частотой кадров, а не с окном.


person Brad    schedule 18.11.2016    source источник


Ответы (1)


В настоящее время этот пример полностью работает только в FireFox, поскольку хром просто останавливает поток холста, когда вкладка размыта ... (вероятно, связано с эта ошибка, но, похоже, мой таймер работает, но не запись ... )

[Edit]: теперь он работает только в Chrome, так как они исправили эта ошибка, но больше не в FF из-за этот (вызванный e10s).


Похоже, что в MediaStream нет какого-либо события, сообщающего вам, когда в него был отрисован кадр, ни в MediaRecorder.

Даже свойство currentTime в MediaStream (в настоящее время доступно только в FF), похоже, не меняется соответственно с аргументом fps, переданным в методе captureStream().

Но, похоже, вам нужен надежный таймер, который не теряет свою частоту, когда текущая вкладка не сфокусирована (что происходит с rAF).
К счастью, в WebAudio API также есть высокоточный таймер, основанный на аппаратных часах, а не на частоте обновления экрана. .

Таким образом, мы можем предложить альтернативный временной цикл, способный сохранять свою частоту, даже когда вкладка размыта.

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

btn.onclick = start;
<button id="btn">begin</button>
<canvas id="canvas" width="500" height="200"></canvas>

Nota Bene:
В этом примере я установил частоту на 25 кадров в секунду, но мы можем установить ее на 60 кадров в секунду, и, похоже, она работает правильно даже на моем старом ноутбуке, по крайней мере, с такой простой анимацией .

person Kaiido    schedule 19.11.2016
comment
Интересное творческое решение. Я поэкспериментирую с этим. Спасибо! - person Brad; 19.11.2016
comment
Это решение больше не работает в текущих сборках Chrome, я бы предложил эту библиотеку: npmjs.com/package / worker-timers для любого вида фонового рендеринга / тайминга. - person Jacob Greenway; 30.06.2019
comment
@JacobGreenway здесь работает таймер. Что сломалось, так это холст для шага медиапотока. - person Kaiido; 30.06.2019