I have done a lot of research, both on Google and StackOverflow. All the answers I found do not work in iOS 7. I started writing fresh app in iOS 7 SDK with Xcode 5.
All
I managed to solve this, and to save hair pulling by another poor soul here goes:
Firstly make sure your Info.plist correctly lists audio as a background mode.
(If you dont know what i'm talking about select YOURAPPNAME-Info.plist select that. Click oin the plus sign and add a new key called UIBackgroundModes
and expand it. Add a value called audio
.)
You'll need a reference to whatever playback object is creating the audio. Since I'm only playing audio and AVplayer was not abiding by the background audio, use this in your view controller's header:
@property (nonatomic, retain) MPMoviePlayerController *audioPlayer;
In the implementation, do the following:
[super viewDidAppear:animated];
//Once the view has loaded then we can register to begin recieving controls and we can become the first responder
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
and
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//End recieving events
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
add two methods
//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void) registerForAudioObjectNotifications {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver: self
selector: @selector (handlePlaybackStateChanged:)
name: MixerHostAudioObjectPlaybackStateDidChangeNotification
object: audioObject];
}
now ALL important code - this enables your app to control audio from "control center" and from lock screen:
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
[self playOrStop: nil];
break;
default:
break;
}
}
}
you can add many many types of Event types here and call any method.
Typical events are:
UIEventSubtypeRemoteControlPlay = 100, //Parent EVENT
// All below are sub events and you can catch them using switch or If /else.
UIEventSubtypeRemoteControlPause = 101,
UIEventSubtypeRemoteControlStop = 102,
UIEventSubtypeRemoteControlTogglePlayPause = 103,
UIEventSubtypeRemoteControlNextTrack = 104,
UIEventSubtypeRemoteControlPreviousTrack = 105,
UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
UIEventSubtypeRemoteControlEndSeekingBackward = 107,
UIEventSubtypeRemoteControlBeginSeekingForward = 108,
UIEventSubtypeRemoteControlEndSeekingForward = 109,
To Debug help you can use:
MPMoviePlayerController *mp1= (MPMoviePlayerController *)[notification object];
NSLog(@"Movie State is: %d",[mp1 playbackState]);
switch ([mp1 playbackState]) {
case 0:
NSLog(@"******* video has stopped");
break;
case 1:
NSLog(@"******* video is playing after being paused or moved.");
break;
case 2:
NSLog(@"******* video is paused");
break;
case 3:
NSLog(@"******* video was interrupted");
break;
case 4:
NSLog(@"******* video is seeking forward");
break;
case 5:
NSLog(@"******* video is seeking Backwards");
break;
default:
break;
and thats it - hope it helps some one out there! - this is working perfect on iOS 7 and iOS 6 with Storyboard app as well as control using Headphone and all new control centre too.
Apparently the problem was on Apple's side as iOS update 7.0.3 fixes this issue. Besides what Alex noted about UIEventSubtype changes the code that worked on iOS6 now works on iOS7.
For sake of completeness, here is my relevant code that is working in both iOS6 and iOS7 - after the udpate to 7.0.3. Also included AVFoundation.framework and MediaPlayer.framework in project Build Phases -> Link binary with libraries. No code for this in app delegate.
In viewcontroller .h file:
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
@interface NewsDetailViewController : UIViewController <UIWebViewDelegate, AVAudioSessionDelegate>
@property (nonatomic) MPMoviePlayerController *audioPlayer;
In viewcontroller .m file:
- (void)viewDidLoad
{
[super viewDidLoad];
self.audioPlayer = [[MPMoviePlayerController alloc] initWithContentURL:audioUrl];
[self.audioPlayer prepareToPlay];
[self.audioPlayer.view setFrame:CGRectMake(0, 0, self.audioView.frame.size.width, 42)];
self.audioPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.audioView addSubview:self.audioPlayer.view];
[self.audioPlayer play];
NSError *setCategoryError = nil;
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive:YES error:&activationError];
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.audioPlayer stop];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlPlay:
[self.audioPlayer play];
break;
case UIEventSubtypeRemoteControlPause:
[self.audioPlayer pause];
break;
case UIEventSubtypeRemoteControlTogglePlayPause:
if (self.audioPlayer.playbackState == MPMoviePlaybackStatePlaying) {
[self.audioPlayer pause];
}
else {
[self.audioPlayer play];
}
break;
default:
break;
}
}
}
One thing to note that is different from iOS6 to iOS7 with remote control events is that in iOS6 the play/pause events came as one UIEventSubtype
:
UIEventSubtypeRemoteControlTogglePlayPause
And in iOS7 they come as two separate subtypes:
UIEventSubtypeRemoteControlPause
UIEventSubtypeRemoteControlPlay
Since I am also interested in finding this solution I will add some more information from my side.
I am experiencing the same problem, but still Apple documentation hasn't changed about remote control event management.
I tried moving some stuff and something interesting happened.
I originally had the remote control event management in my TabBar controller. Now that i moved everything in the player view controller, I can see that I can again control the music playback with my headset but not from the buttons in the new control panel of iOS7 (the one that come from the bottom on the screen).
This is quite weird.
In order to solve the problem, let's try to enrich the thread with our test.
If you want to play audio in background in iphone and simulator also then you need to write this code in plist and Firstly make sure your Info.plist correctly lists audio as a background mode.
(If you dont know what i'm talking about select YOURAPPNAME-Info.plist select that. Click on the plus sign and type a key UIBackgroundModes and enter. Add a value called "App plays audio"(for simulator) or "App plays audio or streams audio/video using AirPlay"(For iphone).)
in AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
__block UIBackgroundTaskIdentifier task = 0;
task=[application beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"Expiration handler called %f",[application backgroundTimeRemaining]);
[application endBackgroundTask:task];
task=UIBackgroundTaskInvalid;
}];
}
Add these two framework in your project and some line of code in ViewController.h
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController : UIViewController <UIWebViewDelegate, AVAudioSessionDelegate>
@property (nonatomic) MPMoviePlayerController *audioPlayer;
Remind that these frameworks refrences should be added in your project.
Then in Viewcontrller.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.audioPlayer stop];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURL *audioUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"songName" ofType:@"mp3"]];
self.audioPlayer = [[MPMoviePlayerController alloc] initWithContentURL:audioUrl];
[self.audioPlayer prepareToPlay];
[self.audioPlayer.view setFrame:CGRectMake(0, 0, self.view.frame.size.width-100, 42)];
self.audioPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:self.audioPlayer.view];
[self.audioPlayer play];
// for run application in background
NSError *setCategoryError = nil;
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive:YES error:&activationError];
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
}
I hope it will help you to play audio in background in iphone and simulator as well.