From f0fca2880f9e882da97546adae5084518f2ef3f4 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 18 Sep 2016 19:22:09 -0300 Subject: [PATCH] Handle audio interruptions on iOS/tvOS. Fixes bugs 2569 and 2960. --- src/audio/coreaudio/SDL_coreaudio.h | 4 ++ src/audio/coreaudio/SDL_coreaudio.m | 102 +++++++++++++++++++++++++--- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h index b40b5bb5f..b7e5b8e21 100644 --- a/src/audio/coreaudio/SDL_coreaudio.h +++ b/src/audio/coreaudio/SDL_coreaudio.h @@ -34,6 +34,7 @@ #include #else #import +#import #endif #include @@ -56,6 +57,9 @@ struct SDL_PrivateAudioData SDL_atomic_t shutdown; #if MACOSX_COREAUDIO AudioDeviceID deviceID; +#else + SDL_bool interrupted; + CFTypeRef interruption_listener; #endif }; diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index 23dd7cef5..1beb95f4c 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -273,10 +273,58 @@ static int open_playback_devices = 0; static int open_capture_devices = 0; #if !MACOSX_COREAUDIO -static BOOL update_audio_session() + +static void interruption_begin(_THIS) +{ + if (this != NULL && this->hidden->audioQueue != NULL) { + this->hidden->interrupted = SDL_TRUE; + AudioQueuePause(this->hidden->audioQueue); + } +} + +static void interruption_end(_THIS) +{ + if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL + && this->hidden->interrupted) { + this->hidden->interrupted = SDL_FALSE; + AudioQueueStart(this->hidden->audioQueue, NULL); + } +} + +@interface SDLInterruptionListener : NSObject + +@property (nonatomic, assign) SDL_AudioDevice *device; + +@end + +@implementation SDLInterruptionListener + +- (void)audioSessionInterruption:(NSNotification *)note +{ + @synchronized (self) { + NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; + if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { + interruption_begin(self.device); + } else { + interruption_end(self.device); + } + } +} + +- (void)applicationBecameActive:(NSNotification *)note +{ + @synchronized (self) { + interruption_end(self.device); + } +} + +@end + +static BOOL update_audio_session(_THIS, SDL_bool open) { @autoreleasepool { AVAudioSession *session = [AVAudioSession sharedInstance]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; NSString *category; NSError *err = nil; @@ -291,6 +339,12 @@ static BOOL update_audio_session() category = AVAudioSessionCategoryAmbient; } + if (![session setCategory:category error:&err]) { + NSString *desc = err.description; + SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); + return NO; + } + if (open_playback_devices + open_capture_devices == 1) { if (![session setActive:YES error:&err]) { NSString *desc = err.description; @@ -301,10 +355,38 @@ static BOOL update_audio_session() [session setActive:NO error:nil]; } - if (![session setCategory:category error:&err]) { - NSString *desc = err.description; - SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); - return NO; + if (open) { + SDLInterruptionListener *listener = [SDLInterruptionListener new]; + listener.device = this; + + [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. */ + [center addObserver:listener + selector:@selector(applicationBecameActive:) + name:UIApplicationDidBecomeActiveNotification + object:session]; + + [center addObserver:listener + selector:@selector(applicationBecameActive:) + name:UIApplicationWillEnterForegroundNotification + object:session]; + + this->hidden->interruption_listener = CFBridgingRetain(listener); + } else { + if (this->hidden->interruption_listener != NULL) { + SDLInterruptionListener *listener = nil; + listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener); + @synchronized (listener) { + listener.device = NULL; + } + [center removeObserver:listener]; + } } } @@ -441,6 +523,10 @@ COREAUDIO_CloseDevice(_THIS) AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); #endif +#if !MACOSX_COREAUDIO + update_audio_session(this, SDL_FALSE); +#endif + if (this->hidden->thread) { SDL_AtomicSet(&this->hidden->shutdown, 1); SDL_WaitThread(this->hidden->thread, NULL); @@ -468,10 +554,6 @@ COREAUDIO_CloseDevice(_THIS) } else { open_playback_devices--; } - -#if !MACOSX_COREAUDIO - update_audio_session(); -#endif } #if MACOSX_COREAUDIO @@ -649,7 +731,7 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) } #if !MACOSX_COREAUDIO - if (!update_audio_session()) { + if (!update_audio_session(this, SDL_TRUE)) { return -1; } #endif