From a74722ed746a54f176a1fb44c8d9d1ae7db3d950 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 27 Apr 2026 10:25:25 -0400 Subject: [PATCH] coreaudio: Always init/deinit session listener on iOS. Previously, if UpdateAudioSession() failed on close--which it might if something strange has happened with the system's audio configuration--the listener wouldn't be deregistered, and would risk touching a free'd pointer if the app moved to or from the background afterwards, firing an event handler that should have been deregistered. Closes #15439. --- src/audio/coreaudio/SDL_coreaudio.m | 70 +++++++++++++++-------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index dda5f573c7..471a472c7f 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -502,39 +502,6 @@ static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_pl [session setActive:NO error:nil]; session_active = false; } - - if (open) { - SDLInterruptionListener *listener = [SDLInterruptionListener new]; - listener.device = device; - - [center addObserver:listener - selector:@selector(audioSessionInterruption:) - name:AVAudioSessionInterruptionNotification - object:session]; - - /* An interruption end notification is not guaranteed to be sent if - we were previously interrupted... resuming if needed when the app - becomes active seems to be the way to go. */ - // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications. - [center addObserver:listener - selector:@selector(applicationBecameActive:) - name:UIApplicationDidBecomeActiveNotification - object:nil]; - - [center addObserver:listener - selector:@selector(applicationBecameActive:) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - - device->hidden->interruption_listener = CFBridgingRetain(listener); - } else { - SDLInterruptionListener *listener = nil; - listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener); - [center removeObserver:listener]; - @synchronized(listener) { - listener.device = NULL; - } - } } return true; @@ -627,6 +594,17 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) return; } + #ifndef MACOSX_COREAUDIO + if (device->hidden->interruption_listener) { + SDLInterruptionListener *listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener); + device->hidden->interruption_listener = nil; + [center removeObserver:listener]; + @synchronized(listener) { + listener.device = NULL; + } + } + #endif + // dispose of the audio queue before waiting on the thread, or it might stall for a long time! if (device->hidden->audioQueue) { AudioQueueFlush(device->hidden->audioQueue); @@ -998,6 +976,32 @@ static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("%s", device->hidden->thread_error); } +#ifndef MACOSX_COREAUDIO + SDLInterruptionListener *listener = [SDLInterruptionListener new]; + listener.device = device; + + [center addObserver:listener + selector:@selector(audioSessionInterruption:) + name:AVAudioSessionInterruptionNotification + object:session]; + + /* An interruption end notification is not guaranteed to be sent if + we were previously interrupted... resuming if needed when the app + becomes active seems to be the way to go. */ + // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications. + [center addObserver:listener + selector:@selector(applicationBecameActive:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + + [center addObserver:listener + selector:@selector(applicationBecameActive:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + device->hidden->interruption_listener = CFBridgingRetain(listener); +#endif + return (device->hidden->thread != NULL); }