This has been a common practice on IOS/Android. Safari on the desktop banned multimedia autoplay with sound in version 11 of 2017, followed by Chrome 66 in April 2018.

Initially, mobile browsers completely banned audio and video autoplay, considering the bandwidth and battery drain on the phone. But then it changed, because browser manufacturers found that web developers might use GIF giFs instead of Video for autoplay. As the IOS document says, using GIF has 12 times the bandwidth traffic of Video(H264) and twice the performance consumption, so it’s not good for users. Or use Canvas for hacks, as the Android Chrome documentation mentions. As a result, browser manufacturers have freed up the restrictions on multimedia autoplay as long as the following conditions are met:

(1) No audio track, or set the muted attribute

Display: none or visibility: hidden; (2) If it is visible in the view, it must be inserted into the DOM and is not display: none or visibility: hidden.

In other words, as long as you don’t turn on the noise and it’s visible to the user, let you play it automatically, without having to hack it with GIF methods.

Desktop browsers have also used this strategy recently, as shown in the updated Safari 11:

And the Chrome documentation:

This strategy will hit video sites the hardest, as shown by Tudou’s prompt in Safari:

A setup wizard has been added. Chrome’s ban is more personal. It has a MEI policy, which basically says that as long as the user has actively played more than 7s of audio and video on the current page (the video window must not be less than 200 x 140), it will allow autoplay.


How can web developers effectively avoid this risk?

Chrome’s documentation gives a best practice: give audio and video a muted attribute to auto-play, then display a button that gives the user a cue to click on a muted sound. For video, you can do this, but for audio, a lot of people are listening for page clicks, and once you tap it, it starts playing sound, which is usually background music. But how do you auto-play multiple sounds for a page with multiple sound sources?

First, if the user calls the sound API before interacting with it, Chrome prompts:

DOMException: play() failed because the user didn’t interact with the document first.

This is what Safari says:

NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

Chrome’s error message is the friendliest, meaning that the user has not yet interacted and cannot play. What are the user interactions? This includes user-triggered touchend, click, Doubleclick or KeyDown events where play can be set.

So as mentioned above, a lot of people are listening to the whole page of click events to play, no matter where they click, as long as they click, including touch down. This method only applies to one sound source, not multiple sounds. How should multiple sounds be broken? Here does not mean and browser capabilities, “playing god”, our aim is to enhance the user experience, because some of the scenes if can automatically play is really good, as some of the questions scene, need to listen for the answer, if the user in the process of problem solving that can in turn automatically play the voice of the corresponding title, is more convenient. It also discusses the technical implementation of sound playback.

Native video should only be played with the video tag, but native audio, in addition to the audio tag, has another API called AudioContext, which is used to control the playback of sound and has a lot of rich control interfaces. The difference between AudioContext and AudioContext is that it plays anywhere on the page after the user has clicked on it. So you can play the sound with an AudioContext instead of an Audio tag.

We first use audio. Play to check if the page supports autoplay so we can decide when to play.

1. Auto-play detection

Create an audio element, give it a SRC, append it to the DOM, and then call play to see if an exception is thrown. If it is caught, it is not supported, as shown in this code:

function testAutoPlay () {
    // Return a promise to tell the caller the result of the test
    return new Promise(resolve= > {
        let audio = document.createElement('audio');
        // require a local file, which is in base64 format
        audio.src = require('@/assets/empty-audio.mp3');
        document.body.appendChild(audio);
        let autoplay = true;
        // Play returns a promise
        audio.play().then((a)= > {
            // Support autoplay
            autoplay = true;
        }).catch(err= > {
            // Autoplay is not supported
            autoplay = false;
        }).finally((a)= > {
            audio.remove();
            // Tell the caller the result
            resolve(autoplay);
        });
    });
}Copy the code

Here we use an empty audio file, which is an MP3 file of 0’s length, only 4KB in size, and is webpacked in local Base64 format, so instead of calling play after the canplay event, write synchronous code directly. If SRC is a remote URL, So you have to listen for the CanPlay event and play in it.

Use the Promise resolve approach when telling the caller the result, because the play result is asynchronous and await is not recommended for library functions.

2. Listen to interactive clicks on the page

If the current page can play automatically, then feel free to let the sound play automatically. Otherwise, it will not play until the user starts interacting with the page and clicks, as shown in this code:

let audioInfo = {
    autoplay: false,
    testAutoPlay () {
        // The code is the same as...
    },
    // Listen for click events on the page. Once clicked, you can autoplay
    setAutoPlayWhenClick () {
        function setAutoPlay () {
            // Set autoplay to true
            audioInfo.autoplay = true;
            document.removeEventListener('click', setAutoPlay);
            document.removeEventListener('touchend', setAutoPlay);
        }
        document.addEventListener('click', setCallback);
        document.addEventListener('touchend', setCallback);
    },
    init () {
        // Check whether autoplay can be performed
        audioInfo.testAutoPlay().then(autoplay= > {
            if (!audioInfo.autoplay) {
                audioInfo.autoplay = autoplay;
            }
        });
        // Set it to play automatically after the user clicks the interactionaudioInfo.setAutoPlayWhenClick(); }}; audioInfo.init();export default audioInfo;Copy the code

The code above listens for the Document click event and sets the autoplay value to true inside the click event. In other words, as soon as the user clicks, we can call AudioContext’s play API at any time, even if it’s not in the click event response function, and we can’t call AudioContext in the asynchronous callback, but AudioContext can.

The code finally stores the autoplayable information in the variable audioinfo.autoplay by calling audioinfo.init. When you need to play the sound, for example, cut to the next question and need to automatically play several audio resources of the current question, you can take this variable to determine whether it can be played automatically. If it can be played, it can be played, not just wait for the user to click the sound icon to play, and if he clicked once, it can be played automatically.

So how do you play a sound with an AudioContext?

3. AudioContext plays the sound

First request the audio file, put it into the ArrayBuffer, then use the AudioContext API to decode it, and then let it play.

Let’s write an Ajax request for an audio file:

function request (url) {
    return new Promise (resolve= > {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        // Set the XHR Response format to arrayBuffer
        // Otherwise, the default is binary text
        xhr.responseType = 'arraybuffer';
        xhr.onreadystatechange = function () {
            // The request was completed and succeeded
            if (xhr.readyState === 4 && xhr.status === 200) { resolve(xhr.response); }}; xhr.send(); }); }Copy the code

The important thing to note here is to change the XHR response type to ArrayBuffer, because decode needs to use this storage format. After this setting, xhr.response is an ArrayBuffer format.

Next, instantiate an AudioContext and ask it to decode and play, as shown below:

// Safari uses the webKit prefix
let context = new (window.AudioContext || window.webkitAudioContext)();
// Request audio data
let audioMedia = await request(url);
// Decode and play
context.decodeAudioData(audioMedia, decode => play(context, decode));Copy the code

The play function is implemented as follows:

function play (context, decodeBuffer) {
    let source = context.createBufferSource();
    source.buffer = decodeBuffer;
    source.connect(context.destination);
    // Start from 0s
    source.start(0);
}Copy the code

So that implements the basic function of AudioContext to play audio.

If the current page is not autoplay, the Chrome console will issue a warning when the new AudioContext is created:

So what this means is, you’re initializing an AudioContext before the user’s actually interacting with the page, and I’m not going to let you play it, you have to resume the context after the user clicks on it before you can play it.

Suppose we ignore this warning and call play directly with no error, but no sound. So you need to use the information from the previous step audioInfo. autoPlay, if this is true, you can play, otherwise you can’t play, you need to let the user click on the sound icon toplay. So, reorganize the code:

function play (context, decodeBuffer) {
    // Call resume to resume playing
    context.resume();
    let source = context.createBufferSource();
    source.buffer = decodeBuffer;
    source.connect(context.destination);
    source.start(0);
}

function playAudio (context, url) {
    let audioMedia = await request(url);
    context.decodeAudioData(audioMedia, decode => play(context, decode));
}

let context = new (window.AudioContext || window.webkitAudioContext)();
// If it can play automatically
if (audioInfo.autoplay) {
    playAudio(url);
}
// Users can click on the sound icon to play
$('.audio-icon').on('click'.function () {
    playAudio($(this).data('url'));
});Copy the code

After resume is set, the context will be played if there is an audio that has been disabled before, and automatically play the context will be restored if there is not. This serves the basic purpose. If autoplay is supported, play it directly in the code. If not, wait for a click. Once you tap it, whatever you click on will play automatically. You can do something like automatically broadcast the audio of a question every 3s:

// Automatically play a sound every 3 seconds
playAudio('question-1.mp3');
setTimeout((a)= > playAudio(context, 'question-2.mp3'), 3000);
setTimeout((a)= > playAudio(context, 'question-3.mp3'), 3000);Copy the code

Here’s another problem: how do you know when each sound is finished and then play the next sound every three seconds? The decodeBuffer has the duration property of the current audio, and the context.currentTime property has the precision of the current playback time. Then you can make a timer. Compare context.currentTime to docode.duration every 100ms, if it is, it is done. This is how the SoundJS library is implemented, and we can use this library to facilitate the operation of sound.

This achieves the goal of using AudioContext to automatically play multiple audio. The limitation is that it doesn’t automatically play the first time the user opens the page, but it does once the user has clicked anywhere on the page.

AudioContext does a couple of other things.

4. AudioContext controls the sound properties

For example, this CSS Tricks lists several examples, one of which is writing an electronic xylophone with the AudioContext oscillator:

This example does not use any Audio resources, it is all synthesized directly, feel like this Demo: Play the Xylophone (Web Audio API).

Here’s an example of this reverb equalizer:

See this codepen: Web Audio API: Parametric EQUALIZER.


Finally, it has long been only mobile browsers that disable autoplay of audio and video, and now desktop browsers are starting to do the same. The goal of the browser is to create a quiet environment where the user doesn’t want to open a page with all kinds of ads and other sounds playing. But browsers are not one-size fits all, at least allowing mute audio and video playback. So for videos, you can mute autoplay, add an icon where the sound is turned off and let the user click on it, and add a setup wizard or something to guide the user to Settings that allow autoplay on the current site. For sounds, you can use the AudioContext API, and as soon as the page is clicked once, the AudioContext is activated, and you can control playback directly from your code.

The above can be used as a reference for current best practices of web multimedia playback.

Efficient Front End is going for a second printing. I heard you haven’t bought it yet