summaryrefslogtreecommitdiffstats
path: root/dom/media/test/test_seamless_looping.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/test/test_seamless_looping.html')
-rw-r--r--dom/media/test/test_seamless_looping.html199
1 files changed, 199 insertions, 0 deletions
diff --git a/dom/media/test/test_seamless_looping.html b/dom/media/test/test_seamless_looping.html
new file mode 100644
index 0000000000..94d020c834
--- /dev/null
+++ b/dom/media/test/test_seamless_looping.html
@@ -0,0 +1,199 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for seamless loop of HTMLAudioElements</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="300" height="300"></canvas>
+<script type="application/javascript">
+/**
+ * This test is used to ensure every time we loop audio, the audio can loop
+ * seamlessly which means there won't have any silenece or noise between the
+ * end and the start.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+// Set DEBUG to true to add a canvas with a little drawing of what is going
+// on, and actually outputs the audio to the speakers.
+var DEBUG = true;
+var LOOPING_COUNT = 0;
+var MAX_LOOPING_COUNT = 10;
+// Test files are at 44100Hz, files are one second long, and contain therefore
+// 100 periods
+var TONE_FREQUENCY = 441;
+
+(async function testSeamlesslooping() {
+ let wavFileURL = {
+ name: URL.createObjectURL(new Blob([createSrcBuffer()], { type: 'audio/wav'
+ })),
+ type: "audio/wav"
+ };
+
+ let testURLs = gSeamlessLoopingTests.splice(0)
+ testURLs.push(wavFileURL);
+ for (let testFile of testURLs) {
+ LOOPING_COUNT = 0;
+ info(`- create looping audio element ${testFile.name}`);
+ let audio = createAudioElement(testFile.name);
+
+ info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`);
+ await playAudioAndStartAnalyzingWaveData(audio);
+
+ info(`- test seamless looping multiples times -`);
+ for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) {
+ await once(audio, "seeked");
+ info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`);
+ }
+ window.audio.remove();
+ window.ac.close();
+ }
+
+ info(`- end of seamless looping test -`);
+ SimpleTest.finish();
+})();
+
+/**
+ * Test utility functions
+ */
+function createSrcBuffer() {
+ // Generate the sine in floats, then convert, for simplicity.
+ let channels = 1;
+ let sampleRate = 44100;
+ let buffer = new Float32Array(sampleRate * channels);
+ let phase = 0;
+ const TAU = 2 * Math.PI;
+ for (let i = 0; i < buffer.length; i++) {
+ // Adjust the gain a little so we're sure it's not going to clip. This is
+ // important because we're converting to 16bit integer right after, and
+ // clipping will clearly introduce a discontinuity that will be
+ // mischaracterized as a looping click.
+ buffer[i] = Math.sin(phase) * 0.99;
+ phase += TAU * TONE_FREQUENCY / 44100;
+ if (phase > 2 * TAU) {
+ phase -= TAU;
+ }
+ }
+
+ // Make a RIFF header, it's 23 bytes
+ let buf = new Int16Array(buffer.length + 23);
+ buf[0] = 0x4952;
+ buf[1] = 0x4646;
+ buf[2] = (2 * buffer.length + 15) & 0x0000ffff;
+ buf[3] = ((2 * buffer.length + 15) & 0xffff0000) >> 16;
+ buf[4] = 0x4157;
+ buf[5] = 0x4556;
+ buf[6] = 0x6d66;
+ buf[7] = 0x2074;
+ buf[8] = 0x0012;
+ buf[9] = 0x0000;
+ buf[10] = 0x0001;
+ buf[11] = 1;
+ buf[12] = 44100 & 0x0000ffff;
+ buf[13] = (44100 & 0xffff0000) >> 16;
+ buf[14] = (2 * channels * sampleRate) & 0x0000ffff;
+ buf[15] = ((2 * channels * sampleRate) & 0xffff0000) >> 16;
+ buf[16] = 0x0004;
+ buf[17] = 0x0010;
+ buf[18] = 0x0000;
+ buf[19] = 0x6164;
+ buf[20] = 0x6174;
+ buf[21] = (2 * buffer.length) & 0x0000ffff;
+ buf[22] = ((2 * buffer.length) & 0xffff0000) >> 16;
+
+ // convert to int16 and copy.
+ for (let i = 0; i < buffer.length; i++) {
+ buf[i + 23] = Math.round(buffer[i] * (1 << 15));
+ }
+ return buf;
+}
+
+function createAudioElement(url) {
+ /* global audio */
+ window.audio = document.createElement("audio");
+ audio.src = url;
+ audio.controls = true;
+ audio.loop = true;
+ document.body.appendChild(audio);
+ return audio;
+}
+
+async function playAudioAndStartAnalyzingWaveData(audio) {
+ createAudioWaveAnalyser(audio);
+ ok(await once(audio, "canplay").then(() => true, () => false),
+ `audio can start playing.`)
+ ok(await audio.play().then(() => true, () => false),
+ `audio started playing successfully.`);
+}
+
+function createAudioWaveAnalyser(source) {
+ /* global ac, analyser */
+ window.ac = new AudioContext();
+ window.analyser = ac.createAnalyser();
+ analyser.frequencyBuf = new Float32Array(analyser.frequencyBinCount);
+ analyser.smoothingTimeConstant = 0;
+ analyser.fftSize = 2048; // 1024 bins
+
+ let sourceNode = ac.createMediaElementSource(source);
+ sourceNode.connect(analyser);
+
+ if (DEBUG) {
+ analyser.connect(ac.destination);
+ analyser.timeDomainBuf = new Float32Array(analyser.frequencyBinCount);
+ let cvs = document.querySelector("canvas");
+ analyser.c = cvs.getContext("2d");
+ analyser.w = cvs.width;
+ analyser.h = cvs.height;
+ }
+
+ analyser.notifyAnalysis = () => {
+ if (LOOPING_COUNT >= MAX_LOOPING_COUNT) {
+ return;
+ }
+ let {frequencyBuf} = analyser;
+ analyser.getFloatFrequencyData(frequencyBuf);
+ // Let things stabilize at the beginning. See bug 1441509.
+ if (LOOPING_COUNT > 1) {
+ analyser.doAnalysis(frequencyBuf, ac.sampleRate);
+ }
+
+ if (DEBUG) {
+ let {c, w, h, timeDomainBuf} = analyser;
+ c.clearRect(0, 0, w, h);
+ analyser.getFloatTimeDomainData(timeDomainBuf);
+ for (let i = 0; i < frequencyBuf.length; i++) {
+ c.fillRect(i, h, 1, -frequencyBuf[i] + analyser.minDecibels);
+ }
+
+ for (let i = 0; i < timeDomainBuf.length; i++) {
+ c.fillRect(i, h / 2, 1, -timeDomainBuf[i] * h / 2);
+ }
+ }
+
+ requestAnimationFrame(analyser.notifyAnalysis);
+ }
+
+ analyser.doAnalysis = (buf, ctxSampleRate) => {
+ // The size of an FFT is twice the number of bins in its output.
+ let fftSize = 2 * buf.length;
+ // first find a peak where we expect one.
+ let binIndexTone = 1 + Math.round(TONE_FREQUENCY * fftSize / ctxSampleRate);
+ ok(buf[binIndexTone] > -35,
+ `Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz
+ (${source.src})`);
+
+ // check that the energy some octaves higher is very low.
+ let binIndexOutsidePeak = 1 + Math.round(TONE_FREQUENCY * 4 * buf.length / ctxSampleRate);
+ ok(buf[binIndexOutsidePeak] < -84,
+ `Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db
+ at ${TONE_FREQUENCY * 4}Hz (${source.src})`);
+ }
+
+ analyser.notifyAnalysis();
+}
+</script>
+</body>
+</html>