Skip to content

Commit cac05aa

Browse files
committed
Handle audio interruptions on iOS/tvOS. Fixes bugs 2569 and 2960.
1 parent 0378b12 commit cac05aa

File tree

2 files changed

+96
-10
lines changed

2 files changed

+96
-10
lines changed

src/audio/coreaudio/SDL_coreaudio.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <CoreServices/CoreServices.h>
3535
#else
3636
#import <AVFoundation/AVFoundation.h>
37+
#import <UIKit/UIApplication.h>
3738
#endif
3839

3940
#include <AudioToolbox/AudioToolbox.h>
@@ -56,6 +57,9 @@ struct SDL_PrivateAudioData
5657
SDL_atomic_t shutdown;
5758
#if MACOSX_COREAUDIO
5859
AudioDeviceID deviceID;
60+
#else
61+
SDL_bool interrupted;
62+
CFTypeRef interruption_listener;
5963
#endif
6064
};
6165

src/audio/coreaudio/SDL_coreaudio.m

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,58 @@
273273
static int open_capture_devices = 0;
274274

275275
#if !MACOSX_COREAUDIO
276-
static BOOL update_audio_session()
276+
277+
static void interruption_begin(_THIS)
278+
{
279+
if (this != NULL && this->hidden->audioQueue != NULL) {
280+
this->hidden->interrupted = SDL_TRUE;
281+
AudioQueuePause(this->hidden->audioQueue);
282+
}
283+
}
284+
285+
static void interruption_end(_THIS)
286+
{
287+
if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL
288+
&& this->hidden->interrupted) {
289+
this->hidden->interrupted = SDL_FALSE;
290+
AudioQueueStart(this->hidden->audioQueue, NULL);
291+
}
292+
}
293+
294+
@interface SDLInterruptionListener : NSObject
295+
296+
@property (nonatomic, assign) SDL_AudioDevice *device;
297+
298+
@end
299+
300+
@implementation SDLInterruptionListener
301+
302+
- (void)audioSessionInterruption:(NSNotification *)note
303+
{
304+
@synchronized (self) {
305+
NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
306+
if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
307+
interruption_begin(self.device);
308+
} else {
309+
interruption_end(self.device);
310+
}
311+
}
312+
}
313+
314+
- (void)applicationBecameActive:(NSNotification *)note
315+
{
316+
@synchronized (self) {
317+
interruption_end(self.device);
318+
}
319+
}
320+
321+
@end
322+
323+
static BOOL update_audio_session(_THIS, SDL_bool open)
277324
{
278325
@autoreleasepool {
279326
AVAudioSession *session = [AVAudioSession sharedInstance];
327+
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
280328
NSString *category;
281329
NSError *err = nil;
282330

@@ -291,6 +339,12 @@ static BOOL update_audio_session()
291339
category = AVAudioSessionCategoryAmbient;
292340
}
293341

342+
if (![session setCategory:category error:&err]) {
343+
NSString *desc = err.description;
344+
SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
345+
return NO;
346+
}
347+
294348
if (open_playback_devices + open_capture_devices == 1) {
295349
if (![session setActive:YES error:&err]) {
296350
NSString *desc = err.description;
@@ -301,10 +355,38 @@ static BOOL update_audio_session()
301355
[session setActive:NO error:nil];
302356
}
303357

304-
if (![session setCategory:category error:&err]) {
305-
NSString *desc = err.description;
306-
SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
307-
return NO;
358+
if (open) {
359+
SDLInterruptionListener *listener = [SDLInterruptionListener new];
360+
listener.device = this;
361+
362+
[center addObserver:listener
363+
selector:@selector(audioSessionInterruption:)
364+
name:AVAudioSessionInterruptionNotification
365+
object:session];
366+
367+
/* An interruption end notification is not guaranteed to be sent if
368+
we were previously interrupted... resuming if needed when the app
369+
becomes active seems to be the way to go. */
370+
[center addObserver:listener
371+
selector:@selector(applicationBecameActive:)
372+
name:UIApplicationDidBecomeActiveNotification
373+
object:session];
374+
375+
[center addObserver:listener
376+
selector:@selector(applicationBecameActive:)
377+
name:UIApplicationWillEnterForegroundNotification
378+
object:session];
379+
380+
this->hidden->interruption_listener = CFBridgingRetain(listener);
381+
} else {
382+
if (this->hidden->interruption_listener != NULL) {
383+
SDLInterruptionListener *listener = nil;
384+
listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener);
385+
@synchronized (listener) {
386+
listener.device = NULL;
387+
}
388+
[center removeObserver:listener];
389+
}
308390
}
309391
}
310392

@@ -441,6 +523,10 @@ static BOOL update_audio_session()
441523
AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
442524
#endif
443525

526+
#if !MACOSX_COREAUDIO
527+
update_audio_session(this, SDL_FALSE);
528+
#endif
529+
444530
if (this->hidden->thread) {
445531
SDL_AtomicSet(&this->hidden->shutdown, 1);
446532
SDL_WaitThread(this->hidden->thread, NULL);
@@ -468,10 +554,6 @@ static BOOL update_audio_session()
468554
} else {
469555
open_playback_devices--;
470556
}
471-
472-
#if !MACOSX_COREAUDIO
473-
update_audio_session();
474-
#endif
475557
}
476558

477559
#if MACOSX_COREAUDIO
@@ -649,7 +731,7 @@ static BOOL update_audio_session()
649731
}
650732

651733
#if !MACOSX_COREAUDIO
652-
if (!update_audio_session()) {
734+
if (!update_audio_session(this, SDL_TRUE)) {
653735
return -1;
654736
}
655737
#endif

0 commit comments

Comments
 (0)