129 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| #import <Foundation/Foundation.h>
 | |
| #import <AVFoundation/AVFoundation.h>
 | |
| 
 | |
| static void (*gSuspendCallback)(bool suspend) = nullptr;
 | |
| static bool gIsSuspended = false;
 | |
| static bool gNeedsReset = false;
 | |
| 
 | |
| extern "C" void RegisterSuspendCallback(void (*callback)(bool))
 | |
| {
 | |
|     if (gSuspendCallback || !callback)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     gSuspendCallback = callback;
 | |
| 
 | |
|     [[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionInterruptionNotification object:nil queue:nil usingBlock:^(NSNotification *notification)
 | |
|     {
 | |
|         AVAudioSessionInterruptionType type = (AVAudioSessionInterruptionType)[[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
 | |
|         if (type == AVAudioSessionInterruptionTypeBegan)
 | |
|         {
 | |
|             NSLog(@"Interruption Began");
 | |
|             // Ignore deprecated warnings regarding AVAudioSessionInterruptionReasonAppWasSuspended and
 | |
|             // AVAudioSessionInterruptionWasSuspendedKey, we protect usage for the versions where they are available
 | |
|             #pragma clang diagnostic push
 | |
|             #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 | |
| 
 | |
|             // If the audio session was deactivated while the app was in the background, the app receives the
 | |
|             // notification when relaunched. Identify this reason for interruption and ignore it.
 | |
|             if (@available(iOS 16.0, tvOS 14.5, *))
 | |
|             {
 | |
|                 // Delayed suspend-in-background notifications no longer exist, this must be a real interruption
 | |
|             }
 | |
|             #if !TARGET_OS_TV // tvOS never supported "AVAudioSessionInterruptionReasonAppWasSuspended"
 | |
|             else if (@available(iOS 14.5, *))
 | |
|             {
 | |
|                 if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionReasonKey] intValue] == AVAudioSessionInterruptionReasonAppWasSuspended)
 | |
|                 {
 | |
|                     return; // Ignore delayed suspend-in-background notification
 | |
|                 }
 | |
|             }
 | |
|             #endif
 | |
|             else
 | |
|             {
 | |
|                 if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionWasSuspendedKey] boolValue])
 | |
|                 {
 | |
|                     return; // Ignore delayed suspend-in-background notification
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             gSuspendCallback(true);
 | |
|             gIsSuspended = true;
 | |
| 
 | |
|             #pragma clang diagnostic pop
 | |
|         }
 | |
|         else if (type == AVAudioSessionInterruptionTypeEnded)
 | |
|         {
 | |
|             NSLog(@"Interruption Ended");
 | |
|             NSError *errorMessage = nullptr;
 | |
|             if (![[AVAudioSession sharedInstance] setActive:TRUE error:&errorMessage])
 | |
|             {
 | |
|                 // Interruption like Siri can prevent session activation, wait for did-become-active notification
 | |
|                 NSLog(@"AVAudioSessionInterruptionNotification: AVAudioSession.setActive() failed: %@", errorMessage);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             gSuspendCallback(false);
 | |
|             gIsSuspended = false;
 | |
|         }
 | |
|     }];
 | |
| 
 | |
|     [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *notification)
 | |
|     {
 | |
|         // Unity video playback prior to 2022.3 on tvOS breaks FMOD audio, so force a reset
 | |
|         #if TARGET_OS_TV && !UNITY_2022_3_OR_NEWER
 | |
|         gNeedsReset = true;
 | |
|         #endif
 | |
| 
 | |
|         if (gNeedsReset)
 | |
|         {
 | |
|             gSuspendCallback(true);
 | |
|             gIsSuspended = true;
 | |
|         }
 | |
|         
 | |
|         NSError *errorMessage = nullptr;
 | |
|         if (![[AVAudioSession sharedInstance] setActive:TRUE error:&errorMessage])
 | |
|         {
 | |
|             if ([errorMessage code] == AVAudioSessionErrorCodeCannotStartPlaying)
 | |
|             {
 | |
|                 // Interruption like Screen Time can prevent session activation, but will not trigger an interruption-ended notification.
 | |
|                 // There is no other callback or trigger to hook into after this point, we are not in the background and there is no other audio playing.
 | |
|                 // Our only option is to have a sleep loop until the Audio Session can be activated again.
 | |
|                 while (![[AVAudioSession sharedInstance] setActive:TRUE error:nil])
 | |
|                 {
 | |
|                     usleep(20000);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // Interruption like Siri can prevent session activation, wait for interruption-ended notification.
 | |
|                 NSLog(@"UIApplicationDidBecomeActiveNotification: AVAudioSession.setActive() failed: %@", errorMessage);
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // It's possible the system missed sending us an interruption end, so recover here
 | |
|         if (gIsSuspended)
 | |
|         {
 | |
|             gSuspendCallback(false);
 | |
|             gNeedsReset = false;
 | |
|             gIsSuspended = false;
 | |
|         }
 | |
|     }];
 | |
| 
 | |
|     [[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionMediaServicesWereResetNotification object:nil queue:nil usingBlock:^(NSNotification *notification)
 | |
|     {
 | |
|         if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground || gIsSuspended)
 | |
|         {
 | |
|             // Received the reset notification while in the background, need to reset the AudioUnit when we come back to foreground.
 | |
|             gNeedsReset = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // In the foregound but something chopped the media services, need to do a reset.
 | |
|             gSuspendCallback(true);
 | |
|             gSuspendCallback(false);
 | |
|         }
 | |
|     }];
 | |
| }
 |