From 8bb06108e9f89eeffce7e4d53505f4d3ae0ce549 Mon Sep 17 00:00:00 2001 From: Jakob Wierzba Date: Mon, 11 Mar 2024 13:51:10 +0100 Subject: [PATCH] work around suspended audio streams on iOS Safari 17.4 #1711 --- src/howler.core.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/howler.core.js b/src/howler.core.js index 5198bdfc..0a58c32d 100644 --- a/src/howler.core.js +++ b/src/howler.core.js @@ -993,6 +993,12 @@ }; node.addEventListener(Howler._canPlayEvent, listener, false); + // the node is not actually playing (has received suspend event & NETWORK_IDLE) + if (node.networkState === 1 && node._wasSuspended) { + console.log('wake up suspended audio node') + node.play() + } + // Cancel the end timer. self._clearTimer(sound._id); } @@ -1763,6 +1769,8 @@ // Remove any event listeners. sounds[i]._node.removeEventListener('error', sounds[i]._errorFn, false); sounds[i]._node.removeEventListener(Howler._canPlayEvent, sounds[i]._loadFn, false); + sounds[i]._node.removeEventListener('loadedmetadata', sounds[i]._loadFn, false); + sounds[i]._node.removeEventListener('suspend', setAudioNodeWasSuspendedFromEvent, false); sounds[i]._node.removeEventListener('ended', sounds[i]._endFn, false); // Release the Audio object back to the pool. @@ -2264,6 +2272,11 @@ self._loadFn = self._loadListener.bind(self); self._node.addEventListener(Howler._canPlayEvent, self._loadFn, false); + // sometimes canplaythrough does not fire if the audio node is suspended (see below) + // so we make extra sure to kick off the event queue here + // TODO: this could have side effects beyond the _wasSuspended mitigation... + self._node.addEventListener('loadedmetadata', self._loadFn, false); + // Listen for the 'ended' event on the sound to account for edge-case where // a finite sound has a duration of Infinity. self._endFn = self._endListener.bind(self); @@ -2274,6 +2287,10 @@ self._node.preload = parent._preload === true ? 'auto' : parent._preload; self._node.volume = volume * Howler.volume(); + // we record the suspend event with a dirty param in case we need to mitigate it later + self._node._wasSuspended = false + self._node.addEventListener('suspend', setAudioNodeWasSuspendedFromEvent, false) + // Begin loading the source. self._node.load(); } @@ -2340,8 +2357,9 @@ parent._loadQueue(); } - // Clear the event listener. + // Clear the event listeners self._node.removeEventListener(Howler._canPlayEvent, self._loadFn, false); + self._node.removeEventListener('loadedmetadata', self._loadFn, false); }, /** @@ -2506,6 +2524,10 @@ } }; + var setAudioNodeWasSuspendedFromEvent = function(evt) { + evt.target._wasSuspended = true + } + /** * Setup the audio context when available, or switch to HTML5 Audio mode. */