Skip to content

fix(core): re-apply USB mic preference after AudioSwitch activation#1652

Draft
PratimMallick wants to merge 2 commits intodevelopfrom
fix/usb-mic-reapply-after-audioswitch
Draft

fix(core): re-apply USB mic preference after AudioSwitch activation#1652
PratimMallick wants to merge 2 commits intodevelopfrom
fix/usb-mic-reapply-after-audioswitch

Conversation

@PratimMallick
Copy link
Copy Markdown
Contributor

@PratimMallick PratimMallick commented Apr 20, 2026

Goal

Fixes an issue where USB microphones (e.g. Rode Wireless Go II) were detected and selected via setPreferredInputDevice, but audio was still captured from the phone's built-in mic

Implementation

Root cause: AudioSwitch's activate() sets AudioManager.MODE_IN_COMMUNICATION, which resets system audio routing and overrides the preferred USB input device

Adds reapplyUsbDevicePreference() in MicrophoneManager that re-applies the USB device preference after every AudioSwitch activation — both in select() and in the audioDeviceChangeListener callback

Testing

  • Connect a USB input-only microphone (e.g. Rode Wireless Go II) to an Android device
  • Join a call and verify logs show [reapplyUsbDevicePreference] Re-applying USB device after AudioSwitch activation: <device name> after each AudioSwitch callback
  • Verify audio is captured from the USB mic, not the built-in phone mic
  • Toggle speakerphone on/off and verify USB mic input is preserved
  • Disconnect the USB mic and verify fallback to built-in mic works correctly
  • Run ./gradlew :stream-video-android-core:testDebugUnitTest — all tests pass

☑️Contributor Checklist

General

  • I have signed the Stream CLA (required)
  • Assigned a person / code owner group (required)
  • Thread with the PR link started in a respective Slack channel (required internally)
  • PR targets the develop branch
  • PR is linked to the GitHub issue it resolves

Code & documentation

  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Comparison screenshots added for visual changes
  • Affected documentation updated (KDocs, docusaurus, tutorial)
  • Tutorial starter kit updated
  • Examples/guides starter kits updated (stream-video-examples)

☑️Reviewer Checklist

  • XML sample runs & works
  • Compose sample runs & works
  • Tutorial starter kit
  • Example starter kits work
  • UI Changes correct (before & after images)
  • Bugs validated (bugfixes)
  • New feature tested and works
  • Release notes and docs clearly describe changes
  • All code we touched has new or updated KDocs
  • Check the SDK Size Comparison table in the CI logs

🎉 GIF

Please provide a suitable gif that describes your work on this pull request

Summary by CodeRabbit

Bug Fixes

  • Fixed an issue where the selected USB audio input device would unexpectedly reset when audio settings or modes changed during an active call. The app now properly maintains your preferred USB input device selection and ensures it remains active consistently, even as different audio configurations are applied or detected.

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
@PratimMallick PratimMallick requested a review from a team as a code owner April 20, 2026 08:29
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 20, 2026

PR checklist ❌

The following issues were detected:

  • Missing required label: at least one label starting with pr:.

What we check

  1. Title is concise (5–18 words) unless labeled pr:ignore-for-release.
  2. At least one pr: label exists (e.g., pr:bug, pr:new-feature).
  3. Sections ### Goal, ### Implementation, and ### Testing contain content.

@PratimMallick PratimMallick marked this pull request as draft April 20, 2026 08:29
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 20, 2026

Walkthrough

The PR modifies MicrophoneManager to re-apply USB audio input device preferences after AudioSwitch routing operations, which previously could override the selected USB device. A new internal method reads the stored USB device preference and resets it via peerConnectionFactory whenever device selection or audio device changes occur.

Changes

Cohort / File(s) Summary
MicrophoneManager USB Device Persistence
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt
Added reapplyUsbDevicePreference() method to restore USB input device preference after AudioSwitch operations. Modified select() and AudioSwitchController callback to invoke USB reapplication, ensuring chosen USB device survives routing mode resets.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A whisker-twitch of audio delight,
USB preferences held oh so tight,
AudioSwitch danced, but now we say nay,
Our chosen device won't slip away! 🎙️

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes the required Goal and Implementation sections with clear explanations, and provides detailed Testing steps. However, multiple contributor checklist items remain unchecked, indicating incomplete compliance. Check and complete the contributor checklist items, particularly signing the CLA, assigning code owners, creating a Slack thread, and updating the changelog and KDocs as required.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change—re-applying USB microphone preference after AudioSwitch activation—which directly addresses the bug fix objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/usb-mic-reapply-after-audioswitch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Dispatch reapplyUsbDevicePreference() to the media scope.

AudioSwitch invokes audioDeviceChangeListener from background threads (per Twilio's AudioSwitch library behavior with BroadcastReceiver and Bluetooth callbacks), not the main thread. Calling setPreferredAudioInputDevice() → native ADM.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:

  1. The KDoc references [JavaAudioDeviceModule.setPreferredInputDevice], but JavaAudioDeviceModule isn't imported in this file, so the doc link won't resolve. Either import it or reference the wrapper StreamPeerConnectionFactory.setPreferredAudioInputDevice that is actually invoked here.
  2. reapplyUsbDevicePreference() is invoked from both select() and the audioDeviceChangeListener callback (which can fire repeatedly as devices come/go). Logging at .i each 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.developmentMode for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7e360ce and 37c5099.

📒 Files selected for processing (1)
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 20, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-video-android-core 12.02 MB 12.02 MB 0.00 MB 🟢
stream-video-android-ui-xml 5.68 MB 5.68 MB 0.00 MB 🟢
stream-video-android-ui-compose 6.28 MB 6.28 MB 0.00 MB 🟢

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
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7.7% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant