summaryrefslogtreecommitdiffstats
path: root/dom/media/test/test_setSinkId_after_loop.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/test/test_setSinkId_after_loop.html')
-rw-r--r--dom/media/test/test_setSinkId_after_loop.html126
1 files changed, 126 insertions, 0 deletions
diff --git a/dom/media/test/test_setSinkId_after_loop.html b/dom/media/test/test_setSinkId_after_loop.html
new file mode 100644
index 0000000000..6224daa417
--- /dev/null
+++ b/dom/media/test/test_setSinkId_after_loop.html
@@ -0,0 +1,126 @@
+<!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>