iosswiftuiviewcontrollernsnotificationcenteravaudiosession

Swift -How to check if Headphones are Plugged In when the VC First Appears


When a vc is first pushed on/presented, how can I check if headphones are already plugged in?

In the below code, if the headphones aren't plugged in when the vc first appears, if I then plug in headphones and unplug them, everything works fine.

But if the headphones are already plugged when the vc first loads, the Notification to detect them doesn't fire. It does fire once I unplug them though.

var didSubviewsLayout = false
override func viewDidLayoutSubviews() { // I also tried viewDidLoad
    super.viewDidLayoutSubviews()

    if didSubviewsLayout { return }
    didSubviewsLayout = true

    do {
        try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [ .duckOthers,
                                                                                                        .allowBluetoothA2DP,
                                                                                                        .allowAirPlay,
                                                                                                        .mixWithOthers,
                                                                                                        .defaultToSpeaker]
        )
            
        try AVAudioSession.sharedInstance().setActive(true)
            
    } catch { }

    setHeadphonesNotification() // tried this here first
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    setHeadphonesNotification() // tried this here after it didn't work the first time
}

func setHeadphonesNotification() {
    
    NotificationCenter.default.addObserver(self, selector: #selector(audioRouteChangeListener),
                                           name: AVAudioSession.routeChangeNotification,
                                           object: nil)
}

@objc private func audioRouteChangeListener(notification: NSNotification) {
    guard let userInfo = notification.userInfo else { return }
    guard let audioRouteChangeReason = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt else { return }
 
    switch audioRouteChangeReason {
    case AVAudioSession.RouteChangeReason.newDeviceAvailable.rawValue:
        print("headphone plugged in")
        view.backgroundColor = .red
        
        let session = AVAudioSession.sharedInstance()
        for output in session.currentRoute.outputs where output.portType == AVAudioSession.Port.headphones {
            view.backgroundColor = .blue
            break
        }
    case AVAudioSession.RouteChangeReason.oldDeviceUnavailable.rawValue:
        print("headphone pulled out")
        view.backgroundColor = .orange

        if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
            for output in previousRoute.outputs where output.portType == AVAudioSession.Port.headphones {
                view.backgroundColor = .white
                break
            }
        }
    default:
        break
    }
}

Solution

  • You can use currentRoute.outputs on the AVAudioSession to check what outputs initially exist:

    /// All current connected output device port types.
    var outputPorts: [AVAudioSession.Port] { AVAudioSession.sharedInstance().currentRoute.outputs.map { $0.portType } }
    
    let areHeadphonesConnected: Bool = outputPorts.contains(.headphones)