129 lines
5.6 KiB
Plaintext
129 lines
5.6 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);
|
||
|
}
|
||
|
}];
|
||
|
}
|