There are a lot of questions relating to background music playback in iOS on StackOverflow. None fully explore all edge cases, the aim of
this question is to be the final word in background audio question on
iOS
Definitions & Assumptions
All the code, questions and examples refer to ios5.
"background" — The state an app is put into when the user presses the home button or the power button (so the devices displays the lock screen). The app can also be put into background using the multitasking switcher or the multitasking gestures on iPad.
"audio" — Audio played back using AudioQueue (including AVAudioPlayer)
Prerequisites
As I understand it there are 2 requirements to get an app to play audio in the background.
Set UIBackgroundModes to audio in the Info.plist
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
Requirements
My use-case is playing relatively long audio in the background (music). There are potentially hundreds of tracks and the app will play them sequentially. It can be considered that the audio will play indefinitely.
The app will handle interruptions by pausing the playback.
Questions
I've had mixed success with:
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:...];
Allowing audio to play in the background. But I'm confused as to if its required and how it differs to:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
Edge Cases
Interruptions. If you register to be notified of audio interruptions (phone calls etc), by becoming the delegate of AVAudioPlayer. For example, if you then pause or stop your audio when an interruption starts and resume when it ends is your app suspended if the interruption exceeds 10 minutes (max time allowed for background tasks to complete)?
The Simulator will stop the audio if Lock or Home are invoked, while using:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
However this works on a device. Is this a known issue?
I have some experience with GPS background mode, and background audio. This is not exactly the same as your situation (you want to play a long audio file, and I play short messages) but here's what I can tell you:
beginBackgroundTaskWithExpirationHandler This selector has one purpose when being invoked when in background: avoid the application to return to the suspended state in which no code can be invoked anymore (you're "frozen"). So as long as you invoked beginBackgroundTaskWithExpirationHandler and before you terminated your long running task with beginBackgroundTaskWithExpirationHandler, you use the CPU, and consume battery.
I really doubt that playing a file in the background should use the battery of the iPhone as if it was running an app so I doubt that beginBackgroundTaskWithExpirationHandler is really involved in your flow.
Simulator: don't rely on the simulator: it does not fully implement background modes. Actually, when you click on the home button, your app goes in background, but at this stage, you may still be able to execute code in your app. After a while, then, your app will be suspended (=frozen), and your code execution will be suspended in order to save the battery.
This suspended state will never occur on the simulator.
Interruptions. It's not up to you to pause/resume the playback when a phone call comes in. the platform is in charge of this, and you can just react to this with your AVAudioSessionDelegate . However, you can influence the way your session is going to interact with other audio sounds by setting property on your audio session (see kAudioSessionProperty_OverrideCategoryMixWithOthers for instance).
So the flow is more: your describe the way your audio session should interact with the rest of the system, the system will mix the sounds accordingly to that, and if your session gets interrupted, you'll be notified with the AVAudioSessionDelegate.
Hope this helps.
I have used below code to Device Control -
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
Used to get register for listening the remote control.
Once done remove it -
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
make the App canBecomeFirstResponder-
- (BOOL)canBecomeFirstResponder {
return YES;
}
Used delegate method to handle iPhone control, like play and pause while doble tap on the home button
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl) {
if (event.subtype == UIEventSubtypeRemoteControlPlay) {
[audioPlayer play];
NSLog(#"play");
} else if (event.subtype == UIEventSubtypeRemoteControlPause) {
[audioPlayer stop];
NSLog(#"pause");
} else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause) {
NSLog(#"toggle");
}
}
}
Related
I have created GPS base Application.
In which App logged GPS data every 1 second.
App used the NSTimer to fetch GPS data every second.
This NSTimer is start in background. The NSTimer is start when app received the silent push notification from APNS.
I have seen a problem in iOS 7 that when Phone attached with power cable at that time timer call appropriately but without attached power cable timer stops while App in background.
Any inputs to resolve this issue greatly appreciated.
An NSTimer is not guaranteed to fire if your app is not in foreground. Once you unplug the cable the system puts your app into background to save battery.
Using an NSTimer is not the supported method to get location data. Your CLLocationManager will tell its delegate when there is a new location. No need to poll it.
If you need to track geolocation in background you need to declare location updates as a background mode from the capabilities tab in Xcode 5 target settings. Otherwise your location manager will stop delivering location updates once your app is not in foreground.
There are only few use cases that you can implement for foreground. If you don't want your application to be refused within review please don't use any hacks. Of course, you can use NSTimer in background, but it must be created in thread (runloop) of background task. But this background task lives for the only certain amount of time, so your timer must fire during this period. In other words, your first goal is to create background task, and only then you can use the timer. There are few cases suitable for your purpose (that allow you to create this task): 1.Monitor significant location changes or region 2.Fetch data (iOs 7). So please refer
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"BG fetch in action");
//the only 30 sec is allowed to perform all your actions
//during this period you can each second track GPS data
[self performSelector:#selector(finishBackgroundFetch:) withObject:completionHandler afterDelay:27];
[[NSNotificationCenter defaultCenter] postNotificationName:#"StartMyGPSRoutine" object:nil];
}
-(void)finishBackgroundFetch:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"BG fetch completion handler");
[[NSNotificationCenter defaultCenter] postNotificationName:#"ForceStopAnyBackgroundTaskCreatedWithStartMyGPSRoutine" object:nil];
completionHandler(UIBackgroundFetchResultNewData);
}
of your application delegate -- this will be useful in your case as well as monitor significant location changes.
You ought to put anywhere:
if ([[UIApplication sharedApplication] respondsToSelector:#selector(setMinimumBackgroundFetchInterval:)]) {
NSLog(#"Set Force BG interval to %ld", interval);
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:interval];
}
In my case I fetch the GPS data each 5 min so 27sec to complete this task is not so long. Anyway you can play with time intervals with XCode. Please refer Main Menu->Debug->Simulate Background Fetch
we have an app that has a specific purpose where an exit is required. After the exit a process needs to run in the background for a certain amount of time or until finished. We just need to know how to programmatically force the app to enter the background where processes can continue running. Any help on this would be great! Thanks in advance!
UPDATE: We have confirmed that there does not seem to be a programmatic way to force the app to quit / enter background and continue running background tasks. You can force the the app to exit using exit(0); but this kills the app all together. However, the bulk of this question was concerning running tasks in the background. We have found a solution that allows our app to begin processing data and handling tasks that a user has setup to be processed. Here is the code required. This needs to be added to the app delegate and multitasking is required on the device / IOS.
- (void)applicationDidEnterBackground:(UIApplication *)app{
// Check that IOS supports multitasking
if ([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)]){
// Check that the device supports multitasking
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
// Custom setting to allow users the freedom to enable or disable background tasks
BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:#"backgroundTasksEnabled_key"];
if ( enabled ){
//Get the shared application instance
backGroundApp = [UIApplication sharedApplication];
background_task = [backGroundApp beginBackgroundTaskWithExpirationHandler: ^ {
[backGroundApp endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
// Run in background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"\n\nProcess background tasks!\n\n");
// Do your work here
});
}
}
}
}
You can force an iOS app into the background by sending a registered URL to another app to handle, such as a web site URL to Safari.
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: myWebsiteURL ]];
Many many apps that call Safari to handle URLs are approved by Apple.
To get any time in the background, an app has to be appropriately configured using one of the allowed background modes (audio, location or VOIP); or the app can request a few minutes of extra time in the background before being suspended by calling the beginBackgroundTaskWithExpirationHandler method
You can't have a process running (doing work) in the background in iOS, you get a few seconds when the app quits to do any clean up and that's it!
You cannot force an application into the background, I'm fairly sure that Apple's guidelines prohibit you from doing that. What could your app possibly be doing that it can only do in the background and not in the foreground?
I'm working on an iPhone iOS4 application that incorporates playing music from the user's iPod library. I also want to keep track of what songs have been played and be able to change the song randomly, even while in the background. So I set the music player using:
[self setMusicPlayer: [MPMusicPlayerController iPodMusicPlayer]];
Now, I want this application to continue to run and play music in the background, so I have set:
Required background modes: App plays audio
The problem I'm having is that my application loses control when it is moved into the background (when applicationDidEnterBackground is called, ie. on app switches). Since I'm using the iPodMusicPlayer the music continues to play but my app does not have control and therefore can't track or change the song.
Now, the Apple documentation states that your application should continue to execute in the background using this required background modes tag, but mine does not. Is it because I'm using MPMusicPlayer? Is there any way to get around it? Any ideas?
PS. I'm also trying to get the remote locked and multitasking iPod controllers to work with my application. I'm using the code below, but remoteControlReceivedWithEvent never gets called! Does it work with MPMusicPlayer? I've only seen it with AVAudioPlayer.
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
NSLog(#"remoteControlReceivedWithEvent");
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
NSLog(#"Play Pause");
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(#"Next");
break;
default:
break;
}
}
- (BOOL)canBecomeFirstResponder {
NSLog(#"canBecomeFirstResponder");
return YES;
}
- (void) viewWillAppear:(BOOL)animated{
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
You are correct. Your iPhone app will not run in the background while using MPMusicPlayerController. This will also prevent you from receiving remote control events.
If you want to play audio from the iPod library and have your app continue running in the background, you must use the lower-level AVPlayer class.
I have an application that requires the iPhone screen to remain active (or not, depending on user choice). I've done this by disabling the application idle timer, which works fine and dandy until I start playing media via the MPMusicPlayerController. Due to a bug in the SDK, this then reenables the idle timer with no apparent way to disable it again.
My app flow is:
App starts
Screen stays on
<...time passes...>
Play audio file
Idle timer kicks in
Screen turns off
I have an empty audio file playing in the background to stop the phone going into deep sleep, but I'd really like to keep the screen unlocked too.
Has anyone managed to figure out a workaround for this?
I had a similiar problem, and found a fix for it. The fix might work for you too:
I call a method periodically (every 10 seconds), which sets idleTimerDisabled first to NO, then to YES.
- (void)calledEveryTenSeconds
{
[UIApplication sharedApplication].idleTimerDisabled = NO;
[UIApplication sharedApplication].idleTimerDisabled = YES;
}
Only setting to YES alone does not fix the problem. It seems the property has to change first to be recognized by UIApplication.
My problem was, that the screen kept turning dark as soon as I switched music tracks on the iPod player via the headphone remote. My guess is, that this is the same issue as you are experiencing.
You should simply turn off the idle timer. What I usually do in a viewcontroller that needs to stay 'awake' is this:
- (void) viewWillAppear:(BOOL)animated
{
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
- (void) viewWillDisappear: (BOOL) animated
{
[[UIApplication sharedApplication] setIdleTimerDisabled: NO];
}
This will make sure the screen will not get locked due to user inactivity.
I found a solution to this problem. Invoke a method that disables the idleTimer in about 5 seconds after you start playing the music. It's a bit of a hack, but it is a workaround.
[[SoundEngine mainEngine] playMusic];
[self performSelector:#selector(setIdleTimeDisabled) withObject:nil afterDelay:5.0];
- (void) setIdleTimeDisabled {
[UIApplication sharedApplication].idleTimerDisabled = YES;
NSLog(#"Setting idleTimer to TRUE");}
let player = MPMusicPlayerController.applicationMusicPlayer()
player.setQueueWithStoreIDs(["some id"])
player.play()
player.pause()
how to set iPhone device to stay active ( to not lock ) while my app is running ?
Any idea
I'm not sure if this prevents the device from locking, but you can prevent the screen from dimming with the UIApplication's idleTimerDisabled property:
[UIApplication sharedApplication].idleTimerDisabled = YES;
From the documentation:
Important: You should set this property only if necessary and should be sure to reset it to NO when the need no longer exists. Most applications should let the system turn off the screen when the idle timer elapses. This includes audio applications. With appropriate use of Audio Session Services, playback and recording proceed uninterrupted when the screen turns off. The only applications that should disable the idle timer are mapping applications, games, or similar programs with sporadic user interaction.
This code will prevent your iPhone from going to sleep while your app is running
// avoid sleeping when this application is running
UIApplication *application = [UIApplication sharedApplication];
application.idleTimerDisabled = YES;
// Or simpler
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
If you landed here looking for an answer in Swift, it's this:
UIApplication.sharedApplication().idleTimerDisabled = true
for Swift 3
UIApplication.shared.isIdleTimerDisabled = true
The warning in this comment still applies.