Skip to content

Commit 310736b

Browse files
committed
Update mobile auto enable to also unlock desktop Chrome
* Adds a new `unlock` event. * Fires `playerror` when sound tries to play while locked.
1 parent 97a6353 commit 310736b

File tree

2 files changed

+50
-18
lines changed

2 files changed

+50
-18
lines changed

README.md

+23-5
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ Fires when the sound's playback rate has changed. The first parameter is the ID
225225
Fires when the sound has been seeked. The first parameter is the ID of the sound.
226226
#### onfade `Function`
227227
Fires when the current sound finishes fading in/out. The first parameter is the ID of the sound.
228+
#### onunlock `Function`
229+
Fires when audio has been automatically unlocked through a touch/click event.
228230

229231

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

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

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

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

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

417419

418-
### Mobile Playback
419-
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:
420+
### Mobile/Chrome Playback
421+
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:
420422

421423
```javascript
422424
Howler.mobileAutoEnable = false;
423425
```
424426

427+
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:
428+
429+
```javascript
430+
var sound = new Howl({
431+
src: ['sound.webm', 'sound.mp3'],
432+
onplayerror: function() {
433+
sound.once('unlock', function() {
434+
sound.play();
435+
});
436+
}
437+
});
438+
439+
sound.play();
440+
```
441+
442+
425443
### Dolby Audio Playback
426444
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.
427445

src/howler.core.js

+27-13
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,13 @@
279279
var self = this || Howler;
280280

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

288287
self._mobileEnabled = false;
288+
self.mobileAutoEnable = false;
289289

290290
// Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views.
291291
// Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000.
@@ -302,7 +302,9 @@
302302
// Call this method on touch start to create and play a buffer,
303303
// then check if the audio actually played to determine if
304304
// audio has now been unlocked on iOS, Android, etc.
305-
var unlock = function() {
305+
var unlock = function(e) {
306+
e.preventDefault();
307+
306308
// Fix Android can not play in suspend state.
307309
Howler._autoResume();
308310

@@ -329,17 +331,23 @@
329331

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

334335
// Remove the touch start listener.
335336
document.removeEventListener('touchstart', unlock, true);
336337
document.removeEventListener('touchend', unlock, true);
338+
document.removeEventListener('click', unlock, true);
339+
340+
// Let all sounds know that audio has been unlocked.
341+
for (var i=0; i<self._howls.length; i++) {
342+
self._howls[i]._emit('unlock');
343+
}
337344
};
338345
};
339346

340347
// Setup a touch start listener to attempt an unlock in.
341348
document.addEventListener('touchstart', unlock, true);
342349
document.addEventListener('touchend', unlock, true);
350+
document.addEventListener('click', unlock, true);
343351

344352
return self;
345353
},
@@ -498,6 +506,7 @@
498506
self._onvolume = o.onvolume ? [{fn: o.onvolume}] : [];
499507
self._onrate = o.onrate ? [{fn: o.onrate}] : [];
500508
self._onseek = o.onseek ? [{fn: o.onseek}] : [];
509+
self._onunlock = o.onunlock ? [{fn: o.onunlock}] : [];
501510
self._onresume = [];
502511

503512
// Web Audio or HTML5 Audio?
@@ -771,13 +780,18 @@
771780
self._playLock = true;
772781

773782
// Releases the lock and executes queued actions.
774-
var runLoadQueue = function() {
775-
self._playLock = false;
776-
if (!internal) {
777-
self._emit('play', sound._id);
778-
}
779-
};
780-
play.then(runLoadQueue, runLoadQueue);
783+
play
784+
.then(function() {
785+
self._playLock = false;
786+
if (!internal) {
787+
self._emit('play', sound._id);
788+
}
789+
})
790+
.catch(function() {
791+
self._playLock = false;
792+
self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
793+
'on mobile devices and Chrome where playback was not within a user interaction.');
794+
});
781795
} else if (!internal) {
782796
self._emit('play', sound._id);
783797
}
@@ -788,7 +802,7 @@
788802
// If the node is still paused, then we can assume there was a playback issue.
789803
if (node.paused) {
790804
self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
791-
'on mobile devices where playback was not within a user interaction.');
805+
'on mobile devices and Chrome where playback was not within a user interaction.');
792806
return;
793807
}
794808

0 commit comments

Comments
 (0)