From c02eb70f04bd3b352242fdd873326a64d3519ab8 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 29 May 2026 02:01:33 -0700 Subject: [PATCH] fix notification sound listener cleanup --- src/preload/index.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/preload/index.ts b/src/preload/index.ts index 3d9af79af3e..f8b7a9f3bb6 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -266,9 +266,19 @@ let cachedNotificationSound: { audio: HTMLAudioElement } | null = null let isNotificationSoundPlaying = false +// Why: audio.play() can reject before ended/error fires; keep a cleanup hook +// so failed or replaced plays do not accumulate listeners on the cached Audio. +let cleanupNotificationSoundPlayback: (() => void) | null = null + +function clearNotificationSoundPlaybackState(): void { + cleanupNotificationSoundPlayback?.() + cleanupNotificationSoundPlayback = null + isNotificationSoundPlaying = false +} function disposeCachedNotificationSound(): void { if (cachedNotificationSound) { + clearNotificationSoundPlaybackState() cachedNotificationSound.audio.pause() cachedNotificationSound.audio.src = '' URL.revokeObjectURL(cachedNotificationSound.blobUrl) @@ -1549,11 +1559,21 @@ const api = { audio.volume = Math.min(1, Math.max(0, options.volume / 100)) } isNotificationSoundPlaying = true + cleanupNotificationSoundPlayback?.() const release = (): void => { + cleanup() + if (cleanupNotificationSoundPlayback === cleanup) { + cleanupNotificationSoundPlayback = null + } isNotificationSoundPlaying = false } - audio.addEventListener('ended', release, { once: true }) - audio.addEventListener('error', release, { once: true }) + const cleanup = (): void => { + audio.removeEventListener('ended', release) + audio.removeEventListener('error', release) + } + cleanupNotificationSoundPlayback = cleanup + audio.addEventListener('ended', release) + audio.addEventListener('error', release) try { await audio.play() } catch { @@ -1562,7 +1582,7 @@ const api = { } return { played: true } } catch { - isNotificationSoundPlaying = false + clearNotificationSoundPlaybackState() return { played: false, reason: 'playback-failed' } } }