iPhone App backgrounding with MPMusicPlayer - iphone

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.

Related

Run app in background only if audio os playing

Is it possible to run an app in background only if audio is playing?
I have made an app and I want that when the app goes in background, it will check if audio is playing or not. If audio is playing, then the app will run in background else the app will close.
TO DO this I have set in info.plist
Application does not run in background YES
Then audio is not playing in background.
Now to run audio I set
if(AUDIO_IS_PLAYING==NO){
exit(0);
}
But I think apple will not permit this.
Any other ways if you know to run the app in background only if audio is playing else it will be closed.
Use notifications:
Following notification is posted when the application enters the background.
UIApplicationDidEnterBackgroundNotification
Following notification is posted when the application becomes active.
UIApplicationDidBecomeActiveNotification
Add notification observer in function
//Adding observer for notification
-(void)viewDidLoad
{
// Your other code goes here
// Adding observer for notification when application entered the background
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidEnterBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
// This method will be called when application entered in background
-(void)applicationDidEnterBackgroundNotification:(id)sender
{
// Do whatever you want when application in background
}

Implementing and Troubleshooting Background Audio in iOS

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

Prevent iPhone from locking when connected to charger and app is running

I'm writing an iPhone app. When the app is running and the iPhone is charging, there is no need to lock the iPhone. Is it possible to prevent the locking of the iPhone when the device is charging and my app is running?
You can subscribe to UIDeviceBatteryStateDidChangeNotification notification to get the moment when your iphone begins/stops to charge. Then in case iphone is charging you can set idleTimerDisabled property in UIApplication object to YES to prevent device to go to sleep:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateBatteryState:)
name:UIDeviceBatteryStateDidChangeNotification
object:nil];
- (void) updateBatteryState:(NSNotification*)notification{
[UIApplication sharedApplication].idleTimerDisabled =
([UIDevice currentDevice].batteryState == UIDeviceBatteryStateCharging);
}
P.S. If user decides to put device to sleep with sleep/wake button there's no way to prevent him of doing so
This is not possible with current SDK.
EDIT: hmm, haven't got the question correctly from the first read - look on other replies for correct answer; my guess was, you were asking about if it possible to prevent appearance of the sync/charge screen when connecting device via usb or to the wall outlet

iOS 4: Remote controls for background audio

I'm currently attempting to set up background audio for an app I'm developing for iOS 4. The app doesn't have a dedicated music player viewController, however, unlike other background audio apps such as Pandora, which makes the task a bit more confusing.
I've set the appropriate Info.plist settings correctly and have an AVAudioPlayer object in my app delegate which is accessible from everywhere. When the user plays a song, I replace the AVAudioPlayer with a new one initialized with the song and play it. This all works great, except now I have no idea how to go about supporting remote control events.
Based on Apple's documentation, I have this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch(event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
if([iPhoneAppDelegate backgroundAudioPlayer].playing)
[iPhoneAppDelegate pauseBackgroundAudioPlayer];
else
[iPhoneAppDelegate playBackgroundAudioPlayer];
break;
}
}
The thing is, where do I put this? Apple's documentation seems to suggest this should go in some view controller somewhere, but my app has lots of view controllers and navigation controllers. Wherever I try to put this, for some reason tapping the Toggle Play/Pause button in the multitasking tray remote controls either causes the song to just pause for a moment and then unpause, or somehow causes the song to play twice.
The documentation examples are a bit misleading, but there is no need to subclass anything anywhere. The correct place to put remoteControlReceivedWithEvent: is in the application delegate, as it remains in the responder chain regardless of whether the app is in the foreground or not. Also the begin/end receiving remote control events should be based on whether you actually need the events, not on the visibility of some random view.
I found a couple of solutions to receiving global remote control events on the Apple Developer Forums after a bit of searching.
One way is to subclass UIWindow and override its remoteControlReceivedWithEvent:.
The second, perhaps nicer way is to subclass UIApplication and override sendEvent:. That way, you can intercept all the remote control events and handle them there globally, and not have any other responders handle them later in the responder chain.
- (void)sendEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
// Handle event
}
else
[super sendEvent:event];
}
The second method didn't work for me, sendEvent was never called. However the first method worked just nicely (subclassing UIWindow).
I struggled with this one for a while and none of the answers above worked. The bug in my code, and I hope that it will help someone reading this, was that I had the AudioSession set to mix with others. You want to be the foreground audio player to get Remote Control events. Check to see if you have INCORRECT code like this:
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
UInt32 doSetProperty = 0;
AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryMixWithOthers,
sizeof (doSetProperty),
&doSetProperty
);
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
And remove the AudioSessionSetProperty, or change doSetProperty to 1.
No need to subclass Window or forward events. Simply handle it from your main view controller. See the Audio Mixer (MixerHost) example for details.
http://developer.apple.com/LIBRARY/IOS/#samplecode/MixerHost/Listings/Classes_MixerHostViewController_m.html
Documentation explains it very well.
https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html
One thing that seems to influence this behavior is any category options you set for your AVAudioSession using setCategory:withOptions:error: instead of just setCategory:error:. In particular, from trial and error, it appears that if you set AVAudioSessionCategoryOptionMixWithOthers you will not get remote control events; the now playing controls will still control the iPod app. If you set AVAudioSessionCategoryOptionDuckOthers you will get remote control events, but it seems like there may be some ambiguity regarding which app is controlled. Setting the categoryOptions to 0 or just calling setCategory:error: works best.

How to stop MPMusicPlayerController from enabling screen locking

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()