1
votes

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
Did you read your own code? You are creating an err variable, but you then never use it... how should the methods you are calling know that they should write errors into err? I recommend you use try-catch.return true
err is irrelevant since it never gets hituser1079052
Of course it never gets hit because you are not using it. Try using try-catch instead.return true
Are you talking about when I am setting up the audio?user1079052
Maybe this answer can help here: stackoverflow.com/questions/53922387/…Dmitry Zhukov

1 Answers

1
votes

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.