web-dev-qa-db-ja.com

スプライトキットとサウンドの再生はアプリの終了につながります

aRCを使用する

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

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

これで、バックグラウンドに移動しようとすると、サウンドが終了したにもかかわらず、iOSがgpus_ReturnNotPermittedKillClientのためにアプリを終了しているようです。

今、私がこの行にコメントし、アクションを実行していない場合にのみ、iOSはバックグラウンドでそれをうまく実行します(もちろん、一時停止しますが、終了しません)。

私は何が間違っているのですか?

[〜#〜] edit [〜#〜]:行が実行されなかった場合、iOSはアプリを終了しません-たとえば、実行されなかったif statementにあった場合ブール値がfalseの場合、(soundOn == YES)またはそのようなもの

25
Lior Pollak

問題は、アプリがバックグラウンドに入っている間は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に含まれます。

28
LearnCocos2D

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

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

それでもここで説明するクラッシュにつながります: バックグラウンドに入るとSpritekitがクラッシュします

したがって、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)

スタックオーバーフローを気にしないので、これは本当に短いです:) 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アプリでのAVAudioSessionの中断と比較すると簡単です。それを処理しないと、遅かれ早かれ、SpriteKitが頑固で、「再生できなくても「再生」音が鳴ります:)

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

AVAudioPlayerの実装による私の完全なオールインワンソリューションはここにあります:---(http://iknowsomething.com/ios-sdk-spritekit-sound/

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

14

私はこの問題を抱えています。他の回答に見られるように、2つの解決策があるようです。アプリが非アクティブになったとき、(1)オーディオの再生を停止するか、(2)すべてのSKViewを破棄します。その後、アプリが再びアクティブになったときに、その応答を元に戻す必要がある場合があります。

現在画面に表示されていないSKView(たとえば、ナビゲーションコントローラースタックの初期のView Controllerに関連付けられているSKView)でも、クラッシュが発生します。

だから、私はここにソリューション(2)を実装しました: https://github.com/jawj/GJMLessCrashySKView

これは、SKViewの基本機能を提供し、それらを独自のSKViewサブビューに転送する単純なUIViewサブクラスです。ただし、重要なのは、このSKViewが画面から消えたり、アプリがアクティブを終了したりするたびに破棄し、画面に戻ってアプリがアクティブになったときに再構築することです。画面に表示されている場合は、破棄されたSKViewをそれ自体の静止スナップショットに置き換え、SKViewが再構築されると再びフェードアウトするため、アプリが非アクティブであるがまだ表示されている場合(たとえば、バッテリー警告UIAlertが表示されるか、アプリスイッチャーのホームボタンをダブルクリックしました)すべてが正常に見えます。

このソリューションは、(おそらく無関係の)ボーナスの頭痛の種も修正します。これは、SKViewが消えて再び表示されると(たとえば、モーダルビューコントローラーが表示されてから閉じられるため)、フリーズすることがあります。

1
jawj

オーディオの再生で同様の問題が発生し(SKActionノードでオーディオを使用していませんが)、結果として同じバックグラウンドクラッシュが発生しました。

SKSceneのpausedプロパティをYESに設定してこれを解決しようとしましたが、オーディオの再生中にSpriteKitにバグがあるようです。この状況では、pausedがYESに設定された後、updateメソッドが実際に呼び出されます。これが私の更新コードです:

- (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の割り当てを解除する必要があるため、SKViewへの強い参照がないことを確認してください。

applicationWillEnterForegroundで、次のように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にはバグがあると思います。

これがお役に立てば幸いです。

[〜#〜]編集[〜#〜]

上記の大いに受け入れられた答え。残念ながら、オーディオキューを使用していて、アプリがバックグラウンドで再生されているときに音楽を再生し続ける必要があるため、機能しません。

まだAppleからの修正を待っています。

1
Keith Murray

私はまったく同じ問題を抱えていましたが、別の方法で解決しました(他の解決策が機能しなかったため)。なぜ私のソリューションが機能したのかわかりません。誰かが素晴らしい説明をしてくれれば。

基本的に、サウンドが必要なSKActionごとに新しいSKShapeNodeインスタンスを作成していました。そこで、必要なサウンドごとにインスタンス変数を使用してシングルトンクラスを作成しました。それぞれのSKShapeNodeと出来上がりでそれらの変数を参照します!

シングルトンコードは次のとおりです。

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

乾杯!

0
cohenadair

AVAudioSessionをアクティブ/非アクティブに設定することに加えて、アプリケーションをバックグラウンド/フォアグラウンドするときにpause and playを呼び出すことで、これを解決することができました(LearnCocos2Dのソリューションで説明されています)。

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

私は同じ問題を抱えていたので、SKActionの代わりにこのコードを使用してサウンドを再生しました

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

そしてそれは私の問題を解決しました、私はこれが役立つことを願っています

0
m.eldehairy