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: ()
}
}
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.