| <!DOCTYPE HTML> |
| <html> |
| <head> |
| <script src="../../resources/testharness.js"></script> |
| <script src="../../resources/testharnessreport.js"></script> |
| </head> |
| <body> |
| <script> |
| |
| // If a constraint is set with applyConstraints, it should come back in |
| // getConstraints(). |
| promise_test(() => { |
| return navigator.mediaDevices.getUserMedia({video: {width: {exact: 800}}}) |
| .then(s => { |
| var track = s.getVideoTracks()[0]; |
| var constraints = track.getConstraints(); |
| assert_equals(Object.keys(constraints).length, 1); |
| assert_true(constraints.hasOwnProperty('width')); |
| assert_equals(constraints.width.exact, 800); |
| |
| return track.applyConstraints({width: {exact: 640}}) |
| .then(() => { |
| constraints = track.getConstraints(); |
| assert_equals(Object.keys(constraints).length, 1); |
| assert_true(constraints.hasOwnProperty('width')); |
| assert_equals(constraints.width.exact, 640); |
| }) |
| }); |
| }, 'applyConstraints() sets the value of a constraint set by getUserMedia()'); |
| |
| // The deviceId constraint must be rejected, since the source of a track cannot |
| // be changed with applyConstraints. |
| promise_test(test => { |
| return promise_rejects_js(test, |
| OverconstrainedError, |
| navigator.mediaDevices.getUserMedia({audio: true}) |
| .then(s => { |
| return s.getAudioTracks()[0].applyConstraints( |
| { deviceId: {exact: 'mydevice-id'}}); |
| })); |
| }, 'Attempting to change the deviceId with applyConstraints() fails'); |
| |
| promise_test(() => { |
| let track; |
| return navigator.mediaDevices.getUserMedia({video: true}) |
| .then(s => { |
| track = s.getVideoTracks()[0]; |
| return track.getSettings(); |
| }).then(settings => { |
| return track.applyConstraints( |
| { width: { exact: settings.width }, notKnownName: { exact: true }}) |
| }).then(() => { |
| constraints = track.getConstraints(); |
| assert_equals(Object.keys(constraints).length, 1); |
| assert_false(constraints.hasOwnProperty('notKnownName')); |
| }); |
| }, 'An unsupported constraint is ignored by applyConstraints()'); |
| |
| function constraintElementsEqual(a, b) { |
| if (a === b) |
| return true; |
| if (!(a instanceof Object)) |
| return false; |
| if (!(b instanceof Object)) |
| return false; |
| if (Object.keys(a).length != Object.keys(b).length) |
| return false; |
| for (var p in a) { |
| if (!a.hasOwnProperty(p)) |
| continue; // Skip prototypes and such things. |
| if (!b.hasOwnProperty(p)) |
| return false; |
| if (a[p] instanceof Object && b[p] instanceof Object) { |
| if (!constraintElementsEqual(a[p], b[p])) |
| return false; |
| continue; |
| } |
| if (a[p] !== b[p]) return false; // Simple types. |
| } |
| return true; |
| } |
| |
| promise_test(() => { |
| // Construct a constraint set that covers constraints that make sense for |
| // video. |
| const complexConstraintSet = { |
| width: { min: 30, max: 480 }, |
| height: { min: 30, max: 480, exact: 350 }, |
| }; |
| // These constraints are syntactically valid, but may cause rejection. |
| // They are included in an "advanced" constraint. |
| // The particular values chosen are picked to exercise multiple parser paths, |
| // but the most important is that all legal names are represented. |
| const ignorableConstraintSet = { |
| frameRate: { ideal: 30.0 }, |
| facingMode: { ideal: "user" }, |
| aspectRatio: { ideal: 1.3333333, exact: 1.4444 }, |
| |
| sampleRate: { ideal: 42, min: 31, max: 54 }, |
| sampleSize: 3, |
| echoCancellation: { ideal: false, exact: true }, |
| autoGainControl: { ideal: false, exact: true }, |
| noiseSuppression: { ideal: false, exact: true }, |
| latency: 0.22, |
| channelCount: 2, |
| deviceId: { ideal: ["foo", "fooz"] }, |
| groupId: ["bar", "baz"] |
| }; |
| let complexConstraints = complexConstraintSet; |
| complexConstraints.advanced = [ ignorableConstraintSet ]; |
| |
| return navigator.mediaDevices.getUserMedia({video: true}) |
| .then(s => { |
| var track = s.getVideoTracks()[0]; |
| return track.applyConstraints(complexConstraints).then(() => { |
| constraints = track.getConstraints(); |
| assert_true(constraintElementsEqual(constraints, complexConstraints), |
| "Unexpected result: In: " + |
| JSON.stringify(complexConstraints, null, 2) + |
| " Out: " + JSON.stringify(constraints, null, 2)); |
| }); |
| }); |
| }, 'All valid keys are returned for complex constraints'); |
| |
| // Syntax tests for constraints. |
| // These work by putting the constraints into an advanced constraint |
| // (so that they can be ignored), calling getUserMedia, and then |
| // inspecting the constraints. |
| // In advanced constraints, naked values mean "exact", and "exact" values |
| // are thus unwrapped, which is the opposite behavior from the "basic" |
| // constraint set (outside advanced). |
| function constraintSyntaxTestWithChange(name, constraints, expected_result) { |
| promise_test(() => { |
| return navigator.mediaDevices.getUserMedia( |
| {'video': true}) |
| .then(s => { |
| var track = s.getVideoTracks()[0]; |
| track.applyConstraints({ 'advanced': [ constraints ]}).then(() => { |
| var constraints_out = track.getConstraints().advanced[0]; |
| assert_true(constraintElementsEqual(expected_result, constraints_out), |
| "Unexpected result: Expected: " + |
| JSON.stringify(expected_result, null, 2) + |
| " Out: " + JSON.stringify(constraints_out, null, 2)); |
| }) |
| }) |
| }, name); |
| } |
| |
| function constraintSyntaxTest(name, constraints) { |
| constraintSyntaxTestWithChange(name, constraints, constraints); |
| } |
| |
| constraintSyntaxTest('Simple integer', { height: 42 }); |
| constraintSyntaxTest('Ideal integer', { height: { ideal: 42 }}); |
| constraintSyntaxTest('Min/max integer', { height: { min: 42, max: 43 }}); |
| constraintSyntaxTestWithChange('Exact unwrapped integer', |
| { height: { exact: 42 } }, { height: 42 }); |
| |
| constraintSyntaxTest('Simple double', { aspectRatio: 1.5 }); |
| constraintSyntaxTest('Ideal double', { aspectRatio: { ideal: 1.5 }}); |
| constraintSyntaxTest('Min/max double', { aspectRatio: { min: 1.5, max: 2.0 }}); |
| constraintSyntaxTestWithChange( |
| 'Exact unwrapped double', |
| { aspectRatio: { exact: 1.5 } }, { aspectRatio: 1.5 }); |
| |
| constraintSyntaxTest('Simple String', { facingMode: "user1" }); |
| constraintSyntaxTest('Ideal String', { facingMode: { ideal: "user2" }}); |
| constraintSyntaxTest('Multiple String in Brackets', |
| { facingMode: { ideal: ["user3", "left3"]}}); |
| constraintSyntaxTest('Multiple Bracketed Naked String', |
| { facingMode: ["user4", "left4"] }); |
| constraintSyntaxTestWithChange( |
| 'Single Bracketed string unwrapped', |
| { 'facingMode': ["user5"]}, { facingMode: "user5" }); |
| constraintSyntaxTest('Both Ideal and Exact string', |
| { facingMode: { ideal: "user6", exact: "left6" }}); |
| |
| constraintSyntaxTest('echoCancellation with simple boolean value', { echoCancellation: true }); |
| constraintSyntaxTest('echoCancellation with ideal boolean value', { echoCancellation: { ideal: true }}); |
| constraintSyntaxTestWithChange('echoCancellation with exact unwrapped boolean value', |
| { echoCancellation: { exact: true } }, { echoCancellation: true }); |
| |
| constraintSyntaxTest('autoGainControl with simple boolean value', { autoGainControl: true }); |
| constraintSyntaxTest('autoGainControl with ideal boolean value', { autoGainControl: { ideal: true }}); |
| constraintSyntaxTestWithChange('autoGainControl with exact unwrapped boolean value', |
| { autoGainControl: { exact: true } }, { autoGainControl: true }); |
| |
| constraintSyntaxTest('noiseSuppression with simple boolean value', { noiseSuppression: true }); |
| constraintSyntaxTest('noiseSuppression with ideal boolean value', { noiseSuppression: { ideal: true }}); |
| constraintSyntaxTestWithChange('noiseSuppression with exact unwrapped boolean value', |
| { noiseSuppression: { exact: true } }, { noiseSuppression: true }); |
| |
| promise_test(async t => { |
| let stream = await navigator.mediaDevices.getUserMedia( |
| {video: {width: 639, height: 479}}); |
| let track = stream.getVideoTracks()[0]; |
| t.add_cleanup(()=>track.stop()); |
| let settings = track.getSettings(); |
| assert_equals(settings.width, 639); |
| assert_equals(settings.height, 479); |
| assert_equals(settings.resizeMode, "crop-and-scale"); |
| |
| await track.applyConstraints({resizeMode: {exact: "none"}}); |
| settings = track.getSettings(); |
| assert_equals(settings.width, 640); |
| assert_equals(settings.height, 480); |
| assert_equals(settings.resizeMode, "none"); |
| |
| try { |
| await track.applyConstraints( |
| {width: {exact: 639}, resizeMode: {exact: 'none'}}); |
| t.step(()=>assert_unreached('applyConstraints should not have succeeded')); |
| } catch(e) { |
| assert_equals(e.name, 'OverconstrainedError'); |
| assert_equals(e.constraint, 'width'); |
| } |
| }, "applyConstraints() supports resizeMode in getUserMedia() tracks"); |
| |
| function constraintSyntaxTestContradictingProperties( |
| name, constraintName, constraints, contradictingConstraints) { |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia(constraints); |
| const tracks = stream.getTracks(); |
| assert_equals(tracks.length, 1); |
| |
| const track = tracks[0]; |
| t.add_cleanup(()=>track.stop()); |
| try { |
| await track.applyConstraints(contradictingConstraints); |
| } catch(e) { |
| assert_equals(e.name, 'OverconstrainedError'); |
| assert_equals(e.constraint, constraintName); |
| return; |
| } |
| assert_unreached('applyConstraints should not have succeeded'); |
| }, name); |
| } |
| |
| constraintSyntaxTestContradictingProperties( |
| 'Contradicting echoCancellation constraints', 'echoCancellation', |
| {audio: {echoCancellation: {exact: true}}}, |
| {echoCancellation: {exact: false}}); |
| |
| constraintSyntaxTestContradictingProperties( |
| 'Contradicting noiseSuppression constraints', 'noiseSuppression', |
| {audio: {noiseSuppression: {exact: true}}}, |
| {noiseSuppression: {exact: false}}); |
| |
| constraintSyntaxTestContradictingProperties( |
| 'Contradicting autoGainControl constraints', 'autoGainControl', |
| {audio: {autoGainControl: {exact: true}}}, |
| {autoGainControl: {exact: false}}); |
| |
| </script> |
| </body> |
| </html> |