2017-05-24 8 views
0

저는 캔버스에 비디오를 렌더링 한 온라인 웹 응용 프로그램을 작성하고 canvas.captureStream() 및 mediaRecorder를 사용하여 캔버스를 기록합니다. 문제는 사용자가 탭을 전환하거나 창을 최소화 할 때 캔버스가 정지한다는 것입니다. webWorkerSetInterval (Hacktimer.js)을 사용하면서 애니메이션이 계속 실행됩니다. 크롬 당 그들은 아직 해결책을 제공하지 않았다 https://bugs.chromium.org/p/chromium/issues/detail?id=639105.녹음을 위해 탭이 비활성/배경으로 표시 될 때 Canvas가 다시 칠하지 않음 (WebGL)

해결 방법을 제안 할만한 사람이 있습니까? 나는 최소화 할 수는 없지만 실패한 새 창을 열어 보았습니다. . 기록은 윈도우를 전환 할 때 중지

+0

의 사용 가능한 복제 [CanvasCaptureMediaStream/MediaRecorder :

쉬운 해결 방법은, 스트림 소스로 오프 스크린 2D 컨텍스트를 사용하고,이 2D 컨텍스트에 우리의 WebGL을 캔버스을 그릴 것입니다 프레임 동기화] (https://stackoverflow.com/questions/40687010/canvascapturemediastream-mediarecorder-frame-synchronization) – Kaiido

+0

Ps를 닫으십시오 : 질문이 다르게 보이지만 핵심 문제는 거의 동일합니다 (rAF 한계가 있음).), 솔루션은 아마도 너도. – Kaiido

+0

rAF 제한은 이미 WebWorker를 사용하여 제거되었습니다. 하지만 당신의 솔루션이 지금 그것을 닫지 않으려 고 노력 중입니다. 또한 숨겨진 캔버스는 아니지만 비활성 탭과 관련이 있습니다. –

답변

2

NB (탭 전환되거나 최소화하는 경우에만 중단)하지 않는다 : 지금이 질문 구체적 WebGL이 상황을 처리하도록 편집되었는지
그것이하지 않을 완전히 previous answer의 복제물이며, 실제로 웹 GL 컨텍스트에서는 작동하지 않습니다. 그러나 크롬 버그 때문에 ...

그래서이 대답은 크롬에서 수정을 기다리는 동안이 버그를 해결하는 방법을 보여줍니다.


Timed 루프를 만들 수있는 웹 오디오 API의 타이밍 방법의 연결 대답했다 사용은 화면 재생 속도 나 창/탭의 가시성에 묶여 있지.

그러나 헤더에서 말했듯이, 현재 크롬의 webgl 컨텍스트에서는 작동하지 않습니다.

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); 
 
    \t ctx.drawImage(webgl_renderer.domElement, 0,0); 
 
    } 
 
\t 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);}; 
 

 
}());
body { 
 
    margin: 0; 
 
    background: #000; 
 
} 
 
button{ 
 
    position: absolute; 
 
    top: 0; 
 
    }
<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>

+0

괜찮은 해킹입니다. 그러나 drawImage는 약 80-90ms가 걸리는 내 situtaiton의 병목입니다. 최소한 8fps의 녹화를하고 싶습니다. 그러나이 방법을 사용하면 매우 느린 프레임 속도에서도 많은 프레임을 건너 뛸 수 있습니다. 최적화를 제안 할 수 있습니까? 2 차원 캔버스와 gl 캔버스를 바인딩하거나 context.readPixels를 사용하여 프로세스 속도를 높일 수 있습니까? –

+0

@AmriteshAnand, 80ms oO !? 캔버스의 크기는 얼마입니까?스 니펫과 같은 애니메이션을 사용했지만 2500 * 2500 캔버스가있는 경우 'clearRect + drawImage'에서 얻은 가장 긴 시간은 평균 1.22ms 인 10ms였습니다. 나도 60fps를 얻지 못했지만 범인은 drawImage가 아닌 webgl 부분에 있었다. 그리고 AFAIK, drawImage는 2 차원 웹 위에 캔버스를 그리는 가장 빠른 방법입니다. – Kaiido

+0

고마워, 내가 맥에서 시도하고 좋은 성능을 준. –