This question is a broader description of the bug documented here. I'm reproducing the steps below on iPhone 8 and iOS 14.7.
How can I achieve my AVAudioSession
and AVAudioEngine
instances to work properly, even when I disconnect a microphone after setting it as a preferred input, since Route Change Notifications do not work in the situation above?
When there are multiple audio inputs available to the iOS, I can set the active one with .setPreferredInput(...)
.
When .setPreferredInput(...)
is called, both the preferredInput
and the active input given by currentRoute
are set to the requested input/microphone.
Also, I can subscribe to route change, audio interruption and OS Media Reset/Lost notifications given by the OS - this communication is managed by AVAudioSession
- . These notifications work properly when I connect and disconnect IO audio devices, like wired earphones, wired EarPods, bluetooth headsets and so on. These notifications also work properly while I have a running AVAudioEngine
in my application, with input and output nodes capturing and reproducing audio while these notifications are fired.
All the considerations here are being reproduced with AVAudioEngine
category set to .playAndRecord
and with the AVAudioEngine
running as: inputNode->mainMixerNode->outputNode
.
However, a particular situation arrives when I select a microphone to be active, and then I disconnect this mic. This is reproduced by calling .setPreferredInput(...)
and unplugging the mic.
In the situation mentioned above, the route change notification is not called. Instead, the OS Media Reset/Lost events MAY be called in a range from some milliseconds, to 6 seconds, or even not being fired at all. While in this "empty" route state, the active input is preempted by the OS to be nil.
The uncertainty of the mentioned situation is a problem, because I cannot find a suited and secure solution for these steps above when they are reproduced. The user may, for example, ask for an audio node to play when I have a player node output connected to the main mixer node input. When this happens, It crashes with the message: player did not see an IO cycle.
(mentioned here). I attempted some workarounds, by double-checking if the active input is not nil in every critical action. Attempting to overwrite routes in the "idle" state between unplugging and OS Media Lost/Reset being fired also crashes the app.
Also, notice that, EarPods are plugged while both the AVAudioEngine
is running and the AVAudioSession
instance is active, the EarPods are automatically set as the active input/output WITHOUT calling .setPreferredInput(...)
, since it's the default OS behavior, which can be tracked by Route Change notifications. When the EarPods are unplugged in the following this situation, the OS gracefully handles it by firing .oldDeviceUnavailable
, and both the AVAudioSession
and AVAudioEngine
instances automatically follows to work with the built-in mic/speaker. It doesn't happen when .setPreferredInput(...)
is called before unplugging the EarPods.
My sample application is here.