Giving people control over when to start, resume, and skip media is essential for the usability of your pages, so this API should be used with caution to ensure your visitors have agency while browsing.
You also shouldn’t presume that visitors want media to be paused automatically when a page is hidden.
Exposing a user preference or allowing visitors to opt-in to this kind of functionality is highly recommended.
There used to be a code snippet on MDN on the visibilitychange
page that was a good example of how this API can cause usability issues.
Can you spot what the problem is in the JavaScript code below?
<audio
controls
src="https://mdn.github.io/webaudio-examples/audio-basics/outfoxing.mp3"></audio>
const audio = document.querySelector("audio");
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
audio.pause();
} else {
audio.play();
}
});
In the HTML, there is an <audio>
element with controls visible so the user can start and stop the media.
In JavaScript, we’re looking at the visibilitychange
event and doing the following:
- If the page is hidden, pause the audio.
- If the page is shown, play the audio.
This sounds reasonable at first, but we encounter issues such as audio autoplaying when the user switches to another tab and back again or when restoring a browser window after it’s been minimized.
The point is that page visibility may toggle between hidden
and visible
without the user ever interacting with the <audio>
element.
This situation becomes even more surprising and distracting if the <audio>
element is located below the page fold and is not visible when the page loads.
You might unintentionally prompt your users to search for tabs where audio is playing and lead them to a place where it’s not obvious what is playing, which can be extremely frustrating.
To avoid usability issues, it’s always a good idea to see if people have interacted with the media before resuming playback.
One way of doing this is to store the state of the audio player when the page is hidden.
When the page visibility changes to visible
again, we resume media playback only if it was playing when the page was hidden.
Let’s modify the event listener to store the audio state on page hide.
A boolean playingOnHide
variable is sufficient for this purpose; we can set it when the document’s visibility changes to hidden
:
let playingOnHide = false;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
playingOnHide = !audio.paused;
audio.pause();
}
});
As with document.hidden
, there is no audio.playing
state, so we have to check !audio.paused
(not paused) instead.
That means the playingOnHide
Boolean can be set to whatever the value of !audio.paused
is on page hide, and we’re pretty much done.
The only other state the visibility of the page can be is visible
, so in the else
statement, we handle the playing logic:
let playingOnHide = false;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
playingOnHide = !audio.paused;
audio.pause();
} else {
if (playingOnHide) {
audio.play();
}
}
});
The finished code looks like this with a neat little gate for checking the audio state on page hide:
<audio
controls
src="https://mdn.github.io/webaudio-examples/audio-basics/outfoxing.mp3"></audio>
const audio = document.querySelector("audio");
let playingOnHide = false;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
playingOnHide = !audio.paused;
audio.pause();
} else {
if (playingOnHide) {
audio.play();
}
}
});