@@ -178,23 +178,20 @@ export function shouldPlay(
178178type CurrentThreadIdAccessor = ( ) => ThreadId | null ;
179179
180180class 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