vnglst
vnglst

Reputation: 642

Safari 11's new autoplay restrictions on HTML5 Audio

I'm trying to figure out how Safari 11's (and iOS') autoplay restrictions are implemented and I don't understand why the following doesn't start playing the audio file:

/*
    Call stack, this doesn't work 💩
*/

const btn = document.createElement('BUTTON')
const textLabel = document.createTextNode('Play')
const audio = new window.Audio()
audio.src = 'https://raw.githubusercontent.com/vnglst/autoplay-tutorial/master/mp3/winamp.mp3'
// audio.controls = true

btn.appendChild(textLabel)
document.getElementById('root').appendChild(btn)
document.getElementById('root').appendChild(audio)

btn.onclick = e => {
  window
    .fetch(`https://api.github.com/repos/vnglst/autoplay-tutorial/contents/mp3/modem-sound.mp3`)
    .then(resp => resp.json())
    .then(json => {
      audio.src = json.download_url
      audio.play()
    })
}
<div id='root'/>

Whereas Safari is fine with the following:

/*
    Call stack, using a fake Promise. This works 👍
*/

const btn = document.createElement('BUTTON')
const textLabel = document.createTextNode('Play')
const audio = new window.Audio()
audio.src = 'https://raw.githubusercontent.com/vnglst/autoplay-tutorial/master/mp3/modem-sound.mp3'
// audio.controls = true

btn.appendChild(textLabel)
document.getElementById('root').appendChild(btn)
document.getElementById('root').appendChild(audio)

const mockedPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const src = 'https://raw.githubusercontent.com/vnglst/autoplay-tutorial/master/mp3/winamp.mp3'
    return resolve(src)
  }, 500)
})

btn.onclick = (e) => {
  mockedPromise.then(src => {
    audio.src = src
    audio.play()
  })
}
<div id='root'/>

Does anybody know how Safari determines whether something is an autoplay or not? I'm not looking for a work around (starting and pausing helps for instance) but I'm trying to figure out how this works.

(some more background information on Safari's new autoplay policy can be found here: https://webkit.org/blog/7734/auto-play-policy-changes-for-macos/)

Upvotes: 22

Views: 6194

Answers (2)

Artem Bernatskyi
Artem Bernatskyi

Reputation: 4667

You need to change your algorithm from

user clicks -> loads audio information -> changes source -> play

to

user clicks -> changes source -> play

Basically you need to change src in audio and start playback right after user clicks play button.

iOS prevents autoplay when intermediary fetching is present.

Upvotes: 1

DataCure
DataCure

Reputation: 425

Autoplay

audio.autoplay = true;

Please note that with mobile devices W3C have specified the user must trigger the playback in order to avoid mobile data charges.

Therefore with mobile devices, you will need to present the user with the button to trigger the autoplay.

const btn = document.createElement('BUTTON');
const textLabel = document.createTextNode('Play');
const audio = new window.Audio();
audio.src = 'https://raw.githubusercontent.com/vnglst/autoplay-tutorial/master/mp3/modem-sound.mp3';
audio.autoplay = true;
// audio.controls = true

btn.appendChild(textLabel)
document.getElementById('root').appendChild(btn);
document.getElementById('root').appendChild(audio);

const mockedPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const src = 'https://raw.githubusercontent.com/vnglst/autoplay-tutorial/master/mp3/winamp.mp3';
    return resolve(src);
  }, 500);
})

btn.onclick = (e) => {
  mockedPromise.then(src => {
    audio.src = src;
    audio.play();
  })
}
<div id='root'/>

Upvotes: 0

Related Questions