Handle audio interruptions on iOS/tvOS. Fixes bugs 2569 and 2960.
parent
8f8f225b3f
commit
f0fca2880f
|
@ -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 @@ struct SDL_PrivateAudioData
|
|||
SDL_atomic_t shutdown;
|
||||
#if MACOSX_COREAUDIO
|
||||
AudioDeviceID deviceID;
|
||||
#else
|
||||
SDL_bool interrupted;
|
||||
CFTypeRef interruption_listener;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue