Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import android.media.AudioRecord.READ_BLOCKING
import android.media.projection.MediaProjection
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
Expand Down Expand Up @@ -698,7 +697,6 @@ class MicrophoneManager(
*
* Requires Android M (API 23) or higher.
*/
@RequiresApi(Build.VERSION_CODES.M)
fun setupUsbDeviceDetection() {
if (audioDeviceCallback != null) {
logger.d { "[setupUsbDeviceDetection] Already set up" }
Expand Down Expand Up @@ -742,7 +740,6 @@ class MicrophoneManager(
/**
* Updates the list of available USB input devices.
*/
@RequiresApi(Build.VERSION_CODES.M)
private fun updateUsbDeviceList() {
val am = audioManager ?: return

Expand Down Expand Up @@ -773,7 +770,6 @@ class MicrophoneManager(
* @param device The USB device to use, or null to restore default routing.
* @return true if the device was selected successfully, false otherwise.
*/
@RequiresApi(Build.VERSION_CODES.M)
fun selectUsbDevice(device: UsbAudioInputDevice?): Boolean {
logger.i { "[selectUsbDevice] Selecting USB device: ${device?.name}" }

Expand All @@ -793,18 +789,22 @@ class MicrophoneManager(

/**
* Clears the USB device selection, restoring default audio routing.
*
* Resets the state directly rather than going through [selectUsbDevice] with null,
* because the WebRTC ADM's setPreferredInputDevice does not accept null safely.
* When the USB device is physically removed, the system automatically falls back
* to default audio routing, so an explicit ADM call is unnecessary.
*/
@RequiresApi(Build.VERSION_CODES.M)
fun clearUsbDeviceSelection() {
selectUsbDevice(null)
logger.i { "[clearUsbDeviceSelection] Clearing USB device selection" }
_selectedUsbDevice.value = null
}

/**
* Lists all detected USB input devices.
*
* @return StateFlow of available USB input devices.
*/
@RequiresApi(Build.VERSION_CODES.M)
fun listUsbDevices(): StateFlow<List<UsbAudioInputDevice>> {
setupUsbDeviceDetection()
return usbInputDevices
Expand All @@ -813,7 +813,6 @@ class MicrophoneManager(
/**
* Cleans up USB device detection callback.
*/
@RequiresApi(Build.VERSION_CODES.M)
private fun cleanupUsbDeviceDetection() {
audioDeviceCallback?.let { callback ->
audioManager?.unregisterAudioDeviceCallback(callback)
Expand Down Expand Up @@ -881,9 +880,7 @@ class MicrophoneManager(

fun cleanup() {
ifAudioHandlerInitialized { it.stop() }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cleanupUsbDeviceDetection()
}
cleanupUsbDeviceDetection()
setupCompleted.set(false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ public class StreamPeerConnectionFactory(

private var adm: JavaAudioDeviceModule? = null

@Volatile
private var pendingPreferredInputDevice: AudioDeviceInfo? = null

@Volatile
private var hasPendingPreferredDevice = false

private fun createFactory(): PeerConnectionFactory {
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(context)
Expand Down Expand Up @@ -208,6 +214,14 @@ public class StreamPeerConnectionFactory(

adm = initAudioDeviceModule()

if (hasPendingPreferredDevice) {
adm?.setPreferredInputDevice(pendingPreferredInputDevice)
audioLogger.i {
"[createFactory] Applied pending preferred input device: ${pendingPreferredInputDevice?.productName}"
}
hasPendingPreferredDevice = false
}

// Capture the audio bitrate profile when creating the factory
val currentAudioBitrateProfile = audioBitrateProfileProvider?.invoke()
val isMusicHighQuality = currentAudioBitrateProfile ==
Expand Down Expand Up @@ -345,17 +359,23 @@ public class StreamPeerConnectionFactory(
* This allows routing audio input to a specific device, such as a USB microphone
* that may not be detected by AudioSwitch (e.g., Rode Wireless Go II).
*
* Must be called on API 23+ (Android M). On older versions, this is a no-op.
*
* @param deviceInfo The AudioDeviceInfo to use for recording, or null to restore default routing.
* @return true if the preference was set successfully, false otherwise.
*/
@RequiresApi(Build.VERSION_CODES.M)
fun setPreferredAudioInputDevice(deviceInfo: AudioDeviceInfo?): Boolean {
return try {
adm?.setPreferredInputDevice(deviceInfo)
audioLogger.i {
"[setPreferredAudioInputDevice] Set preferred input device: ${deviceInfo?.productName}"
val currentAdm = adm
if (currentAdm != null) {
currentAdm.setPreferredInputDevice(deviceInfo)
audioLogger.i {
"[setPreferredAudioInputDevice] Set preferred input device: ${deviceInfo?.productName}"
}
} else {
pendingPreferredInputDevice = deviceInfo
Comment thread
rahul-lohra marked this conversation as resolved.
hasPendingPreferredDevice = true
audioLogger.w {
"[setPreferredAudioInputDevice] ADM not yet initialized, queuing preference: ${deviceInfo?.productName}"
}
}
true
} catch (e: Exception) {
Expand Down
Loading