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

使用ARC

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

[SKAction playSoundFileNamed:@"sound.wav" waitForCompletion:NO]; 

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

现在,只有当我评论这行,而不是运行iOS的后台运行(当然,暂停,但没有终止)的行动。

我究竟做错了什么?

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

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

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

使用此修复程序的简化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]; } 

PS:这个修复将会包含在Kobold Kit v7.0.3中。

我发现这是关于在AppDelegate applicationDidEnterBackground:中停用AVAudioSession,但经常失败,错误(没有停用有效):

 Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn't be completed. (OSStatus error 560030580.) 

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

所以, 设置主动性是不够的:否 – 我们必须有效地停用它(没有这个错误) 。 我做了一个简单的解决scheme,在AppDelegate中添加专门的实例方法,只要没有错误,就可以closuresAVAudioSession。

总之它看起来像这样:

 - (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]; } 

NSLogcertificate:

 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) 

这真的很简短,因为它不关心stackoverflow :)如果AVAudioSession不想在几千次尝试后closures(崩溃是不可避免的,那么也是)。 所以,这只能被视为黑客直到苹果修复它。 顺便说一下,控制启动AVAudioSession也是值得的。

完整的解决scheme可以像这样:

 - (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; } } 

然而,这个问题与SpriteKit应用程序中的AVAudioSession中断相比是小菜一碟 。 如果我们不处理,迟早会出现内存泄漏和CPU 99%的严重问题(来自[SKSoundSource queuedBufferCount]的56%和[SKSoundSource isPlaying]的34%),因为SpriteKit固执而“播放“的声音,即使他们不能播放:)

据我发现,最简单的方法是设置类别:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers。 我认为,任何其他AVAudioSession类别都需要避免playFileNamed:。 这可以通过使用您自己的SKNode runAction:category来播放声音方法,例如使用AVAudioPlayer。 但这是另一个话题。

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

编辑:修复缺lessparen。

我在播放audio方面遇到了类似的问题(虽然我没有在SKAction节点中使用audio),结果也是如此。

我试图通过将我的SKScene的paused属性设置为YES来解决这个问题,但是当播放audio时,SpriteKit中似乎存在一个错误。 在这种情况下,更新方法实际上是在暂停设置为YES之后调用的。 这是我的更新代码:

 - (void)update:(CFTimeInterval)currentTime { /* Called before each frame is rendered */ if (self.paused) { // Apple bug? NSLog(@"update: called while SKView.paused == YES!"); return; } // update! [_activeLayer update]; } 

当NSLog被追查出来,应用程序将会随GL错误一起崩溃。

我发现解决这个问题的唯一方法是相当重的。 进入后台时,我必须删除和取消分配我的整个SKView。

applicationDidEnterBackground我在我的ViewController中调用了一个这样的函数:

 [self.playView removeFromSuperview]; 

确保你没有任何强大的SKView引用,因为它必须被释放才能工作。

applicationWillEnterForeground我调用一个函数来重build我的SKView,如下所示:

 CGRect rect = self.view.frame; if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) { CGFloat temp = rect.size.width; rect.size.width = rect.size.height; rect.size.height = temp; } SKView *skView = [[SKView alloc] initWithFrame:rect]; skView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; [self.view insertSubview:skView atIndex:0]; self.playView = skView; // Create and configure the scene. self.myScene = [CustomScene sceneWithSize:skView.bounds.size]; self.myScene.scaleMode = SKSceneScaleModeResizeFill; // Present the scene. [skView presentScene:self.myScene]; 

是的,这感觉就像一个彻头彻尾的黑客,但我认为在SpriteKit有一个错误。

我希望这有帮助。

编辑

上面的大接受的答案。 不幸的是,它不适合我,因为我使用的audio队列,需要音乐,当我的应用程序在后台时继续播放。

还在等待苹果的修复。

我有这个问题,并在其他答案看到有两种解决scheme:当应用程序变为不活动时,(1)停止播放audio,或(2)拆除所有SKViews。 当应用程序再次激活时,您可能需要撤消该响应。

即使当前不在屏幕上的SKViews(例如那些在导航控制器堆栈中与视图控制器相关的视图控制器)也会导致崩溃。

所以,我在这里实现了解决scheme(2): https : //github.com/jawj/GJMLessCrashySKView

这是一个简单的UIView子类,它提供了SKView的基本function,并将它们转发到它自己的SKView子视图。 最重要的是,无论什么时候它离开屏幕或者应用程序退出活动状态,它都会将这个SKViewclosures,并且在屏幕上显示并且该应用程序处于活动状态时重build该SKView。 如果在屏幕上,它用自己的静态快照取代了被拆除的SKView,并在重buildSKView时重新淡出,这样,当应用程序处于非活动状态但仍然可见时(例如显示电池警告UIAlert,或者我们已经双击了应用程序切换器的主页button)一切看起来很正常。

这个解决scheme也解决了一个(可能无关的)头痛的问题,那就是当SKViews消失并重新出现时(例如,因为模态视图控制器被呈现然后被解散),它们有时会冻结。

我有同样的问题,所以我用这个代码播放声音,而不是SKAction

 SystemSoundID soundID; NSString *filename = @"soundFileName"; CFBundleRef mainBundle = CFBundleGetMainBundle (); CFURLRef soundFileUrl = CFBundleCopyResourceURL(mainBundle, (__bridge CFStringRef)filename, CFSTR("wav"), NULL); AudioServicesCreateSystemSoundID(soundFileUrl, &soundID); AudioServicesPlaySystemSound(soundID); 

它解决了我的问题,我希望这有助于

除了设置AVAudioSession活动/不活动(如LearnCocos2D的解决scheme中提到的)之外,我还可以通过调用暂停和播放背景/前景应用程序来解决此问题。

 - (void)applicationWillResignActive:(UIApplication *)application { [self.audioPlayer pause]; [[AVAudioSession sharedInstance] setActive:NO error:nil]; } - (void)applicationDidEnterBackground:(UIApplication *)application { [self.audioPlayer pause]; [[AVAudioSession sharedInstance] setActive:NO error:nil]; } - (void)applicationWillEnterForeground:(UIApplication *)application { [[AVAudioSession sharedInstance] setActive:YES error:nil]; [self.audioPlayer play]; } 

我有完全相同的问题,我解决了不同的方式(因为其他解决scheme不起作用)。 我不知道为什么我的解决scheme工作,所以如果有人有一个可怕的解释。

基本上,我为每个需要声音的SKShapeNode创build了新的SKAction实例。 所以我创build了一个带有实例variables的单例类,用于我需要的每个声音。 我在每个SKShapeNode引用这些variables,瞧!

这里是单例代码:

CAAudio.h

 #import <Foundation/Foundation.h> #import <SpriteKit/SpriteKit.h> @interface CAAudio : NSObject @property (nonatomic) SKAction *correctSound; @property (nonatomic) SKAction *incorrectSound; + (id)sharedAudio; @end 

CAAudio.m

 + (id)sharedAudio { static CAAudio *sharedAudio = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedAudio = [self new]; sharedAudio.correctSound = [SKAction playSoundFileNamed:@"correct.wav" waitForCompletion:YES]; sharedAudio.incorrectSound = [SKAction playSoundFileNamed:@"incorrect.wav" waitForCompletion:YES]; }); return sharedAudio; } 

声音然后像这样访问:

 SKAction *sound = [[CAAudio sharedAudio] correctSound]; 

干杯!