Browse Source

Resume audio on iOS after phone call or alarm

When a phone call or an alarm triggers on iOS, the application receives
an "audio interruption" and it's up to the application to resume
playback when the interruption ends. I added handling for audio
interruptions same as if the game is focused out and then back in.
Ruslan Mustakov 7 năm trước cách đây
mục cha
commit
96301e934d

+ 12 - 1
drivers/coreaudio/audio_driver_coreaudio.cpp

@@ -217,13 +217,24 @@ void AudioDriverCoreAudio::start() {
 	if (!active) {
 		OSStatus result = AudioOutputUnitStart(audio_unit);
 		if (result != noErr) {
-			ERR_PRINT("AudioOutputUnitStart failed");
+			ERR_PRINT(("AudioOutputUnitStart failed, code: " + itos(result)).utf8().get_data());
 		} else {
 			active = true;
 		}
 	}
 };
 
+void AudioDriverCoreAudio::stop() {
+	if (active) {
+		OSStatus result = AudioOutputUnitStop(audio_unit);
+		if (result != noErr) {
+			ERR_PRINT(("AudioOutputUnitStop failed, code: " + itos(result)).utf8().get_data());
+		} else {
+			active = false;
+		}
+	}
+}
+
 int AudioDriverCoreAudio::get_mix_rate() const {
 	return mix_rate;
 };

+ 1 - 0
drivers/coreaudio/audio_driver_coreaudio.h

@@ -90,6 +90,7 @@ public:
 	virtual void finish();
 
 	bool try_lock();
+	void stop();
 
 	AudioDriverCoreAudio();
 	~AudioDriverCoreAudio();

+ 1 - 0
platform/iphone/app_delegate.h

@@ -37,6 +37,7 @@
 @interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
 	//@property (strong, nonatomic) UIWindow *window;
 	ViewController *view_controller;
+	bool is_focus_out;
 };
 
 @property(strong, nonatomic) UIWindow *window;

+ 58 - 21
platform/iphone/app_delegate.mm

@@ -140,6 +140,42 @@ void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
 	};
 }
 
+static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
+	if (!*is_focus_out) {
+		*is_focus_out = true;
+		if (OS::get_singleton()->get_main_loop())
+			OS::get_singleton()->get_main_loop()->notification(
+					MainLoop::NOTIFICATION_WM_FOCUS_OUT);
+
+		[view_controller.view stopAnimation];
+		if (OS::get_singleton()->native_video_is_playing()) {
+			OSIPhone::get_singleton()->native_video_focus_out();
+		}
+
+		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
+		if (audio)
+			audio->stop();
+	}
+}
+
+static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
+	if (*is_focus_out) {
+		*is_focus_out = false;
+		if (OS::get_singleton()->get_main_loop())
+			OS::get_singleton()->get_main_loop()->notification(
+					MainLoop::NOTIFICATION_WM_FOCUS_IN);
+
+		[view_controller.view startAnimation];
+		if (OSIPhone::get_singleton()->native_video_is_playing()) {
+			OSIPhone::get_singleton()->native_video_unpause();
+		}
+
+		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
+		if (audio)
+			audio->start();
+	}
+}
+
 - (void)controllerWasConnected:(NSNotification *)notification {
 	// create our dictionary if we don't have one yet
 	if (ios_joysticks == nil) {
@@ -569,6 +605,8 @@ static int frame_count = 0;
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 	CGRect rect = [[UIScreen mainScreen] bounds];
 
+	is_focus_out = false;
+
 	[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
 	// disable idle timer
 	// application.idleTimerDisabled = YES;
@@ -628,6 +666,12 @@ static int frame_count = 0;
 
 	[self initGameControllers];
 
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(onAudioInterruption:)
+				   name:AVAudioSessionInterruptionNotification
+				 object:[AVAudioSession sharedInstance]];
+
 	// OSIPhone::screen_width = rect.size.width - rect.origin.x;
 	// OSIPhone::screen_height = rect.size.height - rect.origin.y;
 
@@ -639,6 +683,18 @@ static int frame_count = 0;
 	return TRUE;
 };
 
+- (void)onAudioInterruption:(NSNotification *)notification {
+	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
+		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
+			NSLog(@"Audio interruption began");
+			on_focus_out(view_controller, &is_focus_out);
+		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
+			NSLog(@"Audio interruption ended");
+			on_focus_in(view_controller, &is_focus_out);
+		}
+	}
+};
+
 - (void)applicationWillTerminate:(UIApplication *)application {
 	[self deinitGameControllers];
 
@@ -656,14 +712,7 @@ static int frame_count = 0;
 - (void)applicationDidEnterBackground:(UIApplication *)application {
 	///@TODO maybe add pause motionManager? and where would we unpause it?
 
-	if (OS::get_singleton()->get_main_loop())
-		OS::get_singleton()->get_main_loop()->notification(
-				MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
-	[view_controller.view stopAnimation];
-	if (OS::get_singleton()->native_video_is_playing()) {
-		OSIPhone::get_singleton()->native_video_focus_out();
-	};
+	on_focus_out(view_controller, &is_focus_out);
 }
 
 - (void)applicationWillEnterForeground:(UIApplication *)application {
@@ -678,19 +727,7 @@ static int frame_count = 0;
 }
 
 - (void)applicationDidBecomeActive:(UIApplication *)application {
-	if (OS::get_singleton()->get_main_loop())
-		OS::get_singleton()->get_main_loop()->notification(
-				MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
-	[view_controller.view
-					startAnimation]; // FIXME: resume seems to be recommended elsewhere
-	if (OSIPhone::get_singleton()->native_video_is_playing()) {
-		OSIPhone::get_singleton()->native_video_unpause();
-	};
-
-	// Fixed audio can not resume if it is interrupted cause by an incoming phone call
-	if (AudioDriverCoreAudio::get_singleton() != NULL)
-		AudioDriverCoreAudio::get_singleton()->start();
+	on_focus_in(view_controller, &is_focus_out);
 }
 
 - (void)dealloc {