Improve USB audio device selection and cleanup lifecycle#1652
Improve USB audio device selection and cleanup lifecycle#1652rahul-lohra merged 4 commits intodevelopfrom
Conversation
AudioSwitch.activate() sets AudioManager.MODE_IN_COMMUNICATION which resets system audio routing, overriding the preferred USB input device set via JavaAudioDeviceModule.setPreferredInputDevice. This caused USB microphones (e.g. Rode Wireless Go) to be detected and selected but audio was still captured from the phone's built-in mic. Re-apply the USB device preference after every AudioSwitch activation: in MicrophoneManager.select() and in the audioDeviceChangeListener. Made-with: Cursor
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
WalkthroughThe PR modifies Changes
Sequence DiagramsequenceDiagram
participant Client as Client Code
participant MicMgr as MicrophoneManager
participant AudioSwitch as AudioSwitch
participant PCF as peerConnectionFactory
Client->>MicMgr: select(usbDevice)
MicMgr->>MicMgr: _selectedDevice = device
MicMgr->>AudioSwitch: activate(device)
AudioSwitch->>AudioSwitch: Apply routing changes
MicMgr->>MicMgr: reapplyUsbDevicePreference()
MicMgr->>PCF: setPreferredAudioInputDevice(deviceInfo)
PCF->>PCF: Restore USB preference
AudioSwitch-->>MicMgr: onAudioDeviceChanged
MicMgr->>MicMgr: Update _devices & _selectedDevice
MicMgr->>MicMgr: reapplyUsbDevicePreference()
MicMgr->>PCF: setPreferredAudioInputDevice(deviceInfo)
PCF->>PCF: Maintain USB preference
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt (1)
950-963:⚠️ Potential issue | 🟡 MinorDispatch
reapplyUsbDevicePreference()to the media scope.AudioSwitch invokes
audioDeviceChangeListenerfrom background threads (per Twilio's AudioSwitch library behavior with BroadcastReceiver and Bluetooth callbacks), not the main thread. CallingsetPreferredAudioInputDevice()→ nativeADM.setPreferredInputDevice()directly from this callback risks thread-safety issues with the underlying WebRTC audio device module.Wrap the call in
mediaManager.scope.launch { reapplyUsbDevicePreference() }to ensure it runs on a consistent thread, matching how camera flip and screen share operations dispatch to the scope.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt` around lines 950 - 963, The audioDeviceChangeListener currently calls reapplyUsbDevicePreference() directly from background threads; change that to dispatch into the MediaManager's coroutine scope (wrap the call in scope.launch { reapplyUsbDevicePreference() } — or mediaManager.scope.launch if used externally) so reapplyUsbDevicePreference() runs on the media coroutine thread, avoiding thread-safety issues with setPreferredAudioInputDevice()/ADM; update the audioDeviceChangeListener block to call scope.launch { reapplyUsbDevicePreference() } instead of invoking reapplyUsbDevicePreference() directly.
🧹 Nitpick comments (1)
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt (1)
806-820: Minor: log verbosity and broken KDoc reference.Two small nits on the new helper:
- The KDoc references
[JavaAudioDeviceModule.setPreferredInputDevice], butJavaAudioDeviceModuleisn't imported in this file, so the doc link won't resolve. Either import it or reference the wrapperStreamPeerConnectionFactory.setPreferredAudioInputDevicethat is actually invoked here.reapplyUsbDevicePreference()is invoked from bothselect()and theaudioDeviceChangeListenercallback (which can fire repeatedly as devices come/go). Logging at.ieach time will be noisy in normal call sessions. Per the guidelines ("Monitor logging verbosity"), consider demoting to.d.♻️ Suggested tweak
private fun reapplyUsbDevicePreference() { val usbDevice = _selectedUsbDevice.value ?: return - logger.i { + logger.d { "[reapplyUsbDevicePreference] Re-applying USB device after AudioSwitch activation: ${usbDevice.name}" } mediaManager.call.peerConnectionFactory.setPreferredAudioInputDevice(usbDevice.deviceInfo) }As per coding guidelines: "Monitor logging verbosity; rely on
StreamVideoImpl.developmentModefor guardrails".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt` around lines 806 - 820, The KDoc and logging are noisy/broken in reapplyUsbDevicePreference: update the doc link to reference the actual API used (StreamPeerConnectionFactory.setPreferredAudioInputDevice) instead of the unresolved JavaAudioDeviceModule.setPreferredInputDevice, and reduce log verbosity by changing logger.i to logger.d (or guard it behind developmentMode) in reapplyUsbDevicePreference which reads _selectedUsbDevice and calls mediaManager.call.peerConnectionFactory.setPreferredAudioInputDevice(…); this fixes the broken KDoc link and prevents excessive info-level logs when select() or audioDeviceChangeListener repeatedly invoke the method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt`:
- Around line 950-963: The audioDeviceChangeListener currently calls
reapplyUsbDevicePreference() directly from background threads; change that to
dispatch into the MediaManager's coroutine scope (wrap the call in scope.launch
{ reapplyUsbDevicePreference() } — or mediaManager.scope.launch if used
externally) so reapplyUsbDevicePreference() runs on the media coroutine thread,
avoiding thread-safety issues with setPreferredAudioInputDevice()/ADM; update
the audioDeviceChangeListener block to call scope.launch {
reapplyUsbDevicePreference() } instead of invoking reapplyUsbDevicePreference()
directly.
---
Nitpick comments:
In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt`:
- Around line 806-820: The KDoc and logging are noisy/broken in
reapplyUsbDevicePreference: update the doc link to reference the actual API used
(StreamPeerConnectionFactory.setPreferredAudioInputDevice) instead of the
unresolved JavaAudioDeviceModule.setPreferredInputDevice, and reduce log
verbosity by changing logger.i to logger.d (or guard it behind developmentMode)
in reapplyUsbDevicePreference which reads _selectedUsbDevice and calls
mediaManager.call.peerConnectionFactory.setPreferredAudioInputDevice(…); this
fixes the broken KDoc link and prevents excessive info-level logs when select()
or audioDeviceChangeListener repeatedly invoke the method.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 73ae9b4d-e232-4c65-9223-968bb0f809b3
📒 Files selected for processing (1)
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt
SDK Size Comparison 📏
|
Early calls to setPreferredAudioInputDevice were silently dropped when the JavaAudioDeviceModule had not yet been created. This stores a pending preference and applies it once the ADM is initialized, and re-applies the preference in onWebRtcAudioRecordStart as a safety net. Made-with: Cursor
…ply mechanism clearUsbDeviceSelection() passed null to the WebRTC ADM's setPreferredInputDevice, which internally calls getId() on the null reference causing an NPE. Since the exception was caught and returned false, _selectedUsbDevice was never reset to null, preventing auto-selection when the device was reconnected. Fix by resetting _selectedUsbDevice directly in clearUsbDeviceSelection() without calling into the ADM — when the USB device is physically removed, Android falls back to default routing automatically. Also remove the reapplyUsbDevicePreference mechanism and associated onAudioRecordStartCallback, as the underlying AudioSwitch routing override issue is no longer applicable with this simplified approach. Made-with: Cursor
|
|
🚀 Available in v1.22.0 |


Goal
Fixes two issues in USB device selection:
Implementation
Fix NPE crash when clearing USB device selection —
clearUsbDeviceSelection()was passingnullto the WebRTC ADM's
setPreferredInputDevice, which internally callsgetId()on the null reference.Now resets state directly since Android handles audio fallback automatically on device removal.
Queue USB mic preference when ADM is not yet initialized — if
setPreferredAudioInputDeviceiscalled before the factory is created, the preference is stored and applied once the ADM becomes available.
Remove unnecessary
@RequiresApi(Build.VERSION_CODES.M)annotations and version gate incleanup()—the
AudioDeviceCallbackAPIs used here are safe without the annotation since the callback is onlyregistered when the API is available.
Testing
[reapplyUsbDevicePreference] Re-applying USB device after AudioSwitch activation: <device name>after each AudioSwitch callback./gradlew :stream-video-android-core:testDebugUnitTest— all tests pass☑️Contributor Checklist
General
developbranchCode & documentation
stream-video-examples)☑️Reviewer Checklist
🎉 GIF
Please provide a suitable gif that describes your work on this pull request
Summary by CodeRabbit
Bug Fixes