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();
    }
  }
});

Similar Posts