David Cordero

Supporting AirPods in your iOS and tvOS media Apps

Published on 19 Oct 2020

The AirPods are equipped with sensors that allow users to trigger some gestures like double-tap or automatic ear detection, to control media playback.

The behavior of each of these gestures can be configured in the Settings of the operating system among the options: Siri, Play/Pause, Next Track, Previous Track, or Off.

By default, if you are using AVPlayerViewController, some of these gestures (like Play/Pause) will work out of the box.

The good news

If you are using your custom player, or you still need to extend the basic support provided by AVPlayerViewController, the good news is that the AirPods behave like any other Remote Control. In terms of development, that means that we can define our custom handlers for each gesture via MPRemoteCommandCenter.

Here you have an example of a simple player defining custom handlers for the gestures: Play/Pause, Next Track and Previous Track.

import UIKit
import AVFoundation
import AVKit
import MediaPlayer

class ViewController: AVPlayerViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        play(stream: URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8")!)
        setUpRemoteCommandCenter()
    }
    
    // MARK: - Private
    
    private func play(stream: URL) {
        let asset = AVAsset(url: stream)
        let playetItem = AVPlayerItem(asset: asset)
        player = AVPlayer(playerItem: playetItem)
        player?.play()
    }
    
    private func setUpRemoteCommandCenter() {
        let remoteCommandCenter = MPRemoteCommandCenter.shared()
        
        remoteCommandCenter.previousTrackCommand.isEnabled = true
        remoteCommandCenter.previousTrackCommand.addTarget(
            handler: {
                [weak self ] (_) -> MPRemoteCommandHandlerStatus in
                self?.previousTrackWasPressed()
                return .success
            })
        
        remoteCommandCenter.nextTrackCommand.isEnabled = true
        remoteCommandCenter.nextTrackCommand.addTarget(
            handler: {
                [weak self ] (_) -> MPRemoteCommandHandlerStatus in
                self?.nextTrackWasPressed()
                return .success
            })
        
        [remoteCommandCenter.playCommand,
         remoteCommandCenter.pauseCommand].forEach {
            $0.isEnabled = true
            $0.addTarget(handler: {
                [weak self] (_) -> MPRemoteCommandHandlerStatus in
                self?.playPauseTrackWasPressed()
                return .success
            })
         }
    }
    
    private func previousTrackWasPressed() {
        // TODO: Add here your logic for the previousTrack handler
    }
    
    private func nextTrackWasPressed() {
        // TODO: Add here your logic for the nextTrack handler
    }
    
    private func playPauseTrackWasPressed() {
        // TODO: Add here your logic for the playPause handler
    }
}

The bad news

The bad news (for tvOS) is that, since tvOS 14.0, MPRemoteCommandCenter is buggy and it does not longer work as expected. Hopefully Apple will eventually fix this topic.

You can find more information about this topic in this thread in the Apple Developer Forums