summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html')
-rw-r--r--testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html162
1 files changed, 162 insertions, 0 deletions
diff --git a/testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html b/testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html
new file mode 100644
index 0000000000..5c0f2e1119
--- /dev/null
+++ b/testing/web-platform/tests/media-source/mediasource-correct-frames-after-reappend.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<!-- Copyright © 2019 Igalia. -->
+<html>
+<head>
+ <title>Frame checking test for MSE playback in presence of a reappend.</title>
+ <meta name="timeout" content="long">
+ <meta name="charset" content="UTF-8">
+ <link rel="author" title="Alicia Boya García" href="mailto:aboya@igalia.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="mediasource-util.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<canvas id="test-canvas"></canvas>
+<script>
+ function waitForEventPromise(element, event) {
+ return new Promise(resolve => {
+ function handler(ev) {
+ element.removeEventListener(event, handler);
+ resolve(ev);
+ }
+ element.addEventListener(event, handler);
+ });
+ }
+
+ function appendBufferPromise(sourceBuffer, data) {
+ sourceBuffer.appendBuffer(data);
+ return waitForEventPromise(sourceBuffer, "update");
+ }
+
+ function waitForPlayerToReachTimePromise(mediaElement, time) {
+ return new Promise(resolve => {
+ function timeupdate() {
+ if (mediaElement.currentTime < time)
+ return;
+
+ mediaElement.removeEventListener("timeupdate", timeupdate);
+ resolve();
+ }
+ mediaElement.addEventListener("timeupdate", timeupdate);
+ });
+ }
+
+ function readPixel(imageData, x, y) {
+ return {
+ r: imageData.data[4 * (y * imageData.width + x)],
+ g: imageData.data[1 + 4 * (y * imageData.width + x)],
+ b: imageData.data[2 + 4 * (y * imageData.width + x)],
+ a: imageData.data[3 + 4 * (y * imageData.width + x)],
+ };
+ }
+
+ function isPixelLit(pixel) {
+ const threshold = 200; // out of 255
+ return pixel.r >= threshold && pixel.g >= threshold && pixel.b >= threshold;
+ }
+
+ // The test video has a few gray boxes. Each box interval (1 second) a new box is lit white and a different note
+ // is played. This test makes sure the right number of lit boxes and the right note are played at the right time.
+ const totalBoxes = 7;
+ const boxInterval = 1; // seconds
+
+ const videoWidth = 320;
+ const videoHeight = 240;
+ const boxesY = 210;
+ const boxSide = 20;
+ const boxMargin = 20;
+ const allBoxesWidth = totalBoxes * boxSide + (totalBoxes - 1) * boxMargin;
+ const boxesX = new Array(totalBoxes).fill(undefined)
+ .map((_, i) => (videoWidth - allBoxesWidth) / 2 + boxSide / 2 + i * (boxSide + boxMargin));
+
+ // Sound starts playing A4 (440 Hz) and goes one chromatic note up with every box lit.
+ // By comparing the player position to both the amount of boxes lit and the note played we can detect A/V
+ // synchronization issues automatically.
+ const noteFrequencies = new Array(1 + totalBoxes).fill(undefined)
+ .map((_, i) => 440 * Math.pow(Math.pow(2, 1 / 12), i));
+
+ // We also check the first second [0, 1) where no boxes are lit, therefore we start counting at -1 to do the check
+ // for zero lit boxes.
+ let boxesLitSoFar = -1;
+
+ mediasource_test(async function (test, mediaElement, mediaSource) {
+ const canvas = document.getElementById("test-canvas");
+ const canvasCtx = canvas.getContext("2d");
+ canvas.width = videoWidth;
+ canvas.height = videoHeight;
+
+ const videoData = await (await fetch("mp4/test-boxes-video.mp4")).arrayBuffer();
+ const audioData = (await (await fetch("mp4/test-boxes-audio.mp4")).arrayBuffer());
+
+ const videoSb = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.4d401f"');
+ const audioSb = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
+
+ mediaElement.addEventListener('error', test.unreached_func("Unexpected event 'error'"));
+ mediaElement.addEventListener('ended', onEnded);
+ mediaElement.addEventListener('timeupdate', onTimeUpdate);
+
+ await appendBufferPromise(videoSb, videoData);
+ await appendBufferPromise(audioSb, audioData);
+ mediaElement.play();
+
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ source = audioCtx.createMediaElementSource(mediaElement);
+ analyser = audioCtx.createAnalyser();
+ analyser.fftSize = 8192;
+ source.connect(analyser);
+ analyser.connect(audioCtx.destination);
+
+ const freqDomainArray = new Float32Array(analyser.frequencyBinCount);
+
+ function checkNoteBeingPlayed() {
+ const expectedNoteFrequency = noteFrequencies[boxesLitSoFar];
+
+ analyser.getFloatFrequencyData(freqDomainArray);
+ const maxBin = freqDomainArray.reduce((prev, curValue, i) =>
+ curValue > prev.value ? {index: i, value: curValue} : prev,
+ {index: -1, value: -Infinity});
+ const binFrequencyWidth = audioCtx.sampleRate / analyser.fftSize;
+ const binFreq = maxBin.index * binFrequencyWidth;
+
+ assert_true(Math.abs(expectedNoteFrequency - binFreq) <= binFrequencyWidth,
+ `The note being played matches the expected one (boxes lit: ${boxesLitSoFar}, ${expectedNoteFrequency.toFixed(1)} Hz)` +
+ `, found ~${binFreq.toFixed(1)} Hz`);
+ }
+
+ function countLitBoxesInCurrentVideoFrame() {
+ canvasCtx.drawImage(mediaElement, 0, 0);
+ const imageData = canvasCtx.getImageData(0, 0, videoWidth, videoHeight);
+ const lights = boxesX.map(boxX => isPixelLit(readPixel(imageData, boxX, boxesY)));
+ let litBoxes = 0;
+ for (let i = 0; i < lights.length; i++) {
+ if (lights[i])
+ litBoxes++;
+ }
+ for (let i = litBoxes; i < lights.length; i++) {
+ assert_false(lights[i], 'After the first non-lit box, all boxes must non-lit');
+ }
+ return litBoxes;
+ }
+
+ await waitForPlayerToReachTimePromise(mediaElement, 2.5);
+ await appendBufferPromise(audioSb, audioData);
+ mediaSource.endOfStream();
+
+ function onTimeUpdate() {
+ const graceTime = 0.5;
+ if (mediaElement.currentTime >= (1 + boxesLitSoFar) * boxInterval + graceTime && boxesLitSoFar < totalBoxes) {
+ assert_equals(countLitBoxesInCurrentVideoFrame(), boxesLitSoFar + 1, "Num of lit boxes:");
+ boxesLitSoFar++;
+ checkNoteBeingPlayed();
+ }
+ }
+
+ function onEnded() {
+ assert_equals(boxesLitSoFar, totalBoxes, "Boxes lit at video ended event");
+ test.done();
+ }
+ }, "Test the expected frames are played at the expected times, even in presence of reappends");
+</script>
+</body>
+</html>