iOS Multitasking: Background Audio

iOS Multitasking: Background Audio

Tutorial Details
  • Technology: iOS SDK
  • Difficulty: Intermediate
  • Completion Time: 30 - 60 Minutes
This entry is part 2 of 4 in the series iOS Multitasking

This is a continuation of the iOS Multitasking series. Along with notifications (as discussed last week), iOS Multitasking also provides a feature called Background Audio. As the name suggests, this feature enables applications to play audio even when the application is in the background, similar to how the iPod or Music Application bundled with the device works.

In this tutorial we will be covering playing a sound in the background, not how to record audio in the background. To demonstrate we will be making a simple noise maker and give it the ability to play in the background.

Step 1: Setting Up the Project

First create a new project and call it NoiseMaker (or some other name) as a View-Based Application with the default settings.

Once the project has been created go to NoiseMaker-Info.plist and add UIBackgroundModes as a new row. It should then create the array.

Open the array and to the right of Item 0 set it to audio. Your NoiseMaker-Info.plist should look like this:

iOS_Background_Audio


Now go to the target settings and go to the tab labeled Build Phases.


iOS_Background_Audio


When you’ve finished the last sub-step go to Link Binary With Libraries and add the AVFoundation framework.


iOS_Background_Audio

In the Spotlight Search on your Mac for the file HeadSpin Long.caf. Find the file and rename it so the space is deleted.

Drag the file into Xcode under the NoiseMaker Directory. Make sure Copy Resources into Destinations Folder is checked.

Step 2: Setting Up the User Interface

In the NoiseMaker Directory select the NoiseMakerViewController.h file and add the following code under the #import declaration.

	#import <AVFoundation/AVFoundation.h> 

Now add the following code under the @interface declaration.


	IBOutlet UIButton *playPauseButton; //Toggles the playback state
    IBOutlet UISlider *volumeControl; //Sets the volume for the audio player
    IBOutlet UILabel *alertLabel; //The alert label showing the status of the loading of the file
    AVAudioPlayer *audioPlayer; //Plays the audio


Then right under the closing bracket add the following code.


@property (nonatomic, retain) IBOutlet UIButton *playPauseButton;
@property (nonatomic, retain) IBOutlet UISlider *volumeControl;
@property (nonatomic, retain) IBOutlet UILabel *alertLabel;
@property (nonatomic, retain) AVAudioPlayer *audioPlayer;
- (IBAction)volumeDidChange:(id)slider; //handle the slider movement
- (IBAction)togglePlayingState:(id)button; //handle the button tapping
- (void)playAudio; //play the audio
- (void)pauseAudio; //pause the audio
- (void)togglePlayPause; //toggle the state of the audio



After all of this code your .h file should look like this.

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface NoiseMakerViewController : UIViewController {
    IBOutlet UIButton *playPauseButton; //Toggles the playback state
    IBOutlet UISlider *volumeControl; //Sets the volume for the audio player
    IBOutlet UILabel *alertLabel; //The alert label showing the status of the loading of the file
    AVAudioPlayer *audioPlayer; //Plays the audio
}
@property (nonatomic, retain) IBOutlet UIButton *playPauseButton;
@property (nonatomic, retain) IBOutlet UISlider *volumeControl;
@property (nonatomic, retain) IBOutlet UILabel *alertLabel;
@property (nonatomic, retain) AVAudioPlayer *audioPlayer;
- (IBAction)volumeDidChange:(id)slider; //handle the slider movement
- (IBAction)togglePlayingState:(id)button; //handle the button tapping
- (void)playAudio; //play the audio
- (void)pauseAudio; //pause the audio
- (void)togglePlayPause; //toggle the state of the audio
@end

Step 3: Building the Interface

Now open the NoiseMakerViewController.xib. First add a UILabel somewhere in the view. In the Attributes Inspector select the option to make the UILabel’s text centered and erase all of the UILabel’s text. Now go to the Size Inspector and set the X position to 20, the Y position to 100, the width to 280, and the height to 21.

Next go to the Connections Inspector and drag the referencing outlet to the files owner and select the alertLabel option.

iOS_Background_Audio
iOS_Background_Audio
iOS_Background_Audio

Now add a UIButton to the view and set the button’s text to Play. Go to the Size Inspector and set the X Position to 86, the Y Position to 211, the width to 150, and the height to 37. Then in the Connections Inspector drag the TouchUpInside action to the file owners and select the togglePlayingState: option.

Drag the referencing outlet to the files owner and select the option playPauseButton. Now drag out a UISlider and put it onto the view.

Go the Size Inspector and set the X Position to 18, the Y Position to 378, the width to 284, and the height to 23 (default height). Go to the Connections Inspector and drag the referencing outlet to the files owner and select the option volumeControl. Next, drag the valueChanged action to the files owner and select the option volumeDidChange:. The finished interface should look like this.

iOS_Background_Audio

Step 4: Implementing the Audio Player

Now open the NoiseMakerViewController.m file. Under the @implementation declaration add the following lines.

@synthesize playPauseButton;
@synthesize volumeControl;
@synthesize alertLabel;
@synthesize audioPlayer;
- (IBAction)volumeDidChange:(UISlider *)slider {
    //Handle the slider movement
    [audioPlayer setVolume:[slider value]];
}
- (IBAction)togglePlayingState:(id)button {
    //Handle the button pressing
    [self togglePlayPause];
}

Now in the dealloc: method add the following lines.

//Remove the objects from memory
    self.playPauseButton = nil;
    self.volumeControl = nil;
    self.alertLabel = nil;
    self.audioPlayer = nil;
    [playPauseButton release];
    [volumeControl release];
    [alertLabel release];
    [audioPlayer release];


Then add the following code under the dealloc: method.


- (void)viewDidAppear:(BOOL)animated {
    [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];
}
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    //End recieving events
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];
}
- (void)playAudio {
    //Play the audio and set the button to represent the audio is playing
    [audioPlayer play];
    [playPauseButton setTitle:@"Pause" forState:UIControlStateNormal];
}
- (void)pauseAudio {
    //Pause the audio and set the button to represent the audio is paused
    [audioPlayer pause];
    [playPauseButton setTitle:@"Play" forState:UIControlStateNormal];
}
- (void)togglePlayPause {
    //Toggle if the music is playing or paused
    if (!self.audioPlayer.playing) {
        [self playAudio];
    } else if (self.audioPlayer.playing) {
        [self pauseAudio];
    }
}
//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
    return YES;
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    //if it is a remote control event handle it correctly
    if (event.type == UIEventTypeRemoteControl) {
        if (event.subtype == UIEventSubtypeRemoteControlPlay) {
            [self playAudio];
        } else if (event.subtype == UIEventSubtypeRemoteControlPause) {
            [self pauseAudio];
        } else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause) {
            [self togglePlayPause];
        }
    }
}



Now in the viewDidLoad: method add the following lines of code under the [super viewDidLoad];

//Declare the audio file location and settup the player
    NSURL *audioFileLocationURL = [[NSBundle mainBundle] URLForResource:@"HeadspinLong" withExtension:@"caf"];
    NSError *error;
    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileLocationURL error:&amp;error];
    [audioPlayer setNumberOfLoops:-1];
    if (error) {
        NSLog(@"%@", [error localizedDescription]);
        [[self volumeControl] setEnabled:NO];
        [[self playPauseButton] setEnabled:NO];
        [[self alertLabel] setText:@"Unable to load file"];
        [[self alertLabel] setHidden:NO];
    } else {
        [[self alertLabel] setText:[NSString stringWithFormat:@"%@ has loaded", @"HeadspinLong.caf"]];
        [[self alertLabel] setHidden:NO];
        //Make sure the system follows our playback status
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        [[AVAudioSession sharedInstance] setActive: YES error: nil];
        //Load the audio into memory
        [audioPlayer prepareToPlay];
    }

Final Thoughts

Thanks for reading this tutorial. Next in the series will be Task Completion. Feel free to comment if you encounter any problems or just want to add a tip.

Series Navigation«iOS Multitasking: Local NotificationsiOS Multitasking: Background Tasks»

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://www.caffeinatedapp.com Curtis

    self.playPauseButton = nil;
    self.volumeControl = nil;
    self.alertLabel = nil;
    self.audioPlayer = nil;

    [playPauseButton release];
    [volumeControl release];
    [alertLabel release];
    [audioPlayer release]

    is bad, your nil’ing out the object reference before releasing it, so the release part doesn’t actually do anything. Should be the other way around.

    [playPauseButton release], playPauseButton = nil;

    and so on..

    • http://www.roathe.com Lane Roathe

      This is actually only partly correct; the nil section does indeed nil the ivars out so that the release doesn’t do anything, but those assignments also release the current assignment, if applicable.

      In other words,

      self.playPauseButton = nil;

      is mostly equivalent to:

      [playPauseButton release], playPauseButton = nil;

      Of course, this only applies if the ivar has a property with the retain option specified.

      So in the original code, the extra calls to release are not needed.

  • Jack Abeel
    Author

    @Curtis Thanks for the comment, I always thought it didn’t matter so much. I will make sure I do that for future projects.

    • http://www.caffeinatedapp.com Curtis

      @jack it matters a lot ;). something = nil; will only nil out the pointer, but the object still exists in memory, [something release] will nock the retain count down by one, so always be sure to release then nil out the pointer. Obviously if this is always being initialised and released many of times then you will get a lot of leaks if you don’t release your objects and have no pointers to them anymore :).

      • http://www.inancgumus.com Inanc Gumus

        Curtis and Jack, btw, dealloc in here will not be called anytime (most probably) so it doesn’t matter which comes first: release or nil. But, semantically I agree with Curtis of course, nil should follow release messages.

      • Craig

        Thanks Jack. Your example program was just what I needed to get AirPlay working in the background (after sleep) in iOS 5. Huh??? That’s right, everything you did to get this working is exactly the same when using an MPMoviePlayerController rather than an AVAudioPlayer. You couldn’t have know this at the time of your post, just an extra bonus I suppose.

        As for the commenters going back and forth about releasing your various pointers. Here is the correct deal on that.

        - Since they are all properties with a “retain” attribute, they manage their own reverence counts.
        - When a property has a “retain” attribute and is simply synthesized, a setter is generated for you which does the necessary retains and releases.
        - So all you need and should do with such properties is set them to nil in your dealloc. Doing so calls the generated setter which calls release on the current pointer and sets the ival to nil. Everything is done in one step.
        - Using properties like this is a “best practice” because as long as your object was created with autorelease, you should never need to make any calls to retain or release. All you have to do is remember to set them to nil in your dealloc and you should not have any memory leak problems.
        - BTW, everyone should be using static analysis of their code. That is capable of finding all kinds of memory problems.
        - Also with iOS 5, there is now automatic reference counting (ARC). That eliminates pretty much all of the memory management tasks we’ve discussed here. Worth a look.

  • Lee

    Hiya
    Thanks for the tutorials, Don’t know what I’d do without mobile tuts.

    I’m getting an error this line of code.

    audioPlayer = [AVAudioPlayer alloc] initWithContentsOfURL:audioFileLocationURL error:&error];

    Hope I’ve done that correct.

    use of undeclared identifer ‘amp’

    Sorry if it’s a stupid question.

    Lee

  • Lee

    Thanks all sorted… :-)

  • Rob DeNicola

    getting same error with that &amp line in the NS error argument someone please message me or email me with a solution!must get this background audio project working asap thanks

    • khushbu

      I am having same problem as ROB
      “use of undeclared identifier amp”
      help me…

  • Jack Abeel
    Author

    Sorry the HTML codes didn’t translate in the publishing stage. The & means & so the line really is

    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileLocationURL error:&error];

  • Pavel

    Very nice.

  • Satyam

    You didn’t handle interruptions like alarm, phone call etc. It would be nice if you have handled them.

  • pavan

    hello everyone , can anyone help me to find the way to develop iphone sdk apps. now i am starter of iPhone SDK app developer . suggest me to what kind of programs i need to learn.

    this help i must Thankfull.

  • Dhiren Shah

    How can we handle the Interruption while the app is in background. I tried the BeginInterruption and EndInterruption delegate methods of AVAudioPlayer. I am able to debug Begin Interruption Method. But my EndInterruption is not called. What could be the problem?? Also what will happen if my song finishes and i have implemented a logic of playing list?

  • http://www.zwiffer.com Matthew Knippen

    I was curious as to how we could send the track information to the background. I want to see the song name when I am at the lock screen and such. Is there any way to manually set this?

    Thanks in advance.
    -Matthew

  • Abby

    Okay, so I’m on step 3 and it’s not giving me the alertLabel option when I try to drag the referencing outlet to the file’s owner. Any idea why this might be? Ironically, I’ve tried this tutorial before and it worked fine, and I don’t think I’ve changed any of my settings since (I’m taking another stab with a different sound file, lss).

    • Abby

      NM; I found the problem.

  • Abby

    Okay, new issue; let’s say I want to have multiple buttons that play different sounds. How do I go about this? Can this be done with a slight modification to this setup or would I have to start from scratch?

  • fvisticot

    Great tutorial !!!
    Just one question. I try with a backgroud app trigged by socket event (voip background) to start playing a sound in background… Your tuto works fine but if i try to adapt the code to start playing the sound when the socket event is coming, no sound is played…:( any idea ?

    Tx

    fred

  • Vilasack

    I downloaded the sample and launched it in IOS Simulator 4.3.. I hit the home button and the music stopped. Was it suppose to play in the background?

    • Patrick

      I had the same problem you did, Vilasack. Apparently, background streaming does not work in the simulator and must be tested on a device.

  • Lars

    Thank you very much for this example. It seems that for backgrounding music, there aren’t any examples out there, but this one. This works like a charm, though! I tried a couple other examples, but couldn’t figure out why the audio wouldn’t work while backgrounded. I did have the plist setting for audio, but just could figure out what was going on.

  • mrljk3

    Does this work if you receive a phone call?

  • suchi

    its humble request to you please provide enough guideline related to code which you are using in this article….. instead of providing the control positioning of related sample like label co-ordination like that.

  • Donkey

    I have a question and a comment but it looks like no replies since
    Jack Abeel July 22, 2011 at 11:03 pm

    Is this tutorial site still being monitored?

  • brus

    Is possible to continue playing when home button is pressed?

    • AnonymousAppDev

      No. Your app must stop playing music

  • HP

    Thanks , your example helped me . i got perfect answer.

  • kuldeep singh

    how can i record a voice in background for my iphone app