Bug 1420322 - Add a test for seamless looping. r?pehrsons draft
authorPaul Adenot <paul@paul.cx>
Tue, 06 Feb 2018 15:25:25 +0100
changeset 752561 052c81bf61ec03c0ae2d2ae67a84fa7ae75cbe16
parent 752511 0ac953fcddf10132eaecdb753d72b2ba5a43c32a
push id98291
push userpaul@paul.cx
push dateThu, 08 Feb 2018 13:55:06 +0000
reviewerspehrsons
bugs1420322
milestone60.0a1
Bug 1420322 - Add a test for seamless looping. r?pehrsons MozReview-Commit-ID: 8WdItn7SXB3
dom/media/test/mochitest.ini
dom/media/test/test_seamless_looping.html
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1048,16 +1048,17 @@ skip-if = toolkit == 'android' # bug 131
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_video_dimensions.html]
 skip-if = toolkit == 'android' # bug 1298238, bug 1304535, android(bug 1232305)
 [test_resolution_change.html]
 skip-if = android_version == '19' # bug 1393866
 tags=capturestream
 [test_resume.html]
 skip-if = true # bug 1021673
+[test_seamless_looping.html]
 [test_seek_negative.html]
 skip-if = toolkit == 'android' # bug 1295443, bug 1306787, android(bug 1232305)
 [test_seek_nosrc.html]
 [test_seek_out_of_range.html]
 skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305)
 [test_seek_promise_bug1344357.html]
 skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305)
 [test_seek-1.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_seamless_looping.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for seamless loop of HTMLMediaElements</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  SimpleTest.waitForExplicitFinish();
+
+  var tone_frequency = 440;
+  // 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 = false;
+  var again = true;
+
+  function doAnalysis(buf, ctxSampleRate) {
+    // The size of an FFT is twice the number of bins in its output.
+    var fftSize = 2 * buf.length;
+    // first find a peak where we expect one.
+    var binIndexTone = 1 + Math.round(tone_frequency * fftSize / ctxSampleRate);
+    ok(buf[binIndexTone] > -25,
+       `Could not find a peak: ${buf[binIndexTone]} db at ${tone_frequency}Hz`);
+
+    // check that the energy some octaves higher is very low.
+    var binIndexOutsidePeak = 1 + Math.round(tone_frequency * 4 * buf.length / ctxSampleRate);
+    ok(buf[binIndexOutsidePeak] < -110,
+       `Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db at ${tone_frequency * 4}Hz`);
+  }
+  window.onload = function () {
+    // Generate the sine in floats, then convert, for simplicity.
+    var channels = 1;
+    var sampleRate = 44100;
+    var buffer = new Float32Array(sampleRate * channels);
+    var phase = 0;
+    const TAU = 2 * Math.PI;
+    for (var 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
+    var 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 (var i = 0; i < buffer.length; i++) {
+      buf[i + 23] = Math.round(buffer[i] * (1 << 15));
+    }
+
+    var b = new Blob([buf], { type: 'audio/wav' });
+
+    var media = document.createElement("audio");
+    media.src = URL.createObjectURL(b);
+    media.controls = true;
+    media.loop = true;
+    document.body.appendChild(media);
+
+    var ac = new AudioContext();
+
+    var analyser = ac.createAnalyser();
+    var frequencyBuf = new Float32Array(analyser.frequencyBinCount);
+    analyser.smoothingTimeConstant = 0;
+    analyser.fftSize = 2048; // 1024 bins
+
+    var source = ac.createMediaElementSource(media);
+    source.connect(analyser);
+
+    if (DEBUG) {
+      analyser.connect(ac.destination);
+      var timeDomainBuf = new Float32Array(analyser.frequencyBinCount);
+      var cvs = document.querySelector("canvas");
+      var c = cvs.getContext("2d");
+      var w = cvs.width;
+      var h = cvs.height;
+    }
+
+
+    // We count the number of times we've played this media.
+    var loopCount = 0;
+    media.onseeked = function () {
+      loopCount++;
+      if (loopCount > 10) {
+        again = false;
+        media.onseeked = null;
+        SimpleTest.finish();
+      }
+    }
+
+    function analysisCallback() {
+      if (!again) {
+        return;
+      }
+      analyser.getFloatFrequencyData(frequencyBuf);
+      // There is an unrelated click at start, skip it.
+      if (loopCount != 0) {
+        doAnalysis(frequencyBuf, ac.sampleRate);
+      }
+
+      if (DEBUG) {
+        c.clearRect(0, 0, w, h);
+        analyser.getFloatTimeDomainData(timeDomainBuf);
+        for (var i = 0; i < frequencyBuf.length; i++) {
+          c.fillRect(i, h, 1, -frequencyBuf[i] + analyser.minDecibels);
+        }
+
+        for (var i = 0; i < timeDomainBuf.length; i++) {
+          c.fillRect(i, h / 2, 1, -timeDomainBuf[i] * h / 2);
+        }
+      }
+
+      requestAnimationFrame(analysisCallback);
+    }
+
+    media.play();
+    analysisCallback();
+  }
+
+</script>
+</pre>
+</body>
+</html>