From b90cbc47f204ddef808b0ae36ea487bf9ceec092 Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Wed, 8 Mar 2023 13:50:26 -0800 Subject: [PATCH] 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 --- .../org.openhab.ui/web/src/components/app.vue | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/bundles/org.openhab.ui/web/src/components/app.vue b/bundles/org.openhab.ui/web/src/components/app.vue index e0a2418bf..209d34e44 100644 --- a/bundles/org.openhab.ui/web/src/components/app.vue +++ b/bundles/org.openhab.ui/web/src/components/app.vue @@ -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)) } } } },