Detecting carAudio
How can you detect when the user connects their phone to their car’s stereo? This simple question that came up during a feature brainstorm session proved to have a surprisingly complicated answer. Let’s dive in.
Background on AVAudioSession
Audio on iOS is mostly handled by the OS itself. You communicate your intent to play audio by telling an instance of AVAudioSession
(usually its singleton sharedInstance()
).
Most important for this discussion, are the available audio routes on the shared AVAudioSession
instance. Each audio route is either an input that’s receiving audio (like the phone’s microphone) or an output where we’re sending audio (like the phone’s speaker or a bluetooth headset). We can access the shared AVAudioSession
’s current route, and inspect all of its outputs:
let audioSession = AVAudioSession.sharedInstance() audioSession.currentRoute.outputs.forEach { output in print(output.portName) }
Each output is of type AVAudioSessionPortDescription
. Conveniently, in addition to each port having a name, it also has a type that’s one of these AVAudioSession.Port
values. Among them is a convenient value called carAudio
.
So! This should be easy! Monitor for changes to the ports in the shared audio session, and when we see one of type .carAudio
, then we’re connected to a car!
Not So Fast
Turns out, there are a lot of ways to connect your iPhone to your car’s stereo:
- A lightning-to-USB cable directly to the stereo system
- The lightning-to-aux adapter
- Direct bluetooth connection to the car
- Bluetooth connection to an after-market stereo adapter
- Bluetooth connection to a CarPlay-enabled car
Do these all report as .carAudio
? Can we distinguish connecting to a car via bluetooth from connecting to AirPods? Can we distinguish between a lighting connection to EarPods and a lightning connection to a car?
To answer these questions, we built a little test app and passed it around the office. The app is pretty simple:
- Listen for any changes to the audio route by registering for
AVAudioSession.routeChangeNotification
s - When a change occurs, record the reason for the change
- After each change, look at all the route’s output port descriptions, and record their name & type.
You can experiment with the app yourself by downloading it here.
The Results
Here’s a quick table of our results, showing the type of connection, if it’s actually a connection to a car, and the port’s portType
value:
Connection | Car? | Port Type |
---|---|---|
After-market bluetooth-to-aux device | Yes | .bluetoothA2DP |
Bose Bluetooth headphones | No | .bluetoothA2DP |
AirPods | No | .bluetoothA2DP |
Direct connection to car’s bluetooth | Yes | .bluetoothA2DP |
Lightning-to-headphone adapter + aux | Yes | .headphones |
Lightning EarPods | No | .headphones |
iPhone to Car’s USB port | Yes | .usbAudio |
Screen mirror to Apple TV | No | .airPlay |
Bluetooth to a CarPlay-enabled car | Yes | .carAudio |
We finally saw a .carAudio
value, but only when directly connected to a CarPlay-enabled car. Even cars with native bluetooth support reported the connection as .bluetoothA2DP
— just like any other bluetooth speaker.
However, these bluetooth connections do provide useful data. Each .bluetoothA2DP
connection also provides an identifiable name and a unique ID. Rather than automatically connect to all cars with bluetooth support, we discovered it would be possible for the user to identify a specific bluetooth device (such as their car) they’re already connected to and the app would know whenever the user connects to that device again. That will have to do for now.