From 7121d09913b7a2abbdd9a22736f4fa6d164e29e1 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 (cherry picked from commit b90cbc47f204ddef808b0ae36ea487bf9ceec092) --- .../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 979724771..4a42d01b0 100644 --- a/bundles/org.openhab.ui/web/src/components/app.vue +++ b/bundles/org.openhab.ui/web/src/components/app.vue @@ -286,6 +286,7 @@ export default { init: false, ready: false, eventSource: null, + audioContext: null, // Framework7 Parameters f7params: { @@ -620,29 +621,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)) } } } },