summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js')
-rw-r--r--dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js243
1 files changed, 203 insertions, 40 deletions
diff --git a/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js b/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
index 6074e2ee16..75f65eb34b 100644
--- a/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
+++ b/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
@@ -4,6 +4,7 @@ const IFRAME_URL =
"https://example.com/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html";
const testVideoId = "video";
+const videoDuration = 5.589333;
add_task(async function setupTestingPref() {
await SpecialPowers.pushPrefEnv({
@@ -18,9 +19,15 @@ add_task(async function setupTestingPref() {
add_task(async function testSetPositionState() {
info(`open media page`);
const tab = await createLoadedTabWrapper(PAGE_URL);
+ logPositionStateChangeEvents(tab);
+
+ info(`apply initial position state`);
+ await applyPositionState(tab, { duration: 10 });
info(`start media`);
+ const initialPositionState = isNextPositionState(tab, { duration: 10 });
await playMedia(tab, testVideoId);
+ await initialPositionState;
info(`set duration only`);
await setPositionState(tab, {
@@ -47,9 +54,15 @@ add_task(async function testSetPositionState() {
add_task(async function testSetPositionStateFromInactiveMediaSession() {
info(`open media page`);
const tab = await createLoadedTabWrapper(PAGE_URL);
+ logPositionStateChangeEvents(tab);
+
+ info(`apply initial position state`);
+ await applyPositionState(tab, { duration: 10 });
info(`start media`);
+ const initialPositionState = isNextPositionState(tab, { duration: 10 });
await playMedia(tab, testVideoId);
+ await initialPositionState;
info(
`add an event listener to measure how many times the position state changes`
@@ -82,48 +95,193 @@ add_task(async function testSetPositionStateFromInactiveMediaSession() {
});
/**
- * The following are helper functions.
+ *
+ * @param {boolean} withMetadata
+ * Specifies if the tab should set metadata for the playing video
*/
-async function setPositionState(tab, positionState) {
+async function testGuessedPositionState(withMetadata) {
+ info(`open media page`);
+ const tab = await createLoadedTabWrapper(PAGE_URL);
+ logPositionStateChangeEvents(tab);
+
+ if (withMetadata) {
+ info(`set media metadata`);
+ await setMediaMetadata(tab, { title: "A Video" });
+ }
+
+ info(`start media`);
+ await emitsPositionState(() => playMedia(tab, testVideoId), tab, {
+ duration: videoDuration,
+ position: 0,
+ playbackRate: 1.0,
+ });
+
+ info(`set playback rate to 2x`);
+ await emitsPositionState(() => setPlaybackRate(tab, testVideoId, 2.0), tab, {
+ duration: videoDuration,
+ position: null, // ignored,
+ playbackRate: 2.0,
+ });
+
+ info(`seek to 1s`);
+ await emitsPositionState(() => setCurrentTime(tab, testVideoId, 1.0), tab, {
+ duration: videoDuration,
+ position: 1.0,
+ playbackRate: 2.0,
+ });
+
+ let positionChangedNum = 0;
const controller = tab.linkedBrowser.browsingContext.mediaController;
- const positionStateChanged = new Promise(r => {
- controller.addEventListener(
- "positionstatechange",
- event => {
- const { duration, playbackRate, position } = positionState;
- // duration is mandatory.
- is(
- event.duration,
- duration,
- `expected duration ${event.duration} is equal to ${duration}`
- );
-
- // Playback rate is optional, if it's not present, default should be 1.0
- if (playbackRate) {
- is(
- event.playbackRate,
- playbackRate,
- `expected playbackRate ${event.playbackRate} is equal to ${playbackRate}`
- );
- } else {
- is(event.playbackRate, 1.0, `expected default playbackRate is 1.0`);
- }
-
- // Position state is optional, if it's not present, default should be 0.0
- if (position) {
- is(
- event.position,
- position,
- `expected position ${event.position} is equal to ${position}`
- );
- } else {
- is(event.position, 0.0, `expected default position is 0.0`);
- }
- r();
- },
- { once: true }
- );
+ controller.onpositionstatechange = () => positionChangedNum++;
+
+ info(`pause media`);
+ // shouldn't generate an event
+ await pauseMedia(tab, testVideoId);
+
+ info(`seek to 2s`);
+ await emitsPositionState(() => setCurrentTime(tab, testVideoId, 2.0), tab, {
+ duration: videoDuration,
+ position: 2.0,
+ playbackRate: 2.0,
+ });
+
+ info(`start media`);
+ await emitsPositionState(() => playMedia(tab, testVideoId), tab, {
+ duration: videoDuration,
+ position: 2.0,
+ playbackRate: 2.0,
});
+
+ is(
+ positionChangedNum,
+ 2,
+ `We should only receive two of position changes, because pausing is effectless`
+ );
+
+ info(`remove tab`);
+ await tab.close();
+}
+
+add_task(async function testGuessedPositionStateWithMetadata() {
+ testGuessedPositionState(true);
+});
+
+add_task(async function testGuessedPositionStateWithoutMetadata() {
+ testGuessedPositionState(false);
+});
+
+/**
+ * @typedef {{
+ * duration: number,
+ * playbackRate?: number | null,
+ * position?: number | null,
+ * }} ExpectedPositionState
+ */
+
+/**
+ * Checks if the next received position state matches the expected one.
+ *
+ * @param {tab} tab
+ * The tab that contains the media
+ * @param {ExpectedPositionState} positionState
+ * The expected position state. `duration` is mandatory. `playbackRate`
+ * and `position` are optional. If they're `null`, they're ignored,
+ * otherwise if they're not present or undefined, they're expected to
+ * be the default value.
+ * @returns {Promise}
+ * Resolves when the event has been received
+ */
+async function isNextPositionState(tab, positionState) {
+ const got = await nextPositionState(tab);
+ isPositionState(got, positionState);
+}
+
+/**
+ * Waits for the next position state and returns it
+ *
+ * @param {tab} tab The tab to receive position state from
+ * @returns {Promise<MediaPositionState>} The emitted position state
+ */
+function nextPositionState(tab) {
+ const controller = tab.linkedBrowser.browsingContext.mediaController;
+ return new Promise(r => {
+ controller.addEventListener("positionstatechange", r, { once: true });
+ });
+}
+
+/**
+ * @param {MediaPositionState} got
+ * The received position state
+ * @param {ExpectedPositionState} expected
+ * The expected position state. `duration` is mandatory. `playbackRate`
+ * and `position` are optional. If they're `null`, they're ignored,
+ * otherwise if they're not present or undefined, they're expected to
+ * be the default value.
+ */
+function isPositionState(got, expected) {
+ const { duration, playbackRate, position } = expected;
+ // duration is mandatory.
+ isFuzzyEq(got.duration, duration, "duration");
+
+ // Playback rate is optional, if it's not present, default should be 1.0
+ if (typeof playbackRate === "number") {
+ isFuzzyEq(got.playbackRate, playbackRate, "playbackRate");
+ } else if (playbackRate !== null) {
+ is(got.playbackRate, 1.0, `expected default playbackRate is 1.0`);
+ }
+
+ // Position is optional, if it's not present, default should be 0.0
+ if (typeof position === "number") {
+ isFuzzyEq(got.position, position, "position");
+ } else if (position !== null) {
+ is(got.position, 0.0, `expected default position is 0.0`);
+ }
+}
+
+/**
+ * Checks if two numbers are equal within one significant digit
+ *
+ * @param {number} got
+ * The value received while testing
+ * @param {number} expected
+ * The expected value
+ * @param {string} role
+ * The role of the check (used for formatting)
+ */
+function isFuzzyEq(got, expected, role) {
+ expected = expected.toFixed(1);
+ got = got.toFixed(1);
+ is(got, expected, `expected ${role} ${got} to equal ${expected}`);
+}
+
+/**
+ * Test if `cb` emits a position state event.
+ *
+ * @param {() => (void | Promise<void>)} cb
+ * A callback that is expected to generate a position state event
+ * @param {tab} tab
+ * The tab that contains the media
+ * @param {ExpectedPositionState} positionState
+ * The expected position state to be generated.
+ */
+async function emitsPositionState(cb, tab, positionState) {
+ const positionStateChanged = isNextPositionState(tab, positionState);
+ await cb();
+ await positionStateChanged;
+}
+
+/**
+ * The following are helper functions.
+ */
+async function setPositionState(tab, positionState) {
+ await emitsPositionState(
+ () => applyPositionState(tab, positionState),
+ tab,
+ positionState
+ );
+}
+
+async function applyPositionState(tab, positionState) {
await SpecialPowers.spawn(
tab.linkedBrowser,
[positionState],
@@ -131,7 +289,12 @@ async function setPositionState(tab, positionState) {
content.navigator.mediaSession.setPositionState(positionState);
}
);
- await positionStateChanged;
+}
+
+async function setMediaMetadata(tab, metadata) {
+ await SpecialPowers.spawn(tab.linkedBrowser, [metadata], data => {
+ content.navigator.mediaSession.metadata = new content.MediaMetadata(data);
+ });
}
async function setPositionStateOnInactiveMediaSession(tab) {