When I have airpods connected to my iphone and I try to override the audio to speaker, the audio defaults back to the airpods. I do not get this problem with any other bluetooth device or other audio options. How can I make the speaker output stick when airpods are connected?

Here is how I set up the audio session:

   var err: Error? = nil
            let session = AVAudioSession.sharedInstance()
            do {
                try session.setCategory(AVAudioSession.Category.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .allowBluetoothA2DP, .mixWithOthers])
            } catch {
                NSLog("Unable to change audio category because : \(String(describing: err?.localizedDescription))")
                err = nil
            }

        try? session.setMode(AVAudioSession.Mode.voiceChat)
        if err != nil {
            NSLog("Unable to change audio mode because : \(String(describing: err?.localizedDescription))")
            err = nil
        }
        let sampleRate: Double = 44100.0
        try? session.setPreferredSampleRate(sampleRate)
        if err != nil {
            NSLog("Unable to change preferred sample rate because : \(String(describing: err?.localizedDescription))")
            err = nil
        }
        try? session.setPreferredIOBufferDuration(0.005)
        if err != nil {
            NSLog("Unable to change preferred sample rate because : \(String(describing: err?.localizedDescription))")
            err = nil
        }

Speaker row on the action sheet:

let speakerOutput = UIAlertAction(title: "Speaker", style: .default, handler: {
            (alert: UIAlertAction!) -> Void in

                self.overrideSpeaker(override: true)
        })
        for description in currentRoute.outputs {
            if convertFromAVAudioSessionPort(description.portType) == convertFromAVAudioSessionPort(AVAudioSession.Port.builtInSpeaker){
                speakerOutput.setValue(true, forKey: "checked")
                break
            }
        }
        speakerOutput.setValue(UIImage(named: "ActionSpeaker.png")?.withRenderingMode(.alwaysOriginal), forKey: "image")
        optionMenu.addAction(speakerOutput)

I am changing to speaker here and the bool does come in as true:

func overrideSpeaker(override : Bool) {
        do {
            let port: AVAudioSession.PortOverride = override ? .speaker : .none
            try session.overrideOutputAudioPort(port)
        } catch {
            NSLog("audioSession error toggling speaker: \(error.localizedDescription)")
        }
    }

Here is my route change delegate, I get override first and then newDeviceAvailable:

@objc func handleRouteChange(_ notification: Notification) {
guard let userInfo = notification.userInfo,
    let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
    let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else {
        return
}
switch reason {
case .newDeviceAvailable,
     .categoryChange:
    var audioShown = false


    for output in session.currentRoute.outputs where output.portType != AVAudioSession.Port.builtInReceiver && output.portType != AVAudioSession.Port.builtInSpeaker {
        self.showAudio()
        audioShown = true
        break
    }
    if !audioShown {
        self.showSpeaker()
    }
    break
case .routeConfigurationChange:
    break
case .override:
    break
default: ()
}

}

1

There are 1 best solutions below

0
On

Although it's not the best answer, you could try calling setCategory() before your call to overrideOutputAudioPort() and when you do so, omit the .allowBluetooth option. Then, if they uncheck speaker, you'll have to put it back.

Using the metadata in AVAudioSession.currentRoute.availableInputs, you could limit the use of this logic to only when the user has airpods attached.