AVPlayer play audio from iOS 7 background notification - ios

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.

Related

my app media play in background mode didn't play if once I open apple music app

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

Play audio while app in background not working

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.

Play a background sound in WatchKit app

I have a button in WatchKit that sends a notification to the main iPhone app like this.
-(IBAction) startSound
{
//turn sound on
NSString *requestString = [NSString stringWithFormat:#"startSound"]; // This string is arbitrary, just must match here and at the iPhone side of the implementation.
NSDictionary *applicationData = [[NSDictionary alloc] initWithObjects:#[requestString] forKeys:#[#"startSound"]];
[WKInterfaceController openParentApplication:applicationData reply:^(NSDictionary *replyInfo, NSError *error) {
//NSLog(#"\nReply info: %#\nError: %#",replyInfo, error);
}];
}
In my iPhone app delegate I have added the following code.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(#"handleWatchKitExtensionRequest ...");
NSMutableDictionary *mutDic = [[NSMutableDictionary alloc] init];
//This block just asks the code put after it to be run in background for 10 mins max
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
NSString *request = [userInfo objectForKey:#"startSound"];
if ([request isEqualToString:#"startSound"])
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: #"warning" ofType: #"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
myAudioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
myAudioPlayer1.numberOfLoops = -1; //inifinite
[myAudioPlayer1 play];
}
reply(nil); //must reply with something no matter what
//once code is all done and the reply has been sent only then end the bg-handler
[application endBackgroundTask:bgTask];
}
Yet, when my app went for apple review, it got rejected for reasons that my app had to be running in the foreground for the sound feature to work. What did I miss?
10.6 - Apple and our customers place a high value on simple, refined, creative, well thought through interfaces. They take more work but are
worth it. Apple sets a high bar. If your user interface is complex or
less than very good, it may be rejected
10.6 Details
We still found that your Apple Watch app requires the containing app
to be running in the foreground on iPhone in order to play siren
sounds, which provides a poor user experience.
Next Steps
Please see the UIApplicationDelegate Protocol Reference to implement
this method and use it to respond to requests from the Apple Watch
app.
Because this method is likely to be called while your app is in the
background, call the beginBackgroundTaskWithName:expirationHandler:
method at the start of your implementation and the endBackgroundTask:
method after you have processed the reply and executed the reply
block. Starting a background task ensures that your app is not
suspended before it has a chance to send its reply.
How long is the audio clip? In the code you've shared it looks like reply() would be called almost immediately, which wouldn't give the clip a chance to play. You should delay calling reply() until your audio clip has completed.
I do something very similar in my WatchKit app. When the user taps on a button I play audio from my iPhone app and my iPhone app does not need to foreground for it to work. There were two things I had to do to get it to work. The first was setting up a background task with beginBackgroundTaskWithName, which I can see you are doing. The second was to enable background audio capabilities in my background modes.
I don't remember doing anything other than those two to get it to work. I did already have background audio working in my app before I added support so it could be controlled with MPNowPlayingInfoCenter.

The UILocalNotification sound does not stop playing

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.

Starting AVQueuePlayer from background

Can't make AVQueuePlayer start playing sound queue when it's starting after the app went into the background.
The basic question is: how to start sound from newly created AVQueuePlayer instance from background?
It's for navi-like app that need to play couple of combined sounds with appropriate directions when the time comes. And most of the time the app works in background...
The details are below...
It plays just fine when I start it from active application, and finishes playing sound even after app went to background.
What I did so far:
In AppDelegate inside didFinishLaunchingWithOptions I added:
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
// Change the default output audio route
UInt32 doChangeDefaultRoute = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
After the app started I clicked the home button so the app went into the background.
When the time came this code executes (the app is still in background, but note, that I have enabled Audio and AirPlay background mode):
-(void)playTrainingFinishedSound
{
NSMutableArray *queue = [NSMutableArray array];
[queue addObject:[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"alertkoniectreningu" withExtension:#"m4a"]]];
[self initializeAudioPlayerWithQueue:queue];
[self.appDelegate.audioPlayer play];
}
-(void)initializeAudioPlayerWithQueue:(NSArray *)queue
{
self.appDelegate.audioPlayer = [[AVQueuePlayer alloc] initWithItems:queue];
self.appDelegate.audioPlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
}
Unfortunately this code doesn't make any sound, opposite to the situation when the app was in foreground.
Oh God, there was just one line missing in the AppDelegate didFinishLaunchingWithOptions [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
so it looks now like:
audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
if (audioSession) [audioSession setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// Change the default output audio route
UInt32 doChangeDefaultRoute = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
The reason the AVPlayer is failing is because you can't create/initialize an Audio Unit while an application is in the background, which is what the AVPlayer is attempting to do under the hood. The exception to this rule is of course audio-playing applications which can be started/resumed via the "Play" buttons built into the OS, while they are in the background. Thus, applications which subscribe to remote control events have the capability to start Audio Units in the background, which is why subscribing to these events appears to solve the problem.
It's unclear whether Apple is OK with using the API feature in this way.

Resources