Skip to content

Commit

Permalink
Update mobile auto enable to also unlock desktop Chrome
Browse files Browse the repository at this point in the history
* Adds a new `unlock` event.
* Fires `playerror` when sound tries to play while locked.
  • Loading branch information
goldfire committed Jul 12, 2018
1 parent 97a6353 commit 310736b
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 18 deletions.
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ Fires when the sound's playback rate has changed. The first parameter is the ID
Fires when the sound has been seeked. The first parameter is the ID of the sound.
#### onfade `Function`
Fires when the current sound finishes fading in/out. The first parameter is the ID of the sound.
#### onunlock `Function`
Fires when audio has been automatically unlocked through a touch/click event.


### Methods
Expand Down Expand Up @@ -285,19 +287,19 @@ Get the duration of the audio source. Will return 0 until after the `load` event

#### on(event, function, [id])
Listen for events. Multiple events can be added by calling this multiple times.
* **event**: `String` Name of event to fire/set (`load`, `loaderror`, `playerror`, `play`, `end`, `pause`, `stop`, `mute`, `volume`, `rate`, `seek`, `fade`).
* **event**: `String` Name of event to fire/set (`load`, `loaderror`, `playerror`, `play`, `end`, `pause`, `stop`, `mute`, `volume`, `rate`, `seek`, `fade`, `unlock`).
* **function**: `Function` Define function to fire on event.
* **id**: `Number` `optional` Only listen to events for this sound id.

#### once(event, function, [id])
Same as `on`, but it removes itself after the callback is fired.
* **event**: `String` Name of event to fire/set (`load`, `loaderror`, `playerror`, `play`, `end`, `pause`, `stop`, `mute`, `volume`, `rate`, `seek`, `fade`).
* **event**: `String` Name of event to fire/set (`load`, `loaderror`, `playerror`, `play`, `end`, `pause`, `stop`, `mute`, `volume`, `rate`, `seek`, `fade`, `unlock`).
* **function**: `Function` Define function to fire on event.
* **id**: `Number` `optional` Only listen to events for this sound id.

#### off(event, [function], [id])
Remove event listener that you've set. Call without parameters to remove all events.
* **event**: `String` Name of event (`load`, `loaderror`, `playerror`, `play`, `end`, `pause`, `stop`, `mute`, `volume`, `rate`, `seek`, `fade`).
* **event**: `String` Name of event (`load`, `loaderror`, `playerror`, `play`, `end`, `pause`, `stop`, `mute`, `volume`, `rate`, `seek`, `fade`, `unlock`).
* **function**: `Function` `optional` The listener to remove. Omit this to remove all events of type.
* **id**: `Number` `optional` Only remove events for this sound id.

Expand Down Expand Up @@ -415,13 +417,29 @@ Get/set the direction the listener is pointing in the 3D cartesian space. A fron
* **zUp**: `Number` The z-orientation of the top of the listener.


### Mobile Playback
By default, audio on iOS, Android, etc is locked until a sound is played within a user interaction, and then it plays normally the rest of the page session ([Apple documentation](https://developer.apple.com/library/safari/documentation/audiovideo/conceptual/using_html5_audio_video/PlayingandSynthesizingSounds/PlayingandSynthesizingSounds.html)). The default behavior of howler.js is to attempt to silently unlock audio playback by playing an empty buffer on the first `touchend` event. This behavior can be disabled by calling:
### Mobile/Chrome Playback
By default, audio on mobile browsers and Chrome is locked until a sound is played within a user interaction, and then it plays normally the rest of the page session ([Apple documentation](https://developer.apple.com/library/safari/documentation/audiovideo/conceptual/using_html5_audio_video/PlayingandSynthesizingSounds/PlayingandSynthesizingSounds.html)). The default behavior of howler.js is to attempt to silently unlock audio playback by playing an empty buffer on the first `touchend` event. This behavior can be disabled by calling:

```javascript
Howler.mobileAutoEnable = false;
```

If you try to play audio automatically on page load, you can listen to a `playerror` event and then wait for the `unlock` event to try and play the audio again:

```javascript
var sound = new Howl({
src: ['sound.webm', 'sound.mp3'],
onplayerror: function() {
sound.once('unlock', function() {
sound.play();
});
}
});

sound.play();
```


### Dolby Audio Playback
Full support for playback of the Dolby Audio format (currently support in Edge and Safari) is included. However, you must specify that the file you are loading is `dolby` since it is in a `mp4` container.

Expand Down
40 changes: 27 additions & 13 deletions src/howler.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,13 @@
var self = this || Howler;

// Only run this on mobile devices if audio isn't already eanbled.
var isMobile = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi/i.test(self._navigator && self._navigator.userAgent);
var isTouch = !!(('ontouchend' in window) || (self._navigator && self._navigator.maxTouchPoints > 0) || (self._navigator && self._navigator.msMaxTouchPoints > 0));
if (self._mobileEnabled || !self.ctx || (!isMobile && !isTouch)) {
var isMobile = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi|Chrome/i.test(self._navigator && self._navigator.userAgent);
if (self._mobileEnabled || !self.ctx || !isMobile) {
return;
}

self._mobileEnabled = false;
self.mobileAutoEnable = false;

// Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views.
// Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000.
Expand All @@ -302,7 +302,9 @@
// Call this method on touch start to create and play a buffer,
// then check if the audio actually played to determine if
// audio has now been unlocked on iOS, Android, etc.
var unlock = function() {
var unlock = function(e) {
e.preventDefault();

This comment has been minimized.

Copy link
@xdumaine

xdumaine Aug 24, 2018

Why was this added? Putting preventDefault on a document click handler is really really bad practice.


// Fix Android can not play in suspend state.
Howler._autoResume();

Expand All @@ -329,17 +331,23 @@

// Update the unlocked state and prevent this check from happening again.
self._mobileEnabled = true;
self.mobileAutoEnable = false;

// Remove the touch start listener.
document.removeEventListener('touchstart', unlock, true);
document.removeEventListener('touchend', unlock, true);
document.removeEventListener('click', unlock, true);

// Let all sounds know that audio has been unlocked.
for (var i=0; i<self._howls.length; i++) {
self._howls[i]._emit('unlock');
}
};
};

// Setup a touch start listener to attempt an unlock in.
document.addEventListener('touchstart', unlock, true);
document.addEventListener('touchend', unlock, true);
document.addEventListener('click', unlock, true);

return self;
},
Expand Down Expand Up @@ -498,6 +506,7 @@
self._onvolume = o.onvolume ? [{fn: o.onvolume}] : [];
self._onrate = o.onrate ? [{fn: o.onrate}] : [];
self._onseek = o.onseek ? [{fn: o.onseek}] : [];
self._onunlock = o.onunlock ? [{fn: o.onunlock}] : [];
self._onresume = [];

// Web Audio or HTML5 Audio?
Expand Down Expand Up @@ -771,13 +780,18 @@
self._playLock = true;

// Releases the lock and executes queued actions.
var runLoadQueue = function() {
self._playLock = false;
if (!internal) {
self._emit('play', sound._id);
}
};
play.then(runLoadQueue, runLoadQueue);
play
.then(function() {
self._playLock = false;
if (!internal) {
self._emit('play', sound._id);
}
})
.catch(function() {
self._playLock = false;
self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
'on mobile devices and Chrome where playback was not within a user interaction.');
});
} else if (!internal) {
self._emit('play', sound._id);
}
Expand All @@ -788,7 +802,7 @@
// If the node is still paused, then we can assume there was a playback issue.
if (node.paused) {
self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
'on mobile devices where playback was not within a user interaction.');
'on mobile devices and Chrome where playback was not within a user interaction.');
return;
}

Expand Down

0 comments on commit 310736b

Please sign in to comment.