Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Bug 2060611 - Fix background audio playback
*
* YouTube pauses playback when the page is backgrounded, by reacting to the
* Page Visibility API. To let audio keep playing when Firefox is sent to the
* background, we make the page believe it is always visible. Gecko still marks
* the tab as hidden internally, so it suspends the video decoder on its own
* (media.suspend-background-video) while audio keeps playing.
*
* Exception: if the user explicitly closes the Picture-in-Picture window, that
* is an intentional stop, so we pause playback rather than keep going.
*
* This runs as an isolated content script so, as the webcompat addon, it can
* read the real visibility with `document.hidden` and the Android PiP state
* via `document.inAndroidPipMode`, while altering the page's own view through
* `document.wrappedJSObject`.
*/
const pageDocument = document.wrappedJSObject;
if (!pageDocument.__firefoxBackgroundVideoPlayback) {
Object.defineProperty(pageDocument, "__firefoxBackgroundVideoPlayback", {
value: true,
});
console.info(
"document.hidden/visibilityState and the visibilitychange event have been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=2060611 for details."
);
// Always report the page as visible so the site does not pause itself when
// the browser is backgrounded.
Object.defineProperties(pageDocument, {
hidden: { value: false },
visibilityState: { value: "visible" },
});
// Overriding the getters is not enough on its own: the visibilitychange event
// still fires. Stop it before the site's own listeners can react. But if the
// page goes hidden while still in Android PiP, the user closed the PiP window
// (an explicit stop), so pause the video instead of playing on.
window.addEventListener(
"visibilitychange",
event => {
event.stopImmediatePropagation();
if (document.hidden && document.inAndroidPipMode) {
for (const video of document.querySelectorAll("video")) {
video.pause();
}
}
},
true
);
// During long background playback YouTube shows an idle "Video paused.
// Continue watching?" prompt. While actually backgrounded, dispatch a keypress
// (the Alt key, which does nothing on its own) once a minute to keep it from
// triggering. Gated on the real visibility so foreground behavior is unchanged.
window.setInterval(() => {
if (!document.hidden) {
return;
}
for (const type of ["keydown", "keyup"]) {
document.dispatchEvent(
new KeyboardEvent(type, {
bubbles: true,
cancelable: true,
keyCode: 18, // Alt
which: 18,
})
);
}
}, 60 * 1000);
}