sound and Nstimer stopped when iphone is in deepsleepmode? - objective-c

I am creating an application in which I'm using nstimer and avaudioplayer to play sound,but both sound and timer stops when phone is in deep sleep mode.how to solve this issue?
here is the code to play audio
-(void)PlayTickTickSound:(NSString*)SoundFileName
{
//Get the filename of the sound file:
NSString *path = [NSString stringWithFormat:#"%#%#",[[NSBundle mainBundle] resourcePath],[NSString stringWithFormat:#"/%#",SoundFileName]];// #"/Tick.mp3"];
//Get a URL for the sound file
NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
NSError *error;
if(self.TickPlayer==nil)
{
self.TickPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:filePath error:&error];
// handle errors here.
self.TickPlayer.delegate=self;
[self.TickPlayer setNumberOfLoops:-1]; // repeat forever
[self.TickPlayer play];
}
else
{
[self.TickPlayer play];
}
}

In order to prevent an app from going to sleep when the screen is locked, you must set your audio session to be of type kAudioSessionCategory_MediaPlayback.
Here's an example:
UInt32 category = kAudioSessionCategory_MediaPlayback;
OSStatus result = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
sizeof(category), &category);
if (result){
DebugLog(#"ERROR SETTING AUDIO CATEGORY!\n");
}
result = AudioSessionSetActive(true);
if (result) {
DebugLog(#"ERROR SETTING AUDIO SESSION ACTIVE!\n");
}
If you don't set the audio session category, then your app will sleep.
This will only continue to prevent the app from being put to sleep as long as you continue to play audio. If you stop playing audio and the screen is still locked, the app will go to sleep and your timers will be paused.
If you want the app to remain awake indefinitely, you'll need to play a "silent" audio file to keep it awake.
I have a code example of this here: Preventing iPhone Sleep

Related

How to trigger AVAudioPlayer playback using a remote push notification?

I'm creating an app that has a remotely-triggered alarm. Essentially, I'm trying to trigger a looping MP3 file to play (while the app is backgrounded) when a remote push notification arrives with a particular payload.
I've tried using didReceiveRemoteNotification: fetchCompletionHandler:, so that code can be run as a result of receiving a remote notification with a particular userInfo payload.
Here is my attempted didReceiveRemoteNotification: fetchCompletionHandler: from my AppDelegate.m:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSString *command = [userInfo valueForKeyPath:#"custom.a.command"];
if (command) {
UIApplicationState applicationState = [[UIApplication sharedApplication] applicationState];
if ([command isEqualToString:#"alarm"] && applicationState != UIApplicationStateActive) {
// Play alarm sound on loop until app is opened by user
NSLog(#"playing alarm.mp3");
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"alarm" ofType:#"mp3"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
NSError *error;
self.player = nil;
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:&error];
self.player.numberOfLoops = -1; // Infinitely loop while self.player is playing
self.player.delegate = self;
[self.player play];
}
}
completionHandler(UIBackgroundFetchResultNewData);
}
I expected the looping audio file to start playing as soon as the push notification arrived (with the app inactive or backgrounded), but it didn't. Instead, the audio playback surprisingly began when I then bring the app to the foreground.
What is missing from this approach, and/or can a different way work better?
You cannot start an audio session with the app in background. Audio sessions have to be initialized/started while the app is in foreground. An audio session properly initialized and running can continue if the app is pushed to background provided another app in foreground does not interrupt it.
Based on this information, I would say that your application likely has to start an audio session while you are in control and in foreground, keep the audio session alive while in background. Upon receiving the push notification, use the existing opened audio session to deliver audio out.
This has serious limitations since any other app, like Netflix, that uses a dedicated audio session may interrupt your app's audio session and prevent it from being able to play the MP3 when it arrives.
You may want to consider pre-packaging and/or downloading the MP3 ahead of time, and refer to them direcly in the Sound parameters of your push notification.
You may follow this tutorial to see how you can play custom sounds using push notifications: https://medium.com/#dmennis/the-3-ps-to-custom-alert-sounds-in-ios-push-notifications-9ea2a2956c11
func pushNotificationHandler(userInfo: Dictionary<AnyHashable,Any>) {
// Parse the aps payload
let apsPayload = userInfo["aps"] as! [String: AnyObject]
// Play custom push notification sound (if exists) by parsing out the "sound" key and playing the audio file specified
// For example, if the incoming payload is: { "sound":"tarzanwut.aiff" } the app will look for the tarzanwut.aiff file in the app bundle and play it
if let mySoundFile : String = apsPayload["sound"] as? String {
playSound(fileName: mySoundFile)
}
}
// Play the specified audio file with extension
func playSound(fileName: String) {
var sound: SystemSoundID = 0
if let soundURL = Bundle.main.url(forAuxiliaryExecutable: fileName) {
AudioServicesCreateSystemSoundID(soundURL as CFURL, &sound)
AudioServicesPlaySystemSound(sound)
}
}

How do I detect when a Quick Look panel is dismissed?

I've written a Quick Look plugin that attempts to play music like this:
OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
NSURL *fileURL = (__bridge NSURL*)url;
AudioPlayer *player = // load player with fileURL
// Create a semaphore
sema = dispatch_semaphore_create(0);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Start playback and signal the semaphore once finished
[player play:^{
dispatch_semaphore_signal(sema);
}];
// Wait here until the player completion block signals the semaphore to stop waiting
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(#"%#", #"done!");
return kQLReturnNoError;
}
For various reasons, it's not practical for me to transcode these audio files into a format that macOS knows, or else I could just hand the OS an MP3 file and get the system's plugin to play it for me. So instead I'm using a dirty hack with semaphores to halt execution to keep my player object around, or else it'd abruptly stop immediately after starting playback.
The problem with that is that the file will just continue playing after the Quick Look panel stops previewing it due to the quicklookd process still running.
Is there a way to stop playback the way the system plugins do when they're dismissed?
Have you tried to use following delegate methods:
According to Apple Documentation:
func previewControllerWillDismiss(QLPreviewController)
Called before the preview controller is closed.
func previewControllerDidDismiss(QLPreviewController)
Called after the preview controller is closed.

iOS MPMoviePlayerController does not play audio after the app was relaunched on iPad

I'm trying to use MPMoviePlayerController to create an audio player without having to implement my own scrubbing and play/pause button.
I have code that records audio into NSData and saves it to disk. The method below tests audio by playing it with the MPMoviePlayerController.
The code below works (plays audio, it is heard) if I execute the method immediately after recording is done. It also works if I press home button, then return to the app.
However, when I kill the app and restart, or hit "run" from xCode, I do not hear any audio. Here are the symptoms:
The code below lists that the NSData exists on disk and has length
The path to NSData is the same both times
NSData is kAudioFormatMPEG4AAC format
The media player displays correct duration
Media player's scrubber moves from start to finish
Speaker volume is set to maximum in both cases.
No audio is heard after the app was killed and restarted.
What could be causing my MPMoviePlayerController to not provide any audio upon app relaunch? I'm writing the audio length into the file's extended attributes, could this be messing with the "Playability" of the file?
-(void)testPlayback:(AudioNote*)note
{
NSString* path = [note filepath];
if(note == nil || path.length == 0)
{
return;
}
NSURL* url = [NSURL fileURLWithPath:path];
//file exists, and data exists on disk in both cases
NSString* exists = ([note fileExists]? #"YES":#"NO");
NSUInteger length = note.fileData.length;
DLog(#"Playing note (exists: %#, data length:%i), duration: %.2f",exists,length,note.durationSeconds);
self.moviePlayer=[[MPMoviePlayerController alloc] initWithContentURL:url];
self.moviePlayer.controlStyle=MPMovieControlStyleDefault;
[self.view addSubview:self.moviePlayer.view];
[self.moviePlayer prepareToPlay];
[self.moviePlayer play];
}

Cannot Control Volume of AVAudioPlayer via Hardware Buttons when AudioSessionActive is NO

I'm building a turn-by-turn navigation app that plays periodic, short clips of sound. Sound should play regardless of whether the screen is locked, should mix with other music playing, and should make other music duck when this audio plays.
Apple discusses the turn-by-turn use case in detail in the "WWDC 2010 session 412 Audio Development for iPhone OS part 1" video at minute 29:20. The implementation works great, but there is one problem - when the app is running, pressing the hardware volume controls adjust the ringer volume, not the app volume. If you want to change the app volume, you must press the volume buttons while a prompt is playing.
Apple is very specific in the video that you shouldn't leave the AVAudioSession active, but if the AVAudioSession is inactive, the volume buttons won't control the volume of my app.
Here is the code I'm using to set things up:
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
propertySetError = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
UInt32 allowMixing = true;
propertySetError = AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(allowMixing), &allowMixing);
UInt32 shouldDuck = true;
propertySetError = AudioSessionSetProperty(kAudioSessionProperty_OtherMixableAudioShouldDuck, sizeof(shouldDuck), &shouldDuck);
OSStatus activationResult = AudioSessionSetActive(true);
NSError* err = nil;
_player = [[AVAudioPlayer alloc] initWithData:audioData error:&err];
_player.delegate = self;
[_player play];
And I set the session active to NO at the end, as Apple recommends:
OSStatus activationResult = AudioSessionSetActive(false);
NSAssert(activationResult == kAudioSessionNoError, #"Error deactivating audio session");
Is there something I'm missing, or do I have to go against what they recommended in the video?
In your case, you don't want to set the audio session inactive. What you need to do is use two methods, one to set the session up for playing a sound, and the other to set it up for being idle. The first method sets up a mix+duck audio mode, and the second uses a background-audio-friendly mode like ambient.
Something like this:
- (void)setActive {
UInt32 mix = 1;
UInt32 duck = 1;
NSError* errRet;
AVAudioSession* session = [AVAudioSession sharedInstance];
[session setActive:NO error:&errRet];
[session setCategory:AVAudioSessionCategoryPlayback error:&errRet];
NSAssert(errRet == nil, #"setCategory!");
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(mix), &mix);
AudioSessionSetProperty(kAudioSessionProperty_OtherMixableAudioShouldDuck, sizeof(duck), &duck);
[session setActive:YES error:&errRet];
}
- (void)setIdle {
NSError* errRet;
AVAudioSession* session = [AVAudioSession sharedInstance];
[session setActive:NO error:&errRet];
[session setCategory:AVAudioSessionCategoryAmbient error:&errRet];
NSAssert(errRet == nil, #"setCategory!");
[session setActive:YES error:&errRet];
}
Then to call it:
[self setActive];
[self _playAudio:nil];
To clean up after playing:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player
successfully:(BOOL)flag {
[self setIdle];
}
To be a good citizen, your app should set the audio session inactive when the it isn't navigating (i.e., performing its main function), but when it is, there is absolutely nothing wrong with keeping the audio session active and using modes to peacefully coexist with other apps. You can duplicate Apple's navigation app functionality using the code above.

Can not restart an interrupted audio input queue in background mode on iOS

I'm writing an iOS App using an AudioQueue for recording. I create an input queue configured to get linear PCM, stated this queue and everything works as expected.
To manage interruptions, I implemented the delegate methods of AVAudioSession to catch the begin and the end of an interruption. The method endInterruption looks like the following:
- (void)endInterruptionWithFlags:(NSUInteger)flags;
{
if (flags == AVAudioSessionInterruptionFlags_ShouldResume && audioQueue != 0) {
NSLog(#"Current audio session - category: '%#' mode: '%#'",
[[AVAudioSession sharedInstance] category],
[[AVAudioSession sharedInstance] mode]);
NSError *error = nil;
OSStatus errorStatus;
if ((errorStatus = AudioSessionSetActive(true)) != noErr) {
error = [self errorForAudioSessionServiceWithOSStatus:errorStatus];
NSLog(#"Could not reactivate the audio session: %#",
[error localizedDescription]);
} else {
if ((errorStatus = AudioQueueStart(audioQueue, NULL)) != noErr) {
error = [self errorForAudioQueueServiceWithOSStatus:errorStatus];
NSLog(#"Could not restart the audio queue: %#",
[error localizedDescription]);
}
}
}
// ...
}
If the app gets interrupted while it is in foreground, everything works correct. The problem appears, if the interruption happens in the background. Activating the audio session result in the error !cat:
The specified audio session category cannot be used for the attempted audio operation. For example, you attempted to play or record audio with the audio session category set to kAudioSessionCategory_AudioProcessing.
Starting the queue without activating the session results in the error code: -12985
At that point the category is set to AVAudioSessionCategoryPlayAndRecord and the mode is AVAudioSessionModeDefault.
I couldn't find any documentation for this error message, nor if it is possible to restart an input audio queue in the background.
Yes it is possible, but to reactivate the session in the background, the audio session has to either set AudioSessionProperty kAudioSessionProperty_OverrideCategoryMixWithOthers
OSStatus propertySetError = 0;
UInt32 allowMixing = true;
propertySetError = AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryMixWithOthers,
sizeof (allowMixing),
&allowMixing
);
or the app has to receive remote control command events:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
At the present there is no way to reactivate if you are in the background.
Have you made your app support backgrounding in the info.plist? I'm not sure if recording is possible in the background, but you probably need to add "Required Background Modes" and then a value in that array of "App plays audio"
Update I just checked and recording in the background is possible.