126 lines
4.2 KiB
HTML
126 lines
4.2 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<title>Test setSinkId() after looping</title>
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
|
</head>
|
|
<script type="application/javascript">
|
|
"use strict";
|
|
|
|
/**
|
|
This test captures loopback device audio to test that a media element produces
|
|
audio output soon after a setSinkId() call after looping. In bug 1838756,
|
|
incorrect timekeeping caused silence in the output as long as the duration of
|
|
loops completed.
|
|
**/
|
|
add_task(async () => {
|
|
const audio = new Audio();
|
|
audio.src = "sin-441-1s-44100.flac";
|
|
audio.loop = true;
|
|
audio.controls = true;
|
|
// Mute initially so that delayed audio from before setSinkId does not
|
|
// trigger a false pass.
|
|
audio.muted = true;
|
|
document.body.appendChild(audio);
|
|
const canplaythroughPromise = new Promise(r => audio.oncanplaythrough = r);
|
|
// Let the audio loop at least twice, which would result in 2 extra seconds
|
|
// of silence with bug 1838756.
|
|
let count = 0;
|
|
const loopedPromise = new Promise(r => audio.onseeked = () => {
|
|
if (++count == 2) {
|
|
r();
|
|
}
|
|
});
|
|
|
|
await SpecialPowers.pushPrefEnv({set: [
|
|
// skip gUM permission prompt
|
|
["media.navigator.permission.disabled", true],
|
|
// enumerateDevices() without focus
|
|
["media.devices.unfocused.enabled", true],
|
|
]});
|
|
let loopbackInputLabel =
|
|
SpecialPowers.getCharPref("media.audio_loopback_dev", "");
|
|
if (!navigator.userAgent.includes("Linux")) {
|
|
todo_isnot(loopbackInputLabel, "", "audio_loopback_dev");
|
|
return;
|
|
}
|
|
isnot(loopbackInputLabel, "",
|
|
"audio_loopback_dev. Try --use-test-media-devices.");
|
|
const loopbackStream =
|
|
await navigator.mediaDevices.getUserMedia({audio: true});
|
|
is(loopbackStream.getTracks()[0].label, loopbackInputLabel,
|
|
"loopback track label");
|
|
|
|
await canplaythroughPromise;
|
|
let loopbackNode;
|
|
try {
|
|
audio.play();
|
|
await new Promise(r => audio.onplaying = r);
|
|
const ac = new AudioContext;
|
|
loopbackNode = ac.createMediaStreamSource(loopbackStream);
|
|
const processor1 = ac.createScriptProcessor(4096, 1, 0);
|
|
loopbackNode.connect(processor1);
|
|
|
|
// Check that the loopback stream contains silence now.
|
|
const {inputBuffer} = await new Promise(r => processor1.onaudioprocess = r);
|
|
loopbackNode.disconnect();
|
|
is(inputBuffer.getChannelData(0).find(value => value != 0.0), undefined,
|
|
"should have silence");
|
|
|
|
// Find output device
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
let loopbackOutputLabel =
|
|
SpecialPowers.getCharPref("media.cubeb.output_device", "");
|
|
const outputDeviceInfo = devices.find(
|
|
({kind, label}) => kind == "audiooutput" && label == loopbackOutputLabel
|
|
);
|
|
ok(outputDeviceInfo, `found ${loopbackOutputLabel}`);
|
|
|
|
await loopedPromise;
|
|
await audio.setSinkId(outputDeviceInfo.deviceId);
|
|
audio.muted = false;
|
|
// Use a new ScriptProcessor so that the first audioprocess event provides
|
|
// the rendering thread time after unmuting.
|
|
const processor2 = ac.createScriptProcessor(4096, 1, 0);
|
|
loopbackNode.connect(processor2);
|
|
|
|
let seenAudioCount = 0;
|
|
let lastSample, firstTime;
|
|
while (seenAudioCount < ac.sampleRate) {
|
|
const event = await new Promise(r => processor2.onaudioprocess = r);
|
|
let samples = event.inputBuffer.getChannelData(0);
|
|
for (const sample of samples) {
|
|
if (!seenAudioCount) {
|
|
if (!firstTime) {
|
|
firstTime = event.playbackTime;
|
|
}
|
|
if (sample != 0.0) {
|
|
seenAudioCount = 1;
|
|
} else {
|
|
const delay = event.playbackTime - firstTime;
|
|
if (delay > 1.0) {
|
|
ok(false, `${delay} seconds passed without receiving audio`);
|
|
return;
|
|
}
|
|
}
|
|
} else if (sample != 0.0) {
|
|
++seenAudioCount;
|
|
} else if (lastSample == 0.0) {
|
|
ok(false, "should not have two consecutive zero sample in audio");
|
|
return;
|
|
}
|
|
lastSample = sample;
|
|
}
|
|
}
|
|
ok(true, "have one continuous second of audio");
|
|
} finally {
|
|
if (loopbackNode) {
|
|
loopbackNode.disconnect();
|
|
}
|
|
audio.pause();
|
|
loopbackStream.getTracks()[0].stop();
|
|
}
|
|
});
|
|
</script>
|
|
</html>
|