Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 7 SDK not abiding background audio

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'm trying to do is play audio in the app from a file stored in the app bundle (not from the Music library). I want to have audio played in background and controlled when screen is locked (in addition to Control Center).

I set the APPNAME-Info.plist key, UIBackgroundModes, to audio. It is not handling things in the app delegate; everything is done inside the ViewController

@interface ViewController : UIViewController <AVAudioPlayerDelegate>

Within the implementation's viewDidAppear: method I call super and then the following code:

// Once the view has loaded then we can register to begin receiving controls and we can become the first responder
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];

In my implementation's viewWillDisappear: method, I have the following code:

// End receiving events
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];

I have also implemented the canBecomeFirstResponder method, which returns YES. Next, I implemented the remoteControlReceivedWithEvent: method:

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    // If it is a remote control event handle it correctly
    if (event.type == UIEventTypeRemoteControl) {
        if (event.subtype == UIEventSubtypeRemoteControlPlay) {
            [self playPauseAudio:self];
        } else if (event.subtype == UIEventSubtypeRemoteControlPause) {
            [self playPauseAudio:self];
        } else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause) {
            [self playPauseAudio:self];
        }
    }
}

What is confusing me is that this exact same setup was working fine on iOS 6. On iOS 7, it doesn't work. It used to be so easy in iOS 6. Something fundamentally changed in iOS 7 SDK. What am I missing?

like image 336
codejunkie Avatar asked Sep 24 '13 12:09

codejunkie


3 Answers

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.

like image 92
codejunkie Avatar answered Nov 20 '22 15:11

codejunkie


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;
        }
    }
}
like image 35
MikeFieldsNE Avatar answered Nov 20 '22 15:11

MikeFieldsNE


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

enter image description here

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.

like image 6
chandan Avatar answered Nov 20 '22 16:11

chandan