Skip to content

Commit fcfa264

Browse files
committed
fix(web): create fresh notification audio elements
1 parent e96f2a9 commit fcfa264

1 file changed

Lines changed: 8 additions & 22 deletions

File tree

apps/web/src/notificationSound.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -178,23 +178,20 @@ export function shouldPlay(
178178
type CurrentThreadIdAccessor = () => ThreadId | null;
179179

180180
class NotificationSoundManager {
181-
private audio: HTMLAudioElement | null = null;
182181
private lastPlayAtMs = 0;
183182
private getCurrentThreadId: CurrentThreadIdAccessor = () => null;
184183

185184
setCurrentThreadAccessor(accessor: CurrentThreadIdAccessor): void {
186185
this.getCurrentThreadId = accessor;
187186
}
188187

189-
private ensureAudio(): HTMLAudioElement | null {
188+
// Fresh elements avoid stale media state after output-device or autoplay-state changes.
189+
private createAudio(): HTMLAudioElement | null {
190190
if (typeof document === "undefined") return null;
191-
if (this.audio === null) {
192-
const audio = new Audio(NOTIFICATION_SOUND_URL);
193-
audio.preload = "auto";
194-
audio.volume = 1;
195-
this.audio = audio;
196-
}
197-
return this.audio;
191+
const audio = new Audio(NOTIFICATION_SOUND_URL);
192+
audio.preload = "auto";
193+
audio.volume = 1;
194+
return audio;
198195
}
199196

200197
private buildFocusContext(): NotificationFocusContext {
@@ -217,14 +214,9 @@ class NotificationSoundManager {
217214
if (!shouldPlay(triggers, settings, focusContext, nowMs, this.lastPlayAtMs)) {
218215
return;
219216
}
220-
const audio = this.ensureAudio();
217+
const audio = this.createAudio();
221218
if (!audio) return;
222219
this.lastPlayAtMs = nowMs;
223-
try {
224-
audio.currentTime = 0;
225-
} catch {
226-
// Some browsers throw when setting currentTime before metadata loads.
227-
}
228220
void audio.play().catch((error) => {
229221
console.warn("[NOTIFICATION_SOUND] play failed", error);
230222
});
@@ -235,21 +227,15 @@ class NotificationSoundManager {
235227
* so callers can show a toast on rejection (e.g. autoplay blocked).
236228
*/
237229
async playTest(): Promise<void> {
238-
const audio = this.ensureAudio();
230+
const audio = this.createAudio();
239231
if (!audio) {
240232
throw new Error("Audio playback is not available in this environment.");
241233
}
242-
try {
243-
audio.currentTime = 0;
244-
} catch {
245-
// ignore
246-
}
247234
await audio.play();
248235
}
249236

250237
/** Test-only: reset internal state. */
251238
resetForTests(): void {
252-
this.audio = null;
253239
this.lastPlayAtMs = 0;
254240
this.getCurrentThreadId = () => null;
255241
}

0 commit comments

Comments
 (0)