Fixes WebAudio sink not playing on Safari (#1765)

Fixes #1717.

Safari (and Chrome possibly as well) requires a user interaction after a
web audio stream has been requested, which means audio will not play
until at least one stream has been started and then the user clicks
somewhere in the web app.
This implements a workaround
(https://www.mattmontag.com/web/unlock-web-audio-in-safari-for-ios-and-macos)
which unlocks and stores the AudioContext globally, so that after the
first stream only a single interaction is required for the lifetime of
that browser session.

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
pull/1783/head
Dan Cunningham 2023-03-08 13:50:26 -08:00 committed by GitHub
parent 32470b36ca
commit b90cbc47f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 20 additions and 13 deletions

View File

@ -290,6 +290,7 @@ export default {
init: false,
ready: false,
eventSource: null,
audioContext: null,
// Framework7 Parameters
f7params: {
@ -624,29 +625,35 @@ export default {
this.eventSource = null
},
playAudioUrl (audioUrl) {
let context
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext
if (typeof (window.AudioContext) !== 'undefined') {
context = new AudioContext()
if (!this.audioContext) {
window.AudioContext = window.AudioContext || window.webkitAudioContext
if (typeof (window.AudioContext) !== 'undefined') {
this.audioContext = new AudioContext()
unlockAudioContext(this.audioContext)
}
}
console.log('Playing audio URL: ' + audioUrl)
this.$oh.api.getPlain(audioUrl, '', '*/*', 'arraybuffer').then((data) => {
context.decodeAudioData(data, function (buffer) {
let source = context.createBufferSource()
this.audioContext.decodeAudioData(data, (buffer) => {
let source = this.audioContext.createBufferSource()
source.buffer = buffer
source.connect(context.destination)
source.onended = function () {
context.close()
}
source.connect(this.audioContext.destination)
source.start(0)
})
})
} catch (e) {
console.warn('Error while playing audio URL: ' + e.toString())
if (context) {
context.close()
}
}
// Safari requires a touch event after the stream has started, hence this workaround
// Credit: https://www.mattmontag.com/web/unlock-web-audio-in-safari-for-ios-and-macos
function unlockAudioContext (audioContext) {
if (audioContext.state !== 'suspended') return
const b = document.body
const events = ['touchstart', 'touchend', 'mousedown', 'keydown']
events.forEach(e => b.addEventListener(e, unlock, false))
function unlock () { audioContext.resume().then(clean) }
function clean () { events.forEach(e => b.removeEventListener(e, unlock)) }
}
}
},