Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!doctype html>
<title>getUserMedia MediaStreamTrack video resizeMode. Assumes Mozilla's fake camera source with 480p and 720p capabilities.</title>
<meta name="timeout" content="long">
<meta name="variant" content="?resizeMode=enabled">
<meta name="variant" content="?resizeMode=disabled">
<p class="instructions">When prompted, accept to share your video stream.</p>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/resources/testdriver.js></script>
<script src=/resources/testdriver-vendor.js></script>
<script src=settings-helper.js></script>
<script src=video-test-helper.js></script>
<script>
"use strict"
function doTest() {
const resizeModeEnabled =
new URLSearchParams(window.location.search).get("resizeMode") == "enabled";
// Native capabilities supported by the fake camera.
const nativeLow = {width: 640, height: 480, frameRate: 30, resizeMode: "none"};
const nativeHigh = {width: 1280, height: 720, frameRate: 10, resizeMode: "none"};
[
[{resizeMode: "none", width: 500}, nativeLow],
[{resizeMode: "none", height: 500}, nativeLow],
[{resizeMode: "none", width: 500, height: 500}, nativeLow],
[{resizeMode: "none", frameRate: 50}, nativeLow],
[{resizeMode: "none", width: 500, height: 500, frameRate: 50}, nativeLow],
[{resizeMode: "none", width: 1000}, nativeHigh],
[{resizeMode: "none", height: 1000}, nativeHigh],
[{resizeMode: "none", width: 1000, height: 1000}, nativeHigh],
[{resizeMode: "none", frameRate: 1}, nativeHigh, [3, 12]],
[{resizeMode: "none", width: 1000, height: 1000, frameRate: 1}, nativeHigh],
[
{resizeMode: "crop-and-scale"},
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", height: 500},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10} :
{width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", width: {min: 500}, height: {max: 200}},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 500, height: 200, frameRate: 30} :
"OverconstrainedError"
],
[
{resizeMode: "crop-and-scale", frameRate: 50},
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", width: 10000, frameRate: {min: 30}},
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", frameRate: {exact: 5}},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 5} :
"OverconstrainedError",
[2, 7]
],
].forEach(([video, expected, testFramerate]) => promise_test(async t => {
let stream;
try {
stream = await navigator.mediaDevices.getUserMedia({video});
} catch(e) {
assert_equals(e.name, expected);
return;
}
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
const settings = track.getSettings();
if (!resizeModeEnabled) {
expected.resizeMode = undefined;
}
for (const key of Object.keys(expected)) {
assert_equals(settings[key], expected[key], key);
}
if (testFramerate) {
const [low, high] = testFramerate;
await test_framerate_between_exclusive(t, track, low, high);
}
}, `gUM gets ${JSON.stringify(expected)} mode by ${JSON.stringify(video)}`));
promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({video: {resizeMode: "none", height: 720}});
const [track] = stream.getTracks();
const stream2 = await navigator.mediaDevices.getUserMedia({video: {resizeMode: "crop-and-scale", height: 300}});
const [track2] = stream2.getTracks();
t.add_cleanup(() => {
track.stop();
track2.stop();
});
const settings = track.getSettings();
assert_equals(settings.resizeMode, resizeModeEnabled ? "none" : undefined, "track resizeMode");
assert_equals(settings.width, 1280, "track width");
assert_equals(settings.height, 720, "track height");
assert_equals(settings.frameRate, 10, "track framerate");
const settings2 = track2.getSettings();
assert_equals(settings2.resizeMode, resizeModeEnabled ? "crop-and-scale" : undefined, "track2 resizeMode");
assert_equals(settings2.width, resizeModeEnabled ? 400 : 640, "track2 width");
assert_equals(settings2.height, resizeModeEnabled ? 300 : 480, "track2 height");
// Bug 1988466: source is configured at 10fps so we cannot completely
// masquerade the selected capability.
assert_equals(settings2.frameRate, 30, "track2 framerate");
}, `gUM gets expected downscaling with competing capabilities`);
promise_test(async t => {
try {
const stream = await navigator.mediaDevices.getUserMedia(
{video: {resizeMode: "none", width: {min: 2000}}}
);
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("gUM is rejected with impossible width");
}, "gUM is rejected by resizeMode none and impossible min-width");
promise_test(async t => {
try {
const stream = await navigator.mediaDevices.getUserMedia(
{video: {resizeMode: "none", width: {max: 200}}}
);
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("gUM is rejected with impossible width");
}, "gUM is rejected by resizeMode none and impossible max-width");
promise_test(async t => {
try {
const stream = await navigator.mediaDevices.getUserMedia(
{video: {resizeMode: "crop-and-scale", width: {min: 2000}}}
);
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("gUM is rejected with impossible width");
}, "gUM is rejected by resizeMode crop-and-scale and impossible width");
promise_test(async t => {
try {
const stream = await navigator.mediaDevices.getUserMedia(
{video: {resizeMode: "crop-and-scale", frameRate: {min: 50}}}
);
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("gUM is rejected with impossible fps");
}, "gUM is rejected by resizeMode crop-and-scale and impossible fps");
[
[{resizeMode: "none", width: 500}, nativeLow],
[{resizeMode: "none", height: 500}, nativeLow],
[{resizeMode: "none", width: 500, height: 500}, nativeLow],
[{resizeMode: "none", frameRate: 50}, nativeLow],
[{resizeMode: "none", width: 500, height: 500, frameRate: 50}, nativeLow],
[{resizeMode: "none", width: 1000}, nativeHigh],
[{resizeMode: "none", height: 1000}, nativeHigh],
[{resizeMode: "none", width: 1000, height: 1000}, nativeHigh],
[{resizeMode: "none", frameRate: 1}, nativeHigh, [3, 12]],
[{resizeMode: "none", width: 1000, height: 1000, frameRate: 1}, nativeHigh],
[
{resizeMode: "crop-and-scale"},
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", height: 400},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 533, height: 400, frameRate: 30} :
{width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", height: 500},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10} :
{width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", height: {exact: 500}},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10} :
"OverconstrainedError"
],
[
{resizeMode: "crop-and-scale", width: {min: 500}, height: {max: 200}},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 500, height: 200, frameRate: 30} :
"OverconstrainedError"
],
[
{resizeMode: "crop-and-scale", frameRate: 50},
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", width: 10000, frameRate: {min: 30}},
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
],
[
{resizeMode: "crop-and-scale", frameRate: {exact: 5}},
resizeModeEnabled ?
{resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 5} :
"OverconstrainedError",
[2, 7]
],
].forEach(([video, expected, testFramerate]) => promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
try {
await track.applyConstraints(video);
} catch(e) {
assert_equals(e.name, expected);
return;
}
const settings = track.getSettings();
if (!resizeModeEnabled) {
expected.resizeMode = undefined;
}
for (const key of Object.keys(expected)) {
assert_equals(settings[key], expected[key], key);
}
if (testFramerate) {
const [low, high] = testFramerate;
await test_framerate_between_exclusive(t, track, low, high);
}
}, `applyConstraints gets ${JSON.stringify(expected)} mode by ${JSON.stringify(video)}`));
promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
try {
await track.applyConstraints({resizeMode: "none", width: {min: 2000}})
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("applyConstraints is rejected with impossible width");
}, "applyConstraints is rejected by resizeMode none and impossible min-width");
promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
try {
await track.applyConstraints({resizeMode: "none", width: {max: 200}})
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("applyConstraints is rejected with impossible width");
}, "applyConstraints is rejected by resizeMode none and impossible max-width");
promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
try {
await track.applyConstraints({resizeMode: "crop-and-scale", width: {min: 2000}})
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("applyConstraints is rejected with impossible width");
}, "applyConstraints is rejected by resizeMode crop-and-scale and impossible width");
promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const [track] = stream.getTracks();
t.add_cleanup(() => track.stop());
try {
await track.applyConstraints({resizeMode: "crop-and-scale", frameRate: {min: 50}});
} catch(e) {
assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
return;
}
assert_unreached("applyConstraints is rejected with impossible fps");
}, "applyConstraints is rejected by resizeMode crop-and-scale impossible fps");
}
window.addEventListener("load", doTest, {once: true});
</script>