How to Make Background Music Continue Playing after Clicking a Link using Javascript

Share

Recently, I tried to add background music to my website, and I noticed a problem: if you click on a link to go to another page, the music stops! That's unacceptable! So, in this tutorial, I'll explain how to keep the music playing using Javascript.

Setting Up Audio

The first thing you're going to need is an <audio> element with some music loaded in it. We can create one with Javascript like this:

const myAudio = document.createElement('audio');

// sets the src
myAudio.src = "https://example.com/music.ogg";

// hides the audio from the user
myAudio.style.display = "none";

// makes the music play forever
myAudio.loop = true;

// makes the audio load
document.body.appendChild(myAudio);

We can use myAudio.play() to start the music, and myAudio.currentTime to get and set what second is currently being played. However, there are caveats, and those caveats have their own caveats.

We can't autoplay audio on Chrome. Chrome is no fun. But it does let us play audio pro grammatically if the user has previously played audio through the browsing context (i.e. in that tab, in that website). While myAudio.play() won't work the first time the user visits the page, it will work if the user has clicked on anything that caused audio to play, including a link to another page.

This means when the new page loads, myAudio.play() will work and we can autoplay starting from the currentTime of the last page.

Storing The Time

In order to resume the audio, we need to know what was the currentTime when the user left the last page. We can do it with the event beforeunload, which runs before the page is unloaded. People normally use this event to warn you that your modifications to a form won't be saved if you exit the page, but we can use it to do anything, including storing the page's state so that the next page can load it.

There are multiple ways we can use to store the page's state, e.g. cookies, localStorage, sessionStorage, etc. Although localStorage is a common choice for storing data that shouldn't be sent to the server like cookies, in our case we need to use sessionStorage because localStorage stores data for the whole website, while sessionStorage stores temporary data for the current tab only.

If we used localStorage and the user opened a link in a new tab, we would start playing the audio on both tabs, which isn't what we want.

window.addEventListener('beforeunload', () => {
    // create a state object to hold our state
    const state = {
        paused: myAudio.paused,
        currentTime: myAudio.currentTime
    };

    // turn it into a string for sessionStorage
    const value = JSON.stringify(state);
    
    // save it in sessionStorage if the browser supports it
    window.sessionStorage?.setItem('bgm-state', value);
});

Loading The State

To load the state, we just have to reverse the what we did to save it. Unfortunately, just doing this won't be enough to make our audio work right.

// get our value from storage
const value = window.sessionStorage?.getItem('bgm-state');
let state;
try {
    state = JSON.parse(value);
} catch {
    state = null;
}

if(state) {
    loadMyAudioState(state);
}

Compensating for Load Time

Simply setting the currentTime the same amount as the previous currentTime isn't enough. If we do that, we would break the rhythm of the music, which is unacceptable. There's because there is a varying number of milliseconds that will be elapsed between the instant that the new page starts loading and the instant that the script to set the currentTime is run.

We can compensate for this load time using performance.now(), which gives us the number of milliseconds elapsed since the page started loading.

Note that currentTime is in seconds, so we will need to convert those milliseconds to seconds before using it.

function loadMyAudioState(state) {
    const loadTimeInMS = performance?.now() ?? 0;
    myAudio.currentTime = state.currentTime + loadTimeInMS / 1000;
    if(!state.paused) {
        myAudio.play();    
    }
}

Other Improvements

The code above should generally work, but there are some improvements we can apply. For example, we aren't accounting for how long it takes for the audio to load.

Considering it should be the second time that the audio loads, the audio data should be in the browser's cache, and if it isn't, you probably should fir your HTTP headers to efficiently cache audio files.

An improved version would use the audio's canplay event to wait until the audio has loaded to call performance.now(). Ideally you would listen to this event before you set the src, considering that the web browser can start loading the audio file at that instant. In practice, I'm pretty sure events are only fired after the script finishes executing, but I never wrote code in the wrong order just to confirm this.

Written by Noel Santos.

About the Author

I'm a self-taught Brazilian programmer graduated in IT from a FATEC. In a world of increasingly complex and essential computers, I decided to use my technical expertise in hardware, desktop applications, and web technologies to create an informative resource to make PC's easier to understand.

View Comments