How to configure an iOS app to play audio in the background. Code samples available in Swift and Objective-C.
Some time 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.
UPDATE (28 Dec, 2021) sample code repos (Swift and Objective-C) have been updated to Support Swift 5, Xcode 13 and iOS deployment target 15.1
- Note: One change I made to this demo code back in 2017 was to play local songs on the device only. I did this by checking the AssetURL for the song is available. Media queries can return songs which are in the library, but haven’t been downloaded yet. I believe songs which haven’t been downloaded onto device can be played by preparing a song for playback, but I haven’t tested this.
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.
- 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 and play audio. 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.
Request permission from users to query the app music library #
To query songs, users must authorize your app to do so. Use the requsetAuthorization handler to do this. Here’s the logic I use in the test app:
Registering for end of song events #
Register for the AVPlayerItemDidPlayToEndTimeNotification. You’ll need to respond to this event 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, AirPlay and Picture in Picture’
Initialise your audio session #
A sample audio session initialisation is show below. We also register for interruptions to allow for updates to your app UI/state.
TestMusicPlayer - session intialisation #
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.
endInterruption methods on AVAudioSessionDelegate to do so.
Remote Control events #
Remote control events are any event received for the purpose of controlling media. eg Music 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 de-register for the remote control events when the view disappears through normal view lifecycle events.
Override remoteControlReceivedWithEvent #
By overriding the
remoteControlReceivedWithEvent method you can choose which event to respond to. In the sample below, we respond to play/pause events only, and pause the queue. There are others events too.
Demo apps #
- Authorize the app to make media qeuries
- Query the device for music
- Setup up Audio session and music playback.
- Receive Receiving remote control events
This code works for songs downloaded on the device. You’ll need to run on a device, and download a song to test. Open the music app, select the song and download: