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.
This commit is contained in:
Ryan C. Gordon
2026-04-27 10:25:25 -04:00
parent 0bf2fa8978
commit a74722ed74

View File

@@ -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);
}