-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7010 from dontcallmedom/setsinkid
Test behavior of setSinkId (automatic and manual)
- Loading branch information
Showing
2 changed files
with
177 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<title>Test setSinkId behavior with permissions / device changes</title> | ||
<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/> | ||
<link rel="help" href="https://www.w3.org/TR/audio-output/#dom-htmlmediaelement-setsinkid"> | ||
<meta name="timeout" content="long"> | ||
|
||
</head> | ||
<body> | ||
<h1 class="instructions">Description</h1> | ||
<p class="instructions">This test checks that <code>setSinkId</code> follows the algorithm, this includes manually checking the proper rendering on new output devices.</p> | ||
<p class="instructions">When prompted to access microphones, please accept as this is the only current way to get permissions for associated output devices.</p> | ||
<p class="instructions">For each authorized output device, check that selecting it makes the audio beat rendered on the corresponding device.</p> | ||
<p>Available but unauthorized devices (only those for which we can gain permission can be selected):</p> | ||
<ul id="available"></ul> | ||
<p>Authorized devices:</p> | ||
<ul id="authorized"></ul> | ||
<audio controls id="beat" src="/media/sound_5.mp3" loop></audio> | ||
|
||
<div id='log'></div> | ||
<script src=/resources/testharness.js></script> | ||
<script src=/resources/testharnessreport.js></script> | ||
<script> | ||
"use strict"; | ||
|
||
const is_output = d => d.kind === "audiooutput"; | ||
const by_id = (id) => d => d.groupId === id; | ||
const is_input = d => d.kind === "audioinput"; | ||
const audio = document.getElementById("beat"); | ||
const available = document.getElementById("available"); | ||
const authorized = document.getElementById("authorized"); | ||
|
||
let outputList; | ||
|
||
const selectDeviceTester = (t) => (e) => { | ||
const groupId = e.target.dataset["groupid"]; | ||
const device = outputList.find(by_id(groupId)); | ||
if (audio.paused) audio.play(); | ||
promise_test(pt => audio.setSinkId(device.deviceId).then(() => { | ||
assert_equals(device.deviceId, audio.sinkId); | ||
|
||
const pass = document.createElement("button"); | ||
const fail = document.createElement("button"); | ||
|
||
const result = (bool) => () => { | ||
assert_true(bool, "Sound rendered on right device"); | ||
fail.remove(); | ||
pass.remove(); | ||
audio.pause(); | ||
e.target.checked = false; | ||
e.target.disabled = true; | ||
t.done(); | ||
}; | ||
|
||
pass.style.backgroundColor = "#0f0"; | ||
pass.textContent = "\u2713 Sound plays on " + device.label; | ||
pass.addEventListener("click", result(true)); | ||
|
||
fail.style.backgroundColor = "#f00"; | ||
fail.textContent = "\u274C Sound doesn't play on " + device.label; | ||
fail.addEventListener("click", result(true)); | ||
|
||
const container = e.target.parentNode.parentNode; | ||
container.appendChild(pass); | ||
container.appendChild(fail); | ||
}), "setSinkId for " + device.label + " resolves"); | ||
}; | ||
|
||
const addAuthorizedDevice = (groupId) => { | ||
const device = outputList.find(by_id(groupId)); | ||
const async_t = async_test("Selecting output device " + device.label + " makes the audio rendered on the proper device"); | ||
const li = document.createElement("li"); | ||
const label = document.createElement("label"); | ||
const input = document.createElement("input"); | ||
input.type = "radio"; | ||
input.name = "device"; | ||
input.dataset["groupid"] = device.groupId; | ||
input.addEventListener("change", selectDeviceTester(async_t)); | ||
const span = document.createElement("span"); | ||
span.textContent = device.label; | ||
label.appendChild(input); | ||
label.appendChild(span); | ||
li.appendChild(label); | ||
authorized.appendChild(li); | ||
}; | ||
|
||
const authorizeDeviceTester = (t) => (e) => { | ||
const groupId = e.target.dataset["groupid"]; | ||
navigator.mediaDevices.getUserMedia({audio: {groupId}}) | ||
.then( () => { | ||
addAuthorizedDevice(groupId); | ||
t.done(); | ||
}); | ||
}; | ||
|
||
promise_test(gum => | ||
navigator.mediaDevices.getUserMedia({audio: true}).then( | ||
() => { | ||
promise_test(t => | ||
navigator.mediaDevices.enumerateDevices().then(list => { | ||
assert_not_equals(list.find(is_output), undefined, "media device list includes at least one audio output device"); | ||
outputList = list.filter(is_output); | ||
outputList.forEach(d => { | ||
const li = document.createElement("li"); | ||
assert_not_equals(d.label, "", "Audio Output Device Labels are available after permission grant"); | ||
li.textContent = d.label; | ||
// Check permission | ||
promise_test(perm => navigator.permissions.query({name: "speaker", deviceId: d.deviceId}).then(({state}) => { | ||
if (state === "granted") { | ||
addAuthorizedDevice(d.groupId); | ||
} else if (state === "prompt") { | ||
const inp = list.find(inp => inp.kind === "audioinput" && inp.groupId === d.groupId); | ||
if (inp || true) { | ||
const async_t = async_test("Authorizing output devices via permission requests for microphones works"); | ||
const button = document.createElement("button"); | ||
button.textContent = "Authorize access"; | ||
button.dataset["groupid"] = d.groupId; | ||
button.addEventListener("click", async_t.step_func_done(authorizeDeviceTester(async_t))); | ||
li.appendChild(button); | ||
} | ||
available.appendChild(li); | ||
} | ||
}, () => { | ||
// if we can't query the permission, we assume it's granted :/ | ||
addAuthorizedDevice(d.groupId); | ||
}) | ||
, "Query permission to use " + d.label); | ||
}); | ||
}), "List media devices"); | ||
}), "Authorize mike access"); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<title>Test setSinkId behavior </title> | ||
<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/> | ||
<link rel="help" href="https://www.w3.org/TR/audio-output/#dom-htmlmediaelement-setsinkid"> | ||
</head> | ||
<body> | ||
<h1 class="instructions">Description</h1> | ||
<p class="instructions">This test checks that <code>setSinkId</code> follows the algorithm (but does not consider actual rendering of the audio which needs to be manual).</p> | ||
<div id='log'></div> | ||
<script src=/resources/testharness.js></script> | ||
<script src=/resources/testharnessreport.js></script> | ||
<script> | ||
"use strict"; | ||
|
||
const is_output = d => d.kind === "audiooutput"; | ||
const audio = new Audio(); | ||
|
||
promise_test(t => audio.setSinkId(""), "setSinkId on default audio output should always work"); | ||
|
||
promise_test(t => promise_rejects(t, "NotFoundError", audio.setSinkId("inexistent_device_id")), "setSinkId fails with NotFoundError on made up deviceid"); | ||
|
||
promise_test(t => | ||
navigator.mediaDevices.enumerateDevices().then(list => { | ||
assert_not_equals(list.find(is_output), undefined, "media device list includes at least one audio output device"); | ||
// since we haven't gained any specific permission, | ||
// for all listed audio output devices, calling setSinkId with device id can | ||
// either create a security exception or work and thus reflect the deviceId | ||
let acceptedDevice = 0; | ||
list.filter(is_output).forEach((d,i) => promise_test(td => audio.setSinkId(d.deviceId).then(r => { | ||
assert_equals(r, undefined, "setSinkId resolves with undefined"); | ||
assert_equals(audio.sinkId, d.deviceId, "when it resolves, setSinkId updates sinkId to the requested deviceId"); | ||
assert_equals(acceptedDevice, 0, "only one output device can be set without permission"); | ||
acceptedDevice++; | ||
promise_test(t => audio.setSinkId(d.deviceId), "resetting sinkid on same current value should always work"); | ||
promise_test(t => audio.setSinkId(""), "resetting sinkid on default audio output should always work"); | ||
}, e => { | ||
assert_equals(e.name, "SecurityError", "On known devices, the only possible failure of setSinkId is a securityerror"); // assuming AbortError can't happen in the test environment by default | ||
}), "Correctly reacts to setting known deviceid as sinkid " + i)); | ||
}), "List media devices"); | ||
|
||
</script> | ||
</body> | ||
</html> |