Reanudar la ejecución del código después de la interrupción mientras la aplicación está en segundo plano

Pasé días investigando en SO y otros sitios web para get la respuesta pero sin suerte.

Básicamente, el desafío que me he propuesto es crear una aplicación de despertador para iOS que suene sin importar dónde esté el usuario (primer plano o background). Esto ya lo he logrado utilizando una instancia de AVAudioPlayer y comenzando a reproducir un file de sonido vacío cuando el usuario establece la alarma para que la aplicación siga ejecutándose en segundo plano. Cuando llega el momento de que la alarma se active (es decir, cuando se dispara el NSTimer), un segundo reproductor, que ya se ha iniciado y preparado para jugar, comienza a reproducir el tono de llamada al que se despierta el usuario.

Además, he logrado manejar las interrupciones mediante una llamada telefónica, el timer del sistema o la alarma mediante la implementación de los methods AVAudioSessionDelegate beginInterruption y endInterruptionWithFlags. Funciona tanto en segundo plano como en primer plano, pero sucede lo más extraño:

Cuando finaliza la interrupción, AVAudioPlayer reanuda la reproducción PERO no puedo ejecutar ningún otro código en mi aplicación a less que vuelva a poner la aplicación en primer plano.

Para llegar al background de esto, he experimentado con un proyecto mucho más simple que estoy publicando a continuación. Lo que hace esta aplicación es que, tan pronto como ingresas a la aplicación, una instancia de la class AVAudioPlayer comienza a hacer un cierto sonido. Luego, cuando lo trae a un segundo plano, el jugador continúa haciendo un bucle con el sonido. Cuando ocurre una interrupción detengo el reproductor y cuando termina utilizo un despacho para esperar un par de segundos antes de llamar a dos methods, es decir (vacío) playPlayer, un método que contiene el código para reanudar la reproducción del file y el probador (vacío) , un método que contiene un timer, que está configurado para detener al jugador 5 segundos después de la interrupción (o 7 segundos para ser exactos) ha terminado. Ambos methods se llaman como lo indican los NSLogs que he puesto en ambos, pero el timer nunca se dispara y el jugador continúa jugando indefinidamente.

Aquí está el código para el file .h:

#import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> #import <AudioToolbox/AudioToolbox.h> @interface InterruptionTest3ViewController : UIViewController <AVAudioSessionDelegate, AVAudioPlayerDelegate> { AVAudioSession *mySession; AVAudioPlayer *myPlayer; } -(void) playPlayer; -(void) pausePlayer; -(void) tester; @end 

Aquí está el código para el file .m:

 #import "InterruptionTest3ViewController.h" @interface InterruptionTest3ViewController () @end @implementation InterruptionTest3ViewController - (void)viewDidLoad { [super viewDidLoad]; mySession = [AVAudioSession shanetworkingInstance]; NSError *setActiveError = nil; [mySession setActive:YES withFlags:AVAudioSessionSetActiveFlags_NotifyOthersOnDeactivation error:&setActiveError]; if (setActiveError) { NSLog(@"Session failed to activate within viewDidLoad"); } else { NSLog(@"Session was activated within viewDidLoad"); } NSError *setCategoryError = nil; [mySession setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError]; if (setCategoryError) { NSLog(@"Category failed to be set"); } else { NSLog(@"Category has been set"); } [mySession setDelegate:self]; NSString *path = [[NSBundle mainBundle] pathForResource:@"headspin" ofType:@"wav"]; NSError *initMyPlayerError = nil; myPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&initMyPlayerError]; if (initMyPlayerError) { NSLog(@"myPlayer failed to initiate"); } else { NSLog(@"myPlayer has been initiated"); } [myPlayer prepareToPlay]; [self playPlayer]; OSStatus propertySetError = 0; UInt32 allowMixing = true; propertySetError = AudioSessionSetProperty ( kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (allowMixing), &allowMixing ); [myPlayer setNumberOfLoops:-1]; [[UIApplication shanetworkingApplication] beginReceivingRemoteControlEvents]; } -(void) beginInterruption { [myPlayer pause]; } -(void) endInterruptionWithFlags:(NSUInteger)flags { if (flags) { if (AVAudioSessionInterruptionFlags_ShouldResume) { { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),dispatch_get_main_queue(), ^{ [self playPlayer]; [self tester]; }); } } } } -(void) tester { [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(pausePlayer) userInfo:nil repeats:NO]; NSLog(@"tester method has been called"); } -(void) playPlayer { [NSTimer timerWithTimeInterval:5.0 target:myPlayer selector:@selector(stop) userInfo:nil repeats:NO]; [myPlayer play]; NSLog(@"playPlayer method has been called"); } -(void) pausePlayer { [myPlayer pause]; } //viewDidUnload etc not listed. 

Entonces, esta es la gente. Una vez más, ¿por qué no se dispara el timer después de una interrupción mientras la aplicación está en segundo plano? ¿Debo configurar algo en el método applicationDidEnterBackground?

¡Muchas gracias por adelantado!

utilice "[NSTimer scheduledTimerWithTimeInterval: 5.0 target: myPlayer selector: @selector (detener) userInfo: nil repeats: NO]";

No para evitar la pregunta, pero el bit play-an-empty-sound-in-the-background es un truco. La capacidad de reproducir un sonido en segundo plano se proporciona para que pueda reproducir música u otro sonido para el beneficio del usuario, no para mantener vivo el process de background.

Considere usar notifications locales en su lugar si desea reproducir un sonido o alertar de otra manera al usuario en un momento determinado.

¿Por qué no se dispara el timer después de una interrupción mientras la aplicación está en segundo plano?

Si no recuerdo mal, los timeres se suspenderán o cancelarán cuando su aplicación entre en segundo plano. La documentation dice explícitamente que debe "detener los timeres y otras tareas periódicas" cuando su aplicación se interrumpe, es decir, cuando se envía al background.