1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
<!DOCTYPE HTML>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Canvas2D test: CaptureStream() with throttled rAF</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="captureStream_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<body>
<script>
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin");
// CaptureStreamTestHelper holding utility test functions.
const h = new CaptureStreamTestHelper2D();
async function measureRequestAnimationFrameRate() {
const frameRate = await new Promise(resolve => {
let start;
let count;
const tick = time => {
if (!start) {
start = time;
count = 0;
} else {
count += 1;
}
if (time - start > 1000) {
// One second has passed, break.
resolve(count / ((time - start) / 1000));
return;
}
window.requestAnimationFrame(tick);
};
window.requestAnimationFrame(tick);
});
return frameRate;
}
async function measureSetTimeoutRate() {
const start = performance.now();
const COUNT = 5;
for(let i = 0; i < COUNT; ++i) {
await new Promise(resolve => setTimeout(resolve, 0));
}
return COUNT / ((performance.now() - start) / 1000);
}
async function measureCanvasCaptureFrameRate(captureRate) {
// Canvas element captured by streams.
const c = h.createAndAppendElement('canvas', 'c');
// Since we are in a background tab, the video element won't get composited,
// so we cannot look for a frame count there. Instead we use RTCPeerConnection
// and RTCOutboundRtpStreamStats.framesEncoded.
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
// Add the canvas.captureStream track.
const ctx = c.getContext('2d');
const [track] = c.captureStream(captureRate).getTracks();
const sender = pc1.addTrack(track);
// Ice candidates signaling
for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) {
local.addEventListener("icecandidate", ({candidate}) => {
if (!candidate || remote.signalingState == "closed") {
return;
}
remote.addIceCandidate(candidate);
});
}
// Offer/Answer exchange
await pc1.setLocalDescription(await pc1.createOffer());
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription(await pc2.createAnswer());
await pc1.setRemoteDescription(pc2.localDescription);
// Wait for connection
while ([pc1, pc2].some(pc => pc.iceConnectionState == "new" ||
pc.iceConnectionState == "checking")) {
await Promise.any(
[pc1, pc2].map(p => new Promise(r => p.oniceconnectionstatechange = r)));
}
for (const [pc, name] of [[pc1, "pc1"], [pc2, "pc2"]]) {
ok(["connected", "completed"].includes(pc.iceConnectionState),
`${name} connection established (${pc.iceConnectionState})`);
}
// Draw to the canvas
const intervalMillis = 1000 / 60;
const getFrameCount = async () => {
const stats = await sender.getStats();
const outbound =
[...stats.values()].find(({type}) => type == "outbound-rtp");
return outbound?.framesEncoded ?? 0;
};
// Wait for frame count change to ensure sender is working.
while (await getFrameCount() == 0) {
h.drawColor(c, h.green);
await new Promise(resolve => setTimeout(resolve, intervalMillis));
}
const startFrameCount = await getFrameCount();
const start = performance.now();
let end = start;
while(end - start <= 1000) {
h.drawColor(c, h.green);
await new Promise(resolve => setTimeout(resolve, intervalMillis));
end = performance.now();
}
const framerate = (await getFrameCount() - startFrameCount) / ((end - start) / 1000);
pc1.close();
pc2.close();
return framerate;
}
(async () => {
// Disable background timer throttling so we can use setTimeout to draw to the
// canvas while the refresh driver is throttled.
await SpecialPowers.pushPrefEnv({
set: [
["dom.timeout.enable_budget_timer_throttling", false],
["dom.min_background_timeout_value", 0],
["dom.min_background_timeout_value_without_budget_throttling", 0],
["browser.link.open_newwindow", 3], // window.open in new tab
],
});
// Throttle the canvas' refresh driver by opening a new foreground tab
const foregroundTab = window.open("about:blank");
// Let the throttling take effect
await new Promise(r => setTimeout(r, 500));
const rAFRate = await measureRequestAnimationFrameRate();
ok(rAFRate < 5, `rAF framerate is at ${rAFRate} fps`);
const setTimeoutRate = await measureSetTimeoutRate();
ok(setTimeoutRate > 30, `setTimeout rate is at ${setTimeoutRate} tps`);
const autoRate = await measureCanvasCaptureFrameRate();
ok(autoRate > 5, `captureStream() framerate is at ${autoRate} fps`);
const cappedRate = await measureCanvasCaptureFrameRate(10);
ok(cappedRate > 5, `captureStream(10) framerate is at ${cappedRate} fps`);
foregroundTab.close();
SimpleTest.finish();
})();
</script>
|