From e9ac2de6cb1614589309c5ed8ee239ea317d763f Mon Sep 17 00:00:00 2001 From: dealerweb Date: Thu, 2 Jul 2026 13:46:08 +0200 Subject: [PATCH] Fix: notification sound preview - base-path prefix + longer default beep The sound picker's preview always played the default beep, even for the other choices, on a subpath deployment. playFile() used a raw '/notification/x.mp3' path, which 404s under a deployment base path (e.g. /webmail); audio.play() then rejected and fell back to the beep for every non-default choice. Prefix the file with withBasePath(). The default beep was a 150 ms tone with no envelope - easy to miss on Bluetooth outputs, whose audio path can take 100-200 ms to wake up and route. Lengthen it to ~0.45 s with a fade in/out (also removes click artifacts). --- lib/notification-sound.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/notification-sound.ts b/lib/notification-sound.ts index 13a21a34..36589e31 100644 --- a/lib/notification-sound.ts +++ b/lib/notification-sound.ts @@ -1,4 +1,5 @@ import { debug } from '@/lib/debug'; +import { withBasePath } from '@/lib/browser-navigation'; export type NotificationSoundChoice = 'default' | 'cheerful' | 'involved' | 'swift' | 'relax'; @@ -20,15 +21,29 @@ function playBeep() { oscillator.frequency.value = 800; oscillator.type = 'sine'; - gainNode.gain.value = 0.1; - oscillator.start(); - oscillator.stop(audioContext.currentTime + 0.15); + // Longer, enveloped tone. A 150 ms blip was easy to miss on Bluetooth + // outputs, whose audio path can take 100-200 ms to wake up and route - by + // the time sound reached the headphones the blip was already over. The + // fade in/out also avoids click artifacts. + const now = audioContext.currentTime; + const duration = 0.45; + const peak = 0.12; + gainNode.gain.setValueAtTime(0.0001, now); + gainNode.gain.exponentialRampToValueAtTime(peak, now + 0.04); + gainNode.gain.setValueAtTime(peak, now + duration - 0.08); + gainNode.gain.exponentialRampToValueAtTime(0.0001, now + duration); + + oscillator.start(now); + oscillator.stop(now + duration + 0.02); oscillator.onended = () => audioContext.close(); } function playFile(file: string) { - const audio = new Audio(file); + // Prefix with the deployment base path (e.g. /webmail); a raw "/notification/ + // x.mp3" 404s under a subpath, which made playFile fall back to the beep for + // every choice. + const audio = new Audio(withBasePath(file)); audio.volume = 0.3; audio.play().catch((e) => { debug.log('push', 'Could not play audio file, falling back to beep:', e);