On iOS, playing audio in the background requires special permissions that you can enable in your app's Capabilities tab. If you enable the Background modes capability, you can select the Audio, Airplay, and Picture in Picture option to make your app eligible for playing audio in the background. The following screenshot shows the enabled capability for playing audio in the background:
If you want to add proper support for background audio playback, there are three features you need to implement:
- Set up an audio session, so audio continues playing in the background.
- Submit metadata to the "now playing" info center.
- Respond to playback actions from remote sources, such as the lock screen.
You can set up the audio session for your app with just two lines of code. When you create an audio session, iOS will treat the audio played by your app slightly differently. For instance, your songs will play even if the device is set to silent. It also makes sure your audio is played when your app is in the background, if you have the proper capabilities set up. Add the following code to viewDidLoad() to set up an audio session for the app:
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.allowAirPlay]) try? AVAudioSession.sharedInstance().setActive(true, options: [])
The second feature to add is to supply information about the currently playing track. All information about the currently playing media file should be passed to the MPNowPlayingInfoCenter object. This object is part of the MediaPlayer framework, and is responsible for showing the user information about the currently playing media file on the lock screen and in the command center. Before you pass information to the "now playing" info center, make sure to import the MediaPlayer framework at the top of the AudioViewController.swift file.
Next, add the following line of code to viewDidLoad():
NotificationCenter.default.addObserver(self, selector: #selector(updateNowPlaying), name: UIApplication.didEnterBackgroundNotification, object: nil)
In the documentation for MPNowPlayingInfoCenter, Apple states that you should always pass the most recent now playing information to the info center when the app goes to the background. To do this, the audio view controller should listen to the UIApplication.didEnterBackgroundNotification notification, so it can respond to the app going to the background. Add the following implementation for the updateNowPlaying() method to the AudioVideoController:
@objc func updateNowPlaying() { var nowPlayingInfo = [String: Any]() nowPlayingInfo[MPMediaItemPropertyTitle] = titleLabel.text ?? "untitled" nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = audioPlayer.currentTime nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = audioPlayer.duration MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo }
The preceding code configures a dictionary with metadata about the currently playing file, and passes it to the "now playing" info center. This method is called automatically when the app goes to the background, but you should also update the "now playing" information when a new song begins playing. Add a call to updateNowPlaying() in the loadTrack() method to make sure the "now playing" information is updated whenever a new track is loaded.
The next and final step is to respond to remote commands. When the user taps the play/pause button, next button, or previous button on the lock screen, this is sent to your app as a remote command. You should explicitly define the handlers that should be called by iOS when a remote command occurs. Add the following method to AudioViewController to add support for remote commands:
func configureRemoteCommands() { let commandCenter = MPRemoteCommandCenter.shared() commandCenter.playCommand.addTarget { [unowned self] event in guard self.audioPlayer.isPlaying == false else { return .commandFailed } self.startPlayback() return .success } commandCenter.pauseCommand.addTarget { [unowned self] event in guard self.audioPlayer.isPlaying else { return .commandFailed } self.pausePlayback() return .success } commandCenter.nextTrackCommand.addTarget { [unowned self] event in self.nextTapped() return .success } commandCenter.previousTrackCommand.addTarget { [unowned self] event in self.previousTapped() return .success } UIApplication.shared.beginReceivingRemoteControlEvents() }
The preceding code obtains a reference to the remote command center and registers several handlers. It also calls beginReceivingRemoteControlEvents() on the application object to make sure it receives remote commands. Add a call to configureRemoteCommands() in viewDidLoad(), to make sure that the app begins receiving remote commands as soon as the audio player is configured.
Try to run your app and send it to the background. You should be able to control media playback from both the control center and the lock screen. The visible metadata should correctly update when you skip to the next or previous song, and the scrubber should accurately represent the current position of playback in the song.
At this point, you have implemented a reasonably complete audio player that has pretty sophisticated behaviors. The next step in your exploration of media on iOS is to discover how you can take pictures and record video.