iOS – Playing audio in the background

A while ago, I wrote an iOS music app called Fonzi.  Fonzi was a music jukebox player app which allowed users to select songs for playback from a single device. Apple provides extensive documentation on Audio session programming.  This post describes whats required to get your iOS app to play audio in the background. Sample code is available on Github for both Objective-C and Swift.

Update (18 January, 2017)

Codebases (Swift and Objective-C) have been updated to Support Swift 3, Xcode 8. The following changes were needed (see changelog for details)

  • Added NSAppleMusicUsageDescription to provide a permission description for Music Library usage. 
  • Adjusted code to use ‘MPMediaLibrary.requestAuthorization { (authStatus) …’ before querying for songs
  • Check the AssetURL for the song is available. Media queries can return songs which are in the library, but haven’t been downloaded yet.

Playing audio in the background

At a very basic level there are three prerequisites your app should satisfy to play audio in the background:

  • Music must be playing
  • Setup ‘Background Modes’  for Audio in your Target Capabilities.
  • For basic playback, initialise your audio session, and set your audio session category to AVAudioSessionCategoryPlayback. If you don’t, you’ll get the default behavior.
 You should also configure your app to respond to
  • changes in audio output
  • audio session interruptions (e.g. phone call)
  • remote control events (via control centre, or the lock screen)

Playing music

I used AVQueuePlayer to play audio because it provided basic queuing mechanisms for audio. I queried iTunes for song lists, allowed songs to be selected by the user, and added them to the queue for playback. If you correctly configure your app to play music in the background AND the app is currently playing audio content, it will continue to run in the background. By queueing up songs in AVQueuePlayer, songs are always playing and the app is never suspended. If there is no music being played, your app will be suspended.

AVQueuePlayer accepts instances of AVPlayerItems. Configure AVQueuePlayer to advance to the next item in the queue. Do this by setting its actionAtItemEnd property to AVPlayerActionAtItemEndAdvance. Register for the AVPlayerItemDidPlayToEndTimeNotification. You’ll need this to update any changes to your UI to indicate when a song has ended.

Setup background modes

Select your app Target..Capabilities…Background Modes. Turn the capability on, and select ‘Audio and AirPlay’

backgroundAudio

Initialise your audio session

A sample audio session initialisation is show below. We also register for interruptions – to decide how to update your UI/app state.

Set Audio Session Category 

By setting the Audio Session category to AVAudioSessionCategoryPlayback, audio will continue to play when the silent switch is enabled,  or when the screen is locked. For more information about categories, particularly around how you get your app to play nice with other audio sessions, see the Apple documentation.

Listen for changes in audio output

When a user plugs in/plugs out headphones. docks/undocks the device, your app should respond appropriately. The old way to do this would be too add listener callbacks, but its much easier now with Notifications. Listen for AVAudioSessionRouteChangeNotification  – check Apple’s documentation

Handling Audio interruptions

The most common cause of an audio interruption is a phone call, but could be a clock or calendar alarm too. The app’s audio session is deactivated. By implementing the AVAudioSessionDelegate, we’re given the opportunity to adjust the app state. In Fonzi’s case – pause the AVQueuePlayer, and if the UI is visible, adjust as necessary. There’s nothing worse than music playing over a phone call – unless the music is more entertaining than the person you’re talking to.

Once the audio interruption is completed, another call to the delegate  is made, allowing the app to resume the audio session, and adjust the UI if necessary. Implement the beginInterruption and endInterruption methods on AVAudioSessionDelegate to do so.

Remote Control events

Remote control events are any event received for the purpose of controlling media. eg iTunes pause/play/next/previous buttons available from the control center, or remote-control events from play/pause buttons on headphones. Your app can respond to these events. Configure your UI to respond to remote control events by:

  • registering for the events;
  • allow the controller to become first responder.
  • If the app is backgrounded, the view controller will respond appropriately.

NOTE: Remember to deregister for the remote control events when the view disappears through normal view lifecycle events.

By overriding the remoteControlReceivedWithEvent method you can choose which event to respond to. In the sample below, we respond to play/pause events only, but there are others too.

Demo apps

Sample apps on Github (Objective-C or Swift) demo:

  • Querying iTunes for music
  • Setting up Audio session and music playback.
  • Background playback.
  • Receiving remote control events

You’ll need to run them on a device with iTunes songs.


Also published on Medium.

8 Comments iOS – Playing audio in the background

  1. jean

    hi !
    on my Xcode, this line fails : YOURPLAYERINSTANCE *playerInstance = (YOURPLAYERINSTANCE*) inUserData;

    I think it’s because I must replace YOURPLAYERINSTANCE by …something, but what ?
    I’m a begginer so everything is a problem for me 😉

    Reply
  2. Jonathan

    Hi Jean,

    YOURPLAYERINSTANCE is the instance of the class you used to register the route changes with:

    AudioSessionAddPropertyListener ( kAudioSessionProperty_AudioRouteChange,
    audioRouteChangeListenerCallback, self );

    YOURPLAYERINSTANCE is the ‘self’ class instance. Basically you’re casting the generic inUserData to your type-specific implementation eg MyAudioPlayer. MyAudioPlayer might be the class with all your Audio Playback logic. The reason I do this is to allow my audio player to respond in an appropriate fashion to route changes eg pause/play etc.

    Hope this helps!
    Jonathan

    Reply
  3. kennedy

    Hi
    Thanks for nice tutorial. Is possible to play remote music instead.. Lets say you have an array of music returned as json from server.

    Reply
  4. xyzisinus

    Thanks for providing the sample code. It is very helpful! When I use the sample app, I see a couple of issues which I have reported to the github repository. I’ll also put the fixes into my fork of the repository in a day. You are welcome to pull my fixes. Thanks again.

    Reply
  5. Javeedpasha.Md

    Hi,
    I’m using Audio Streamer class to play an stream url. I have to pause audio when webview plays an audio file like Sound cloud. I have tried with “MPMoviePlayerController” notifications to know when user starts playing audio file in webview but none of the notification fired when user plays audio in webview.

    Thanks,
    Md.Javeedpasha

    Reply
  6. joh

    Hi,
    I have an app using UIWebView to display my page that streams audio using tag. I need it to play on the background but I’m a noob when it comes to XCode..

    I suppose i need to add this code somewhere

    NSError *setCategoryError = nil;
    BOOL success = [[AVAudioSession sharedInstance]
    setCategory: AVAudioSessionCategoryAmbient
    error: &setCategoryError];
    if (!success) { /* handle the error in setCategoryError */ }

    but where?

    Thanks in advance 🙂

    Reply
    1. Jonathan

      HI – just follow the code in ‘initSession’ – you can see how it works in the sample apps (either Swift or Objective-C). see links above! To see when to call it, follow the call stack in the code. If you need more help, email me!

      Reply

Leave a Reply to Jonathan Cancel reply

Your email address will not be published. Required fields are marked *