Handle audio interruptions on iOS/tvOS. Fixes bugs 2569 and 2960.
diff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h
index b40b5bb..b7e5b8e 100644
--- a/src/audio/coreaudio/SDL_coreaudio.h
+++ b/src/audio/coreaudio/SDL_coreaudio.h
@@ -34,6 +34,7 @@
#include <CoreServices/CoreServices.h>
#else
#import <AVFoundation/AVFoundation.h>
+#import <UIKit/UIApplication.h>
#endif
#include <AudioToolbox/AudioToolbox.h>
@@ -56,6 +57,9 @@
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 23dd7ce..1beb95f 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -273,10 +273,58 @@
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 @@
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 @@
[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 @@
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 @@
} else {
open_playback_devices--;
}
-
-#if !MACOSX_COREAUDIO
- update_audio_session();
-#endif
}
#if MACOSX_COREAUDIO
@@ -649,7 +731,7 @@
}
#if !MACOSX_COREAUDIO
- if (!update_audio_session()) {
+ if (!update_audio_session(this, SDL_TRUE)) {
return -1;
}
#endif