Sprite Kit和播放声音会导致应用程序终止

Lio*_*lak 25 audio opengl-es multitasking ios sprite-kit

使用ARC

只是我遇到的一个问题 - 我有一个SKScene,我在其中使用SKAction类方法播放声音fx

[SKAction playSoundFileNamed:@"sound.wav" waitForCompletion:NO];
Run Code Online (Sandbox Code Playgroud)

现在,当我尝试去背景时,无论声音结束,显然iOS正在终止我的应用程序gpus_ReturnNotPermittedKillClient.

现在只有当我评论这一行并且没有运行动作时,iOS才能在后台运行它(当然,暂停,但没有终止).

我究竟做错了什么?

编辑:如果没有运行,iOS将不会终止应用程序 - 例如,如果它if statement没有运行(soundOn == YES)或类似的东西,当布尔是false

Lea*_*s2D 28

AVAudioSession当应用程序进入后台时,问题无法激活.这并不是很明显,因为Sprite Kit没有提到它在内部使用AVAudioSession.

修复非常简单,也适用于ObjectAL => AVAudioSession在应用程序处于后台时将其设置为非活动状态,并在应用程序进入前台时重新激活音频会话.

使用此修复程序的简化AppDelegate如下所示:

#import <AVFoundation/AVFoundation.h>
...

- (void)applicationWillResignActive:(UIApplication *)application
{
    // prevent audio crash
    [[AVAudioSession sharedInstance] setActive:NO error:nil];
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // prevent audio crash
    [[AVAudioSession sharedInstance] setActive:NO error:nil];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // resume audio
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
}
Run Code Online (Sandbox Code Playgroud)

PS:此修复程序将包含在Kobold Kit v7.0.3中.

  • 此解决方案似乎不再适用于iOS7.1.在iOS 7中运行良好,但我的应用程序现在再次崩溃http://stackoverflow.com/questions/22407111/ios-7-1-sprite-kit-avaudiosession-crash-when-enter-background?noredirect=1#comment34073222_22407111 (4认同)

Krz*_*oda 14

我发现它是关于在AppDelegate applicationDidEnterBackground中停用AVAudioSession的,但通常它会失败并出现错误(实际上没有停用):

Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)
Run Code Online (Sandbox Code Playgroud)

这仍然导致这里描述的崩溃:进入后台时Spritekit崩溃.

所以,setActive还不够:不 - 我们必须有效地停用它(没有那个错误).我通过向AppDelegate添加专用实例方法做了一个简单的解决方案,只要没有错误就停用AVAudioSession.

简而言之它看起来像这样:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"%s", __FUNCTION__);
    [self stopAudio];
}

- (void)stopAudio {
    NSError *error = nil;
    [[AVAudioSession sharedInstance] setActive:NO error:&error];
    NSLog(@"%s AudioSession Error: %@", __FUNCTION__, error);
    if (error) [self stopAudio];
}
Run Code Online (Sandbox Code Playgroud)

NSLog证明:

2014-01-25 11:41:48.426 MyApp[1957:60b] -[ATWAppDelegate applicationDidEnterBackground:]
2014-01-25 11:41:48.431 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
2014-01-25 11:41:48.434 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
2014-01-25 11:41:48.454 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
2014-01-25 11:41:49.751 MyApp[1957:60b] -[ATWAppDelegate stopAudio] AudioSession Error: (null)
Run Code Online (Sandbox Code Playgroud)

这真的很短,因为它不关心stackoverflow :)如果AVAudioSession不想在几千次尝试后关闭(崩溃也是不可避免的).因此,在Apple修复它之前,这只能被视为黑客攻击.顺便说一句,控制启动AVAudioSession也是值得的.

完整解决方案可能如下所示:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"%s", __FUNCTION__);

    [self startAudio];
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"%s", __FUNCTION__);

    // SpriteKit uses AVAudioSession for [SKAction playSoundFileNamed:]
    // AVAudioSession cannot be active while the application is in the background,
    // so we have to stop it when going in to background
    // and reactivate it when entering foreground.
    // This prevents audio crash.
    [self stopAudio];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    NSLog(@"%s", __FUNCTION__);

    [self startAudio];
}

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"%s", __FUNCTION__);

    [self stopAudio];
}

static BOOL isAudioSessionActive = NO;

- (void)startAudio {
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *error = nil;

    if (audioSession.otherAudioPlaying) {
        [audioSession setCategory: AVAudioSessionCategoryAmbient error:&error];
    } else {
        [audioSession setCategory: AVAudioSessionCategorySoloAmbient error:&error];
    }

    if (!error) {
        [audioSession setActive:YES error:&error];
        isAudioSessionActive = YES;
    }

    NSLog(@"%s AVAudioSession Category: %@ Error: %@", __FUNCTION__, [audioSession category], error);
}

- (void)stopAudio {
    // Prevent background apps from duplicate entering if terminating an app.
    if (!isAudioSessionActive) return;

    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *error = nil;

    [audioSession setActive:NO error:&error];

    NSLog(@"%s AVAudioSession Error: %@", __FUNCTION__, error);

    if (error) {
        // It's not enough to setActive:NO
        // We have to deactivate it effectively (without that error),
        // so try again (and again... until success).
        [self stopAudio];
    } else {
        isAudioSessionActive = NO;
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,与SpriteKit应用程序中的AVAudioSession中断相比,这个问题是小菜一碟.如果我们不处理它,迟早会遇到内存泄漏和CPU 99%的大问题(来自[SKSoundSource queuedBufferCount的56%]和来自[SKSoundSource isPlaying]的34% - 见工具),因为SpriteKit很顽固,"播放"声音,即使他们无法播放:)

据我所知,最简单的方法是setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers.我认为,任何其他AVAudioSession类别都需要避免使用playFileNamed:这可以通过制作自己的SKNode runAction:类别来播放声音方法来实现,例如使用AVAudioPlayer.但这是一个单独的主题.

我的完整的一体化解决方案与AVAudioPlayer实现在这里:http://iknowsomething.com/ios-sdk-spritekit-sound/

编辑:修复失踪的paren.