I've searched all over but cannot get this to work consistently. I want to play audio when a remote push notification arrives while the app is in the background or lock screen and the ringer is off.
Steps I've followed:
1) Set Required Background Modes to "App plays audio" into info.plist.
2) In application:didFinishLaunchingWithOptions:
a) set Audio Session category to Playback
b) make Audio Session active.
c) make app receive remote control events and become first responder
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// https://developer.apple.com/library/ios/qa/qa1668/_index.html
// For playback to continue when the screen locks, or when the Ring/Silent switch is set to silent, use the AVAudioSessionCategoryPlayback
BOOL result = [audioSession setCategory:AVAudioSessionCategoryPlayback error:&sessionError]; // TODO AVAudioSessionCategorySoloAmbient
if (!result && sessionError) {
NSLog(#"AppDelegate error setting session category. error %#", sessionError);
}
else {
NSLog(#"AppDelegate setting session category is successful");
}
result = [audioSession setActive:YES error:&sessionError];
if (!result && sessionError) {
NSLog(#"AppDelegate error activating audio session. error %#", sessionError);
}
else {
NSLog(#"AppDelegate setting session active is successful");
}
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
3) In applicationDidEnterBackground:
a) begin background task
b) receive remote control events
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"AppDelegate applicationDidEnterBackground: called");
NSLog(#"AppDelegate applicationDidEnterBackground: is calling beginBackgroundTaskWithExpirationHandler:");
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
NSLog(#"AppDelegate applicationDidEnterBackground: is calling beginReceivingRemoteControlEvents");
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
4) When the remote notification comes in, play an audio file
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
_currentItem = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"Audio" withExtension:#"wav"]];
_audioPlayer = [AVPlayer playerWithPlayerItem:_currentItem];
[_currentItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
[_audioPlayer addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
NSLog(#"playAudio: calling [_audioPlayer play] on audioPlayer %#", _audioPlayer);
[_audioPlayer play];
}
It works sometimes, but not always. Any ideas how to make this work consistently?
I think that you can't start playing background audio while already in the background, even starting in applicationDidEnterBackground is too late. A work around you could do is play silent audio or pause audio maybe. You could use the queue player to achieve this by looping the silent audio and seeking to the beginning when it ends, then enqueue the audio you'd like to play after it.
Related
I have problem with media play in background mode.
In my app I have to play media play in background mode when server send any notification through socket connection.
In my case media player working fine when app in background mode.
Problem is when app in background mode if I play music app and stop the music player and send notification to my app .Than my app didn't play media player.
I added in plist this one "App plays audio or streams audio/video using AirPlay"
I am using "AVPlayer"
Please help me.
Thanks.
If you look into the AVPlayer documentation it says that it can only play one asset at a time. I think Apple Music uses if not the same API something that is related to this.
AVPlayer is intended for playing a single media asset at a time. The
player instance can be reused to play additional media assets using
its replaceCurrentItem(with:) method, but it manages the playback of
only a single media asset at a time
1). Put this code in you AppDelegate.m (didFinishLaunchingWithOptions) methods :
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
2). After that set Background Modes active for Audio, Airplay and Picture in Picture
Might be, its due to Interruption with current AVplayer running item.
Also, take care you implemented it(Below code)before loading any item to AVPlayer. AND enabled Capabilities to Play audio in background.
NSError *myErr;
[self becomeFirstResponder];
[[UIApplication sharedApplication]beginReceivingRemoteControlEvents];
AVAudioSession *aSession = [AVAudioSession sharedInstance];
[aSession setCategory:AVAudioSessionCategoryPlayback error:&myErr];
[aSession setMode:AVAudioSessionModeDefault error:&myErr];
[aSession setActive: YES error: &myErr];
Implement and Add Interruption handler, so you can re-play your stopped audio after another audio/interruption stopped. and below listener of it will call.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleAudioSessionInterruption:)
name:AVAudioSessionInterruptionNotification
object:aSession];
And manage AVplayer play/pause again in it:
- (void)handleAudioSessionInterruption:(NSNotification*)notification {
NSNumber *interruptionType = [[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey];
NSNumber *interruptionOption = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey];
switch (interruptionType.unsignedIntegerValue) {
case AVAudioSessionInterruptionTypeBegan:{
// • Audio has stopped, already inactive
// • Change state of UI, etc., to reflect non-playing state
IsInteruptionOccured=YES;
} break;
case AVAudioSessionInterruptionTypeEnded:{
// • Make session active
// • Update user interface
// • AVAudioSessionInterruptionOptionShouldResume option
IsInteruptionOccured=NO;
[[AVAudioSession sharedInstance] setActive: YES error: nil];
if (interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume) {
// Here you should continue playback.
// Resume after exteranl interruption.
[_audioPlayer play];
BOOL isPlayingWithOthers = [[AVAudioSession sharedInstance] isOtherAudioPlaying];
// test it with...
NSLog(#"other audio is playing %d",isPlayingWithOthers);
}
} break;
default:
break;
}
}
I have set a custom .aif 30 second file as the local notification sound name. And below is my code for scheduling the local notification.
//Function to schedule local notification
-(void)schedulelocalnotification:(NSDate *)particularfiredate ringtone: (NSString *)particularringtone name:(NSString *)alarmname info:(NSDictionary *)dicttext
{
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.fireDate = particularfiredate;
notification.soundName = [arrayAIFFFiles objectAtIndex:[arraysoundfilesnames indexOfObject:particularringtone]];
notification.alertBody = alarmname;
notification.userInfo = dicttext;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
But when the device is locked, and the user slides on the notification to enter the app, the sound keeps on playing even when the user enters the app. It continues to play even when the user quits/uninstalls the app.
Please suggest what could be the possible reasons.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[[NSUserDefaults standardUserDefaults] setValue:#"0" forKey:#"demo"];
NSLog(#"%i",[[[UIApplication sharedApplication] scheduledLocalNotifications] count]);
NSString *viewcontrollerstring = [notification.userInfo objectForKey:#"smiletosnooze"];
NSLog(#"++++++------%#",viewcontrollerstring);
}
PS: I checked - UILocalNotification stop sound after notification is dismissed and this - Stop UILocalNotification Sound on slide to view but it was of no help. :(
This appears to be an open bug with iOS 7 as filed here. It also seems that when a device is passcode-locked, this issue does not appear. A pretty ugly hack that worked for me is setting a value for the application badge number and removing it immediately when the app comes into foreground. A sample code would be:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[UIApplication sharedApplication] setApplicationIconBadgeNumber: 1];
[[UIApplication sharedApplication] setApplicationIconBadgeNumber: 0];
}
EDIT:
Apparently, the above mentioned hack is not actually working on iOS 7.1+. The only work-around I found is the following, but I'm very hesitant in calling it an actual answer:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
MPMusicPlayerController *musicPlayer = [MPMusicPlayerController applicationMusicPlayer];
[musicPlayer setVolume:0.0f];
}
But there are a number of serious flaws with the code above:
setVolume method is deprecated since iOS 7 (although apparently many apps are still using it)
You have at some point of your app to re-set the volume to a proper (non-zero level)
Setting the volume property will most definitely have side-effects in other apps that might be playing sounds or music at the same time
UPDATE September 18, 2014
This issue seems to be resolved on iOS 8.
I am developing the chat application , If someone sends any message to me & my app is in background then i receive that message and i play the tone whenever message received,while tone is playing in mean while application is opened then music is getting stopped . How to make it continue when app comes to foreground also. (Tone must be complete as per duration available init). How to solve this issue. Waiting for the answer.
You have to work on remote control received events with notification..
- (void)remoteControlReceivedWithEvent:(UIEvent *)theEvent {
if (theEvent.type == UIEventTypeRemoteControl) {
switch(theEvent.subtype) {
case UIEventSubtypeRemoteControlPlay:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TogglePlayPause" object:nil];
break;
case UIEventSubtypeRemoteControlPause:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TogglePlayPause" object:nil];
break;
case UIEventSubtypeRemoteControlStop:
break;
case UIEventSubtypeRemoteControlTogglePlayPause:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TogglePlayPause" object:nil];
break;
default:
return;
}
}
}
you can view complete answer here
You will have to use AVAudioSession. You can have the music temporarily pause or lower in volume while alert plays, then it'll resume full force!
First to initialize audio:
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]
[audioSession setActive:NO error:nil];
Playing the sound (AVAudioPlayer's play/prepareToPlay will activate the audio session for you):
AVAudioPlayer* audioPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:nil];
[audioPlayer play];
Stopping the alert sound:
[audioPlayer stop];
[[AVAudioSession sharedInstance] setActive:NO withFlags:AVAudioSessionSetActiveFlags_NotifyOthersOnDeactivation error:nil];
The flag kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation tells the system to notify background audio to resume playing after the sound is played.
I am trying out the AVFoundationFramework.
Presently I am able to run the AVAudioPlayer in the foreground.
When the app goes to the background, the AVAudioPlayer does not continue to play.
The exact steps of implementation are:
Adding Background Tasks to the .plist
Setting up AVAudioPlayer
self.objAVAudioPlayer = [[AVAudioPlayer alloc]initWithData:mp3Data error:&error];
self.objAVAudioPlayer.delegate = self;
Setting up AVAudioSession
if([self.objAVAudioPlayer prepareToPlay])
{
AVAudioSession *objAVAudioSession = [AVAudioSession sharedInstance];
[objAVAudioSession setCategory:AVAudioSessionCategoryPlayback error:&error];
[objAVAudioSession setActive:YES
error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self.objAVAudioPlayer play];
}
When the app is resumed, it starts from the exact same place where it resigned.
The base SDK is iOS 7.0.
Any idea as to what I am missing? Any help will be appreciated.
For playing audio in background mode you should set up AVAudioSession and add this code
UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:backgroundTask]; //Tell the system that we are done with the tasks
backgroundTask = UIBackgroundTaskInvalid; //Set the task to be invalid
}];
into your AppDelegate.m class in method
- (void)applicationDidEnterBackground:(UIApplication *)application {
//Some code
}
I have audio, fetch and remote-notification set in UIBackgroundModes and I successfully receive remote notifications with my app in the background (not active) via:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
I have the following in my: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions:
self.audioPlayer = [[AVPlayer alloc] init];
NSError *sessionError = nil;
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive:YES error:&activationError];
if (![[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&sessionError]) {
NSLog(#"[AppDelegate] Failed to setup audio session: %#", sessionError);
}
And in - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler I have the following:
NSLog(#"Playing url: %#", filePath);
AVPlayerItem * currentItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:filePath]];
[self.audioPlayer replaceCurrentItemWithPlayerItem:currentItem];
[self.audioPlayer play];
I see this code execute via NSLog but no sound is produced. Actually, if the app receives a notification within a few seconds of going to the background, audio does play, or ie. the first time it gets a notification audio plays, but never after that.
Can an app in iOS 7 initiate audio output asynchronously like this, from the background, ie. after it has been asleep and not produced any audio for some time?
You cannot initiate audio in the background. The only thing the audio background mode allows you to do is to continue producing sound as the app goes from the foreground to the background.
And this is a perfectly reasonable rule. It would be terrible if any app that happened to be running in the background could suddenly start producing sound out of the device whenever it likes, to the mystification and annoyance of the user!
However, if your app is capable of receiving remote events, and if it has produced sound so that it is the remote event target, then, with audio background mode, it can go on being the remote event target and thus can produce sound in the background, as long as no other app becomes the remote event target in the meantime.
The most reliable way to produce a sound while in the background is by attaching the sound to a local notification.