Based on our experience, about 30% of audio app support tickets relate to audio routing issues. When a user pulls out headphones, the app should pause; when they reconnect, it should resume. Without proper handling, audio may continue through the speaker or fail to switch to Bluetooth. These problems degrade the user experience. By following our recommendations, you can ensure smooth device switching.
Why automatic audio switching can break
Complexities arise with multiple audio sources, competition with system sounds (calls, navigation), and switching delays. On iOS, after a route change, AVAudioSession.currentRoute updates with up to 100 ms delay. If you don't wait before fetching the new route, you might refer to the old device. On Android, different API versions and manufacturer fragmentation add 100–300 ms latency. In our tests on five devices, 80% of issues stemmed from missing routeChangeNotification or AudioDeviceCallback handling.
How to manage audio routing on iOS
AVAudioSession is the central object. By default, iOS switches the output device on route change, but the app may not know. To take explicit control, subscribe to routeChangeNotification:
NotificationCenter.default.addObserver( self, selector: #selector(handleRouteChange(_:)), name: AVAudioSession.routeChangeNotification, object: nil ) @objc func handleRouteChange(_ notification: Notification) { guard let info = notification.userInfo, let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } switch reason { case .newDeviceAvailable: resumePlaybackIfNeeded() case .oldDeviceUnavailable: if let previousRoute = info[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription { let wasHeadphones = previousRoute.outputs.contains { $0.portType == .headphones || $0.portType == .bluetoothA2DP } if wasHeadphones { pausePlayback() } } case .categoryChange: reconfigureEngine() default: break } } Handling oldDeviceUnavailable with a pause is standard behavior expected by users (Spotify, Apple Music). Without it, audio continues through the speaker after headphone disconnection.
AirPods Automatic Switching and how to respond
AirPods Pro/Max support Automatic Switching between iPhone, iPad, Mac. The app cannot control this but can react. When AirPods switch, the app receives a routeChangeNotification with reason override or categoryChange. After the route changes, the route doesn't update instantly – add a short Task.sleep(nanoseconds: 100_000_000) or check on the next runloop cycle.
Rebuilding the AVAudioEngine graph after route change
If your app uses AVAudioEngine with effects (equalizer, reverb), a route change may reset the session. A sign is AVAudioEngine.isRunning returning false after routeChangeNotification. The correct pattern: subscribe to AVAudioEngineConfigurationChange and reconnect the graph:
NotificationCenter.default.addObserver( forName: .AVAudioEngineConfigurationChange, object: audioEngine, queue: .main ) { [weak self] _ in self?.rebuildAudioGraph() try? self?.audioEngine.start() } rebuildAudioGraph() – detach all nodes, change outputNode (now pointing to the new device), reconnect. Without this step, AVAudioPlayerNode continues playing but silently – no audio, no error logs.
How to manage audio routing on Android
On Android, use AudioManager and AudioDeviceCallback:
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.registerAudioDeviceCallback(object : AudioDeviceCallback() { override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) { val bluetooth = addedDevices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP || it.type == AudioDeviceInfo.TYPE_BLE_HEADSET } bluetooth?.let { switchToDevice(it) } } override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) { pauseIfHeadphonesRemoved(removedDevices) } }, Handler(Looper.getMainLooper())) AudioManager.setPreferredDevice() (API 28+) forces device selection. On Android 12+, setCommunicationDevice() is specifically for calls – do not confuse with regular playback.
Comparison of iOS and Android approaches
On iOS, switching takes 50–100 ms; on Android, 100–300 ms – iOS is roughly twice as fast.
| Aspect | iOS | Android |
|---|---|---|
| Main API | AVAudioSession | AudioManager |
| Route change notification | routeChangeNotification | AudioDeviceCallback |
| Forced device selection | setPreferredInput/output | setPreferredDevice (API 28+) |
| Graph rebuild required | For AVAudioEngine | Not required (MMSRC) |
| Switching latency | 50–100 ms | 100–300 ms (device-dependent) |
Step-by-step guide to automatic audio switching
- Identify the platform: use
AVAudioSessionfor iOS,AudioManagerfor Android. - Subscribe to notifications: iOS –
routeChangeNotification, Android –AudioDeviceCallback. - Handle connection and disconnection scenarios: on new device – resume playback, on removal – pause.
- For iOS with AVAudioEngine: subscribe to
AVAudioEngineConfigurationChangeand rebuild the graph. - Account for incoming calls: on iOS, restore session category after call; on Android, use
setCommunicationDevice. - Test on real devices: check with AirPods, Bluetooth headsets, and in different app states.
Common mistakes and solutions
| Problem | Solution |
|---|---|
| Audio continues through speaker after headphone disconnection | Handle oldDeviceUnavailable and pause |
| Sound goes to another app when AirPods connect | Use AVAudioSessionCategoryPlayback and activate session |
| Audio doesn't return after a call | Restore original session category (iOS) or use setCommunicationDevice (Android) |
| Switching delay on Android | Ensure you use setPreferredDevice and don't block the main thread |
What our implementation includes
We provide:
- Code handling all audio route change scenarios (headphone connect/disconnect, calls, AirPods).
- Integration with
AVAudioEngineorAVAudioPlayeron iOS,AudioManageron Android. - Testing on 5+ devices (different OS versions, headset models).
- Documentation for support and integration into your stack.
- Guaranteed support for 1 month after delivery.
Timelines and cost
Basic route change handling for one platform: 3–5 days. Full implementation with both platforms, all scenarios, and AVAudioEngine graph rebuild: 2–3 weeks. Cost is calculated individually.
Contact us for a consultation – we'll propose the optimal solution within 1 business day. Request a demo version of the integration on your device. Our engineers with 7+ years of experience (over 50 completed projects) in audio apps guarantee stable operation on iOS 13+ and Android 8+. Implementation of our solution reduces support costs by 30%, saving approximately $5000 per year.







