当Tab无效/背景以进行录制(WebGL)时,画布不会重新粉刷

Amr*_*and 7 javascript google-chrome canvas webgl

我正在构建在线 Web 应用程序,它在画布上呈现视频,然后使用 canvas.captureStream() 和 mediaRecorder 记录画布。问题是当用户切换选项卡或最小化窗口时,画布会冻结。我的动画一直在运行,因为我使用了 webWorkerSetInterval(Hacktimer.js)。根据 chrome 他们尚未提供解决方案https://bugs.chromium.org/p/chromium/issues/detail?id=639105

任何人都可以建议解决方法吗?我尝试了不允许最小化但不成功的新窗口。切换窗口时不停止录制。(仅在选项卡切换或最小化时停止)

Kai*_*ido 6


注意:
既然这个问题已经过专门编辑来处理 webgl 上下文,它可能不完全是这个先前答案的重复,它确实不适用于 webgl 上下文;但由于 chrome 的错误...

因此,此答案将向您展示如何解决此错误,同时等待 chrome 的修复。


链接的答案使用 WebAudio API 的计时方法来创建定时循环,与屏幕刷新率或窗口/选项卡的可见性无关。

但正如标题中所说,这目前不适用于 chrome 上的 webgl 上下文。

简单的解决方法是使用屏幕外 2d 上下文作为流源,并将我们的 webgl 画布绘制到这个 2d 上下文上:

function startRecording(webgl_renderer, render_func) {
  // create a clone of the webgl canvas
  var canvas = webgl_renderer.domElement.cloneNode();
  // init an 2D context
  var ctx = canvas.getContext('2d');
  function anim(){
    // render the webgl Animation
    render_func();
    // draw the wegbl canvas on our 2D one
    ctx.clearRect(0,0,canvas.width, canvas.height);
  	ctx.drawImage(webgl_renderer.domElement, 0,0);
  }
	var fps = 60;
  // start our loop @60fps
  var stopAnim = audioTimerLoop(anim, 1000 / fps);
  // maximum stream rate set as 60 fps
  var cStream = canvas.captureStream(fps);

  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);
  btn.parentNode.removeChild(btn);
}


/*
    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) {

  var freq = frequency / 1000;      // AudioContext time parameters are in seconds
  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;       // A flag to know when we'll stop the loop
  function onOSCend() {
    var osc = aCtx.createOscillator();
    osc.onended = onOSCend; // so we can loop
    osc.connect(silence);
    osc.start(0); // start it now
    osc.stop(aCtx.currentTime + freq); // stop it next frame
    callback(aCtx.currentTime); // one frame is done
    if (stopped) {  // user broke the loop
      osc.onended = function() {
        aCtx.close(); // clear the audioContext
        return;
      };
    }
  };
  // return a function to stop our loop
  return function() {
    stopped = true;
  };
}

/* global THREE */
/* Note that all rAF loop have been removed
   since they're now handled by our 'audioTimerLoop' */


(function() {

    'use strict';
    var WIDTH = 500, HEIGHT = 500;
    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(75, WIDTH / HEIGHT, 0.1, 1000);

    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(WIDTH , HEIGHT);
    document.body.appendChild(renderer.domElement);

    var geometry = new THREE.CubeGeometry(5, 5, 5);
    var material = new THREE.MeshLambertMaterial({
        color: 0x00fff0
    });
    var cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    camera.position.z = 12;
    
    var pointLight = new THREE.PointLight(0xFFFFFF);

    pointLight.position.x = 10;
    pointLight.position.y = 50;
    pointLight.position.z = 130;

    scene.add(pointLight);

    var render = function() {        
        var delta = Math.random() * (0.06 - 0.02) + 0.02;

        cube.rotation.x += delta;
        cube.rotation.y += delta;
        cube.rotation.z -= delta;

        renderer.render(scene, camera);
    };
    render();
    console.clear();
    
    btn.onclick = function(){startRecording(renderer, render);};

}());
Run Code Online (Sandbox Code Playgroud)
body {
    margin: 0;
    background: #000;
}
button{
  position: absolute;
  top: 0;
  }
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
<!-- Mobile devices need an user interaction to start the WebAudio API -->
<button id="btn">Start</button>
Run Code Online (Sandbox Code Playgroud)