ios - para - El kit de Sprite y la reproducción del sonido provocan la finalización de la aplicación
buscar mi iphone donde esta (7)
usando ARC
Es solo un problema con el que me he encontrado. Tengo un SKScene en el que reproduzco un sonido fx usando el método de clase SKAction
[SKAction playSoundFileNamed:@"sound.wav" waitForCompletion:NO];
Ahora, cuando intento pasar al segundo plano, no importa que el sonido haya terminado, aparentemente iOS está gpus_ReturnNotPermittedKillClient
mi aplicación debido a gpus_ReturnNotPermittedKillClient
.
Ahora, solo cuando comento esta línea y no ejecuto la acción, iOS la ejecuta muy bien en segundo plano (por supuesto, en pausa, pero sin terminación).
¿Qué estoy haciendo mal?
EDITAR : iOS no terminará la aplicación si la línea no se ejecutó, por ejemplo, si estaba en una if statement
que no se ejecutó (soundOn == YES)
o algo así, cuando el bool es false
Descubrí que todo se trata de desactivar AVAudioSession en la aplicación AppDelegateDidEnterBackground :, pero a menudo falla con el error (no hay desactivación en vigor):
Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)
que todavía conduce al bloqueo descrito aquí: Spritekit se bloquea al ingresar el fondo .
Por lo tanto, no es suficiente establecer Active: NO: tenemos que desactivarlo de manera efectiva (sin ese error) . Hice una solución simple al agregar un método de instancia dedicado a AppDelegate que desactiva AVAudioSession mientras no haya ningún error.
En resumen, se ve así:
- (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];
}
Prueba 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)
Esto es realmente corto, porque no le importa :) si AVAudioSession no quiere cerrar después de varios miles de intentos (el bloqueo es inevitable, entonces también). Por lo tanto, esto solo se puede considerar como un hack hasta que Apple lo solucione. Por cierto, también vale la pena tomar el control de iniciar AVAudioSession.
La solución completa puede verse así:
- (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;
}
}
Este problema, sin embargo, es un pedazo de pastel en comparación con las interrupciones de AVAudioSession en la aplicación SpriteKit . Si no lo manejamos, tarde o temprano nos enfrentaremos a grandes problemas con pérdidas de memoria y CPU del 99% (56% de [SKSoundSource queuedBufferCount] y 34% de [SKSoundSource isPlaying] - vea Instrumentos), porque SpriteKit es obstinado y " reproduce "suena incluso no se pueden reproducir :)
Por lo que he encontrado, la manera más fácil es establecerCategory: AVAudioSessionCategoryPlayAndRecord withOptions: AVAudioSessionCategoryOptionMixWithOthers. Cualquier otra categoría de AVAudioSession necesita, creo, evitar playFileNamed: en absoluto. Esto se puede hacer creando su propio SKNode runAction: categoría para reproducir el método de sonidos, por ejemplo, con AVAudioPlayer. Pero este es un tema separado.
Mi solución todo en uno completa con la implementación de AVAudioPlayer está aquí: http://iknowsomething.com/ios-sdk-spritekit-sound/
Editar: se corrigió la falta de paren.
El problema es que AVAudioSession
no puede estar activo mientras la aplicación ingresa en segundo plano. Esto no es inmediatamente obvio porque Sprite Kit no menciona que usa AVAudioSession internamente.
La solución es bastante simple, y también se aplica a ObjectAL => configurar la AVAudioSession
a inactivo mientras la aplicación está en segundo plano, y reactivar la sesión de audio cuando la aplicación entra en primer plano.
Un AppDelegate simplificado con esta corrección se ve así:
#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];
}
PD: esta solución se incluirá en Kobold Kit v7.0.3.
Estaba teniendo exactamente el mismo problema y lo resolví de otra manera (ya que las otras soluciones no funcionaron). No tengo idea de por qué mi solución funcionó, así que si alguien tiene una explicación sería increíble.
Básicamente, estaba creando nuevas instancias de SKAction
para cada SKShapeNode
que requería sonidos. Así que creé una clase singleton con variables de instancia para cada sonido que necesitaba. Hago referencia a esas variables en cada SKShapeNode
y voila!
Aquí está el código singleton:
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;
}
Los sonidos se acceden así:
SKAction *sound = [[CAAudio sharedAudio] correctSound];
¡Aclamaciones!
He tenido este problema, y como se ve en las otras respuestas, parece que hay dos soluciones: cuando la aplicación se vuelve inactiva, (1) deja de reproducir el audio o (2) derriba todas tus SKViews. Es posible que deba deshacer esa respuesta cuando la aplicación se vuelva a activar.
Incluso SKViews que no están actualmente en la pantalla (por ejemplo, los que están asociados con los controladores de vista anteriormente en una pila de controlador de navegación) provocarán un bloqueo.
Entonces, implementé la solución (2) aquí: https://github.com/jawj/GJMLessCrashySKView
Es una subclase simple de UIView que ofrece las funciones básicas de un SKView y las reenvía a su propia subvista SKView. Crucialmente, sin embargo, destruye este SKView cada vez que se apaga la pantalla o la aplicación deja de estar activa, y la reconstruye cuando vuelve a aparecer en la pantalla y la aplicación está activa. Si está en pantalla, reemplaza el SKView derribado con una instantánea fija de sí mismo, y se desvanece cuando se reconstruye el SKView, de modo que cuando la aplicación está inactiva pero aún visible (por ejemplo, se muestra una advertencia de batería UIAlert, o ha hecho doble clic en el botón de inicio para el selector de aplicaciones) todo parece normal.
Esta solución también corrige un dolor de cabeza adicional (posiblemente no relacionado), que es que cuando SKViews desaparecen y vuelven a aparecer (por ejemplo, porque un controlador de vista modal se presenta y luego se descarta), a veces se congelan.
He tenido un problema similar con la reproducción de audio (aunque no estoy usando audio en un nodo SKAction) con el mismo bloqueo de fondo como resultado.
Traté de resolver esto estableciendo la propiedad paused
de mi SKScene en YES
, pero cuando se reproduce audio parece haber un error en SpriteKit. En esta situación, el método de actualización se llama realmente después de que paused se establece en YES. Aquí está mi código de actualización:
- (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];
}
Cuando se trace ese NSLog, la aplicación se bloqueará con el error GL.
La única forma que he encontrado para resolverlo es bastante dura. Tengo que eliminar y desasignar mi SKView completo al ingresar el fondo.
En applicationDidEnterBackground
invoco una función en mi ViewController que hace esto:
[self.playView removeFromSuperview];
Asegúrese de que no tiene ninguna referencia sólida al SKView, ya que debe desasignarse para que funcione.
En applicationWillEnterForeground
invoco una función que reconstruye mi SKView de la siguiente manera:
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];
Sí, esto parece un truco total, pero creo que hay un error en SpriteKit.
Espero que esto ayude.
EDITAR
Gran respuesta aceptada arriba. Lamentablemente, no funciona para mí porque estoy usando Colas de audio y necesito música para seguir jugando cuando mi aplicación está en segundo plano.
Todavía estoy esperando una solución de Apple.
Pude resolver esto llamando a pause and play cuando se utilizaba la función background / foregrounding además de configurar la sesión de AVAudioSession activa / inactiva (como se menciona en la solución de 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];
}
tuve el mismo problema, así que usé este código para reproducir sonidos en lugar de SKAction
SystemSoundID soundID;
NSString *filename = @"soundFileName";
CFBundleRef mainBundle = CFBundleGetMainBundle ();
CFURLRef soundFileUrl = CFBundleCopyResourceURL(mainBundle, (__bridge CFStringRef)filename, CFSTR("wav"), NULL);
AudioServicesCreateSystemSoundID(soundFileUrl, &soundID);
AudioServicesPlaySystemSound(soundID);
y resolvió mis problemas, espero que esto ayude