Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: os == 'android' OR apple_silicon OR os == 'mac' && os_version == '14.70' && processor == 'x86_64'
- This test failed 14 times in the preceding 30 days. quicksearch this test
- Manifest: dom/media/webrtc/tests/mochitests/mochitest_getusermedia.toml
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
title: "getUserMedia Basic Screenshare Test",
bug: "1211656",
visible: true,
});
const {AppConstants} = SpecialPowers.ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
// Since the MacOS backend captures in the wrong rgb color profile we need
// large thresholds there, and they vary greatly by color. We define
// thresholds per platform and color here to still allow the test to run.
// Since the colors used (red, green, blue, white) consist only of "pure"
// components (0 or 255 for each component), the high thresholds on Mac will
// still be able to catch an error where the image is corrupt, or if frames
// don't flow.
const thresholds = {
// macos: captures in the display rgb color profile, which we treat as
// sRGB, which is most likely wrong. These thresholds are needed currently
"macosx": { "red": 120, "green": 135, "blue": 35, "white": 10 },
// windows: rounding errors in 1) conversion to I420 (the capture),
// 2) downscaling, 3) conversion to RGB (for rendering).
"win": { "red": 5, "green": 5, "blue": 10, "white": 5 },
// linux: rounding errors in 1) conversion to I420 (the capture),
// 2) downscaling, 3) conversion to RGB (for rendering).
"linux": { "red": 5, "green": 5, "blue": 10, "white": 5 },
// android: we don't have a screen capture backend.
"android": { "red": 0, "green": 0, "blue": 0, "white": 0 },
// other: here just because it's supported by AppConstants.platform.
"other": { "red": 0, "green": 0, "blue": 0, "white": 0 },
};
const verifyScreenshare =
async (video, helper, upleft, upright, downleft, downright) => {
if (video.readyState < video.HAVE_CURRENT_DATA) {
info("Waiting for data");
await new Promise(r => video.onloadeddata = r);
}
// We assume video size will not change. Offsets help to account for a
// square fullscreen-canvas, while the screen is rectangular.
const offsetX = Math.max(0, video.videoWidth - video.videoHeight) / 2;
const offsetY = Math.max(0, video.videoHeight - video.videoWidth) / 2;
const verifyAround = async (internalX, internalY, color) => {
// Pick a couple of samples around a coordinate to check for a color.
// We check multiple rows and columns, to avoid most artifact issues.
let areaSamples = [
{dx: 0, dy: 0},
{dx: 1, dy: 3},
{dx: 8, dy: 5},
];
const threshold = thresholds[AppConstants.platform][color.name];
for (let {dx, dy} of areaSamples) {
const x = offsetX + dx + internalX;
const y = offsetY + dy + internalY;
info(`Checking pixel (${[x,y]}) of total resolution `
+ `${video.videoWidth}x${video.videoHeight} against ${color.name}.`);
let lastPixel = [-1, -1, -1, -1];
await helper.waitForPixel(video, px => {
lastPixel = Array.from(px);
return helper.isPixel(px, color, threshold);
}, {
offsetX: x,
offsetY: y,
cancel: wait(30000).then(_ =>
new Error(`Checking ${[x,y]} against ${color.name} timed out. ` +
`Got [${lastPixel}]. Threshold ${threshold}.`)),
});
ok(true, `Pixel (${[x,y]}) passed. Got [${lastPixel}].`);
}
};
const screenSizeSq = Math.min(video.videoWidth, video.videoHeight);
info("Waiting for upper left quadrant to become " + upleft.name);
await verifyAround(screenSizeSq / 4, screenSizeSq / 4, upleft);
info("Waiting for upper right quadrant to become " + upright.name);
await verifyAround(screenSizeSq * 3 / 4, screenSizeSq / 4, upright);
info("Waiting for lower left quadrant to become " + downleft.name);
await verifyAround(screenSizeSq / 4, screenSizeSq * 3 / 4, downleft);
info("Waiting for lower right quadrant to become " + downright.name);
await verifyAround(screenSizeSq * 3 / 4, screenSizeSq * 3 / 4, downright);
};
/**
* Run a test to verify that we can complete a start and stop media playback
* cycle for a screenshare MediaStream on a video HTMLMediaElement.
*/
runTest(async function () {
await pushPrefs(
["full-screen-api.enabled", true],
["full-screen-api.allow-trusted-requests-only", false],
["full-screen-api.transition-duration.enter", "0 0"],
["full-screen-api.transition-duration.leave", "0 0"],
);
// Improve real estate for screenshots
const test = document.getElementById("test");
test.setAttribute("style", "height:0;margin:0;");
const display = document.getElementById("display");
display.setAttribute("style", "margin:0;");
const testVideo = createMediaElement('video', 'testVideo');
testVideo.removeAttribute("width");
testVideo.removeAttribute("height");
testVideo.setAttribute("style", "max-height:240px;");
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 20;
document.getElementById("content").appendChild(canvas);
const draw = ([upleft, upright, downleft, downright]) => {
helper.drawColor(canvas, helper[upleft], {offsetX: 0, offsetY: 0});
helper.drawColor(canvas, helper[upright], {offsetX: 10, offsetY: 0});
helper.drawColor(canvas, helper[downleft], {offsetX: 0, offsetY: 10});
helper.drawColor(canvas, helper[downright], {offsetX: 10, offsetY: 10});
};
const helper = new CaptureStreamTestHelper2D(1, 1);
const doVerify = async (stream, [upleft, upright, downleft, downright]) => {
// Reset from potential earlier verification runs.
testVideo.srcObject = null;
const playback = new MediaStreamPlayback(testVideo, stream);
playback.startMedia();
await playback.verifyPlaying();
const settings = stream.getTracks()[0].getSettings();
is(settings.width, testVideo.videoWidth,
"Width setting should match video width");
is(settings.height, testVideo.videoHeight,
"Height setting should match video height");
await SpecialPowers.wrap(canvas).requestFullscreen();
try {
await verifyScreenshare(testVideo, helper, helper[upleft], helper[upright],
helper[downleft], helper[downright]);
} finally {
await playback.stopTracksForStreamInMediaPlayback();
await SpecialPowers.wrap(document).exitFullscreen();
// We wait a bit extra here to make sure we have completely left
// fullscreen when the --screenshot-on-fail screenshot is captured.
await wait(300);
}
};
info("Testing screenshare without constraints");
SpecialPowers.wrap(document).notifyUserGestureActivation();
let stream = await getUserMedia({video: {mediaSource: "screen"}});
let settings = stream.getTracks()[0].getSettings();
ok(settings.width <= 8192,
ok(settings.height <= 8192,
let colors = ["red", "blue", "green", "white"];
draw(colors);
await doVerify(stream, colors);
const screenWidth = testVideo.videoWidth;
const screenHeight = testVideo.videoHeight;
info("Testing screenshare with size and framerate constraints");
SpecialPowers.wrap(document).notifyUserGestureActivation();
for (const track of stream.getTracks()) {
track.stop();
}
stream = await getUserMedia({
video: {
mediaSource: 'screen',
width: {
min: '10',
max: '100'
},
height: {
min: '10',
max: '100'
},
frameRate: {
min: '10',
max: '15'
},
},
});
settings = stream.getTracks()[0].getSettings();
ok(settings.width == 0 || (settings.width >= 10 && settings.width <= 100),
ok(settings.height == 0 || (settings.height >= 10 && settings.height <= 100),
colors = ["green", "red", "white", "blue"];
draw(colors);
const streamClone = stream.clone();
await doVerify(streamClone, colors);
settings = stream.getTracks()[0].getSettings();
ok(settings.width >= 10 && settings.width <= 100,
`Width setting ${settings.width} should be within constraints`);
ok(settings.height >= 10 && settings.height <= 100,
`Height setting ${settings.height} should be within constraints`);
is(settings.width, testVideo.videoWidth,
"Width setting should match video width");
is(settings.height, testVideo.videoHeight,
"Height setting should match video height");
let expectedHeight = (screenHeight * settings.width) / screenWidth;
ok(Math.abs(expectedHeight - settings.height) <= 1,
"Aspect ratio after constrained gUM should be close enough");
info("Testing modifying screenshare with applyConstraints");
testVideo.srcObject = stream;
testVideo.play();
await new Promise(r => testVideo.onloadeddata = r);
const resize = haveEvent(
testVideo, "resize", wait(5000, new Error("Timeout waiting for resize")));
await stream.getVideoTracks()[0].applyConstraints({
mediaSource: 'screen',
width: 200,
height: 200,
frameRate: {
min: '5',
max: '10'
}
});
// getSettings() should report correct size as soon as applyConstraints()
// something sane.
const newSettings = stream.getTracks()[0].getSettings();
ok(newSettings.width > settings.width && newSettings.width < screenWidth,
`Width setting ${newSettings.width} should have increased after applyConstraints`);
ok(newSettings.height > settings.height && newSettings.height < screenHeight,
`Height setting ${newSettings.height} should have increased after applyConstraints`);
await resize;
settings = stream.getTracks()[0].getSettings();
ok(settings.width > 100 && settings.width < screenWidth,
`Width setting ${settings.width} should have increased after first frame after applyConstraints`);
ok(settings.height > 100 && settings.height < screenHeight,
`Height setting ${settings.height} should have increased after first frame after applyConstraints`);
is(settings.width, testVideo.videoWidth,
"Width setting should match video width");
is(settings.height, testVideo.videoHeight,
"Height setting should match video height");
expectedHeight = (screenHeight * settings.width) / screenWidth;
ok(Math.abs(expectedHeight - settings.height) <= 1,
"Aspect ratio after applying constraints should be close enough");
colors = ["white", "green", "blue", "red"];
draw(colors);
await doVerify(stream, colors);
for (const track of [...stream.getTracks(), ...streamClone.getTracks()]) {
track.stop();
}
});
</script>
</pre>
</body>
</html>