26

ARCの使用

私が遭遇した問題-SKActionクラスメソッドを使用してサウンドfxを再生するSKSceneがあります

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

今、バックグラウンドに行こうとすると、サウンドが終わっていても、どうやら iOS が原因でアプリを終了しているようですgpus_ReturnNotPermittedKillClient

この行にコメントを付けてアクションを実行していない場合にのみ、iOS はバックグラウンドで正常に実行されます (もちろん、一時停止しますが、終了することはありません)。

私は何を間違っていますか?

編集:行が実行されていない場合、iOSはアプリを終了しません-たとえば、if statement実行されていない行(soundOn == YES)などにあった場合、boolがfalse

4

7 に答える 7

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

PS: この修正はKobold Kit v7.0.3 に含まれる予定です。

于 2013-10-09T22:11:03.070 に答える
14

AppDelegate applicationDidEnterBackground: で AVAudioSession を非アクティブ化することがすべてであることがわかりましたが、多くの場合、エラーで失敗します (非アクティブ化は有効ではありません):

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

それでもここで説明されているクラッシュにつながります: Spritekit crashes when enter background

したがって、setActive:NO を設定するだけでは十分ではありません。(エラーなしで) 効果的に非アクティブ化する必要があります。エラーがない限り AVAudioSession を無効にする専用のインスタンス メソッドを AppDelegate に追加することで、簡単な解決策を作成しました。

要するに、次のようになります。

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

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)

これは、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;
    }
}

ただし、この問題は、SpriteKit app での AVAudioSession の中断に比べれば簡単です。SpriteKit は頑固で、"再生できなくても再生します:)

私が見つけた限り、最も簡単な方法は setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers. 他の AVAudioSession カテゴリは、 playFileNamed: をまったく避ける必要があると思います。これは、独自の SKNode runAction: サウンド メソッドを再生するためのカテゴリ (AVAudioPlayer など) を作成することで実行できます。しかし、これは別のトピックです。

AVAudioPlayer 実装を備えた私の完全なオールインワン ソリューションはこちら: http://iknowsomething.com/ios-sdk-spritekit-sound/

編集:不足している括弧を修正しました。

于 2014-01-25T11:01:23.533 に答える