Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1b7340a
Migrated from twilio's audioswitch to in-house implemenation
PratimMallick Dec 9, 2025
4117310
fix unit tests
PratimMallick Dec 15, 2025
e766ee4
Merge branch 'develop' of github.com:GetStream/stream-video-android i…
PratimMallick Dec 15, 2025
4c6b87a
fix unit tests
PratimMallick Dec 17, 2025
4b0850e
Add missing APIs for CustomAudioDevice
PratimMallick Dec 17, 2025
7a9db52
Remove un-necessary logs
PratimMallick Dec 17, 2025
1dceba7
spotless apply and api dump
PratimMallick Dec 17, 2025
c0d1fad
Merge branch 'develop' of github.com:GetStream/stream-video-android i…
PratimMallick Dec 22, 2025
4470d33
Removed the extra enforcing of setup done in selecting device
PratimMallick Dec 22, 2025
68729cd
Fix test comment
PratimMallick Dec 22, 2025
9285dc9
Remove customAudioDevice and use StreamAudioDevice
PratimMallick Dec 23, 2025
381e5f5
spotless
PratimMallick Dec 23, 2025
03290e1
api dump
PratimMallick Dec 23, 2025
9c38908
Deprecated audio
PratimMallick Dec 23, 2025
92a4f54
use audioDeviceInfo for checking the selected UI
PratimMallick Dec 23, 2025
ba59411
Add unit tests
PratimMallick Dec 24, 2025
a57c887
Merge branch 'develop' of github.com:GetStream/stream-video-android i…
PratimMallick Dec 24, 2025
f22d2dc
Removed Twilio audioSwitch library
PratimMallick Jan 8, 2026
ba32a9d
Merge branch 'develop-v2' into audio_switch_replacement_same_api
aleksandar-apostolov Jan 20, 2026
5a6f039
Reset isAudioSwitchInitScheduled to false when stop is called
PratimMallick Jan 28, 2026
863a815
Removed all BLUETOOTH_CONNECT references across code, tests, and mani…
PratimMallick Jan 28, 2026
b11c273
setCommunicationDevice can return true or false. Expose that to the c…
PratimMallick Jan 28, 2026
2cfa4a9
use unused `isSelected` variable to send `highlight` state to AudioDe…
PratimMallick Jan 28, 2026
244b3c7
spotless apply
PratimMallick Jan 28, 2026
92a65c1
Merge remote-tracking branch 'origin/audio_switch_replacement_same_ap…
PratimMallick Jan 28, 2026
6c133a9
wired MODE_IN_COMMUNICATION into StreamAudioSwitch.start() and restor…
PratimMallick Jan 29, 2026
98c8a6c
Updated the matcher to do ID comparison only for Bluetooth, and type-…
PratimMallick Jan 29, 2026
1d6929f
Fixed the bug of clearing device always
PratimMallick Jan 29, 2026
25fce4d
Added selected audio device data in sfu traces
PratimMallick Jan 29, 2026
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
2 changes: 0 additions & 2 deletions demo-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,6 @@ dependencies {
// Http
implementation(libs.okhttp)

implementation(libs.audioswitch)

// Logging
implementation(libs.okhttp.logging)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package io.getstream.video.android.tests

import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.BLUETOOTH_ADVERTISE
import android.Manifest.permission.BLUETOOTH_CONNECT
import android.Manifest.permission.BLUETOOTH_SCAN
import android.Manifest.permission.CAMERA
import android.Manifest.permission.POST_NOTIFICATIONS
Expand Down Expand Up @@ -80,7 +79,6 @@ abstract class StreamTestCase {
ACCESS_FINE_LOCATION,
BLUETOOTH_ADVERTISE,
BLUETOOTH_SCAN,
BLUETOOTH_CONNECT,
)
for (permission in permissions) {
device.grantPermission(permission)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,45 @@ internal fun SettingsMenu(
is StreamAudioDevice.Speakerphone -> Icons.Default.SpeakerPhone
is StreamAudioDevice.WiredHeadset -> Icons.Default.HeadsetMic
}
// Compare devices by type and audioDeviceInfo ID (if available) since audio can be null when using custom audio switch
val selected = selectedMicroPhoneDevice
val isSelected = when {
selected == null -> false
else -> {
// First try to compare by audioDeviceInfo ID if both have it
val itInfoId = when (it) {
is StreamAudioDevice.BluetoothHeadset -> it.audioDeviceInfo?.id
is StreamAudioDevice.WiredHeadset -> it.audioDeviceInfo?.id
is StreamAudioDevice.Earpiece -> it.audioDeviceInfo?.id
is StreamAudioDevice.Speakerphone -> it.audioDeviceInfo?.id
}
val selectedInfoId = when (selected) {
is StreamAudioDevice.BluetoothHeadset -> selected.audioDeviceInfo?.id
is StreamAudioDevice.WiredHeadset -> selected.audioDeviceInfo?.id
is StreamAudioDevice.Earpiece -> selected.audioDeviceInfo?.id
is StreamAudioDevice.Speakerphone -> selected.audioDeviceInfo?.id
}

if (itInfoId != null && selectedInfoId != null) {
// Both have audioDeviceInfo, compare by ID
itInfoId == selectedInfoId
} else {
// Fall back to type comparison
when {
it is StreamAudioDevice.BluetoothHeadset && selected is StreamAudioDevice.BluetoothHeadset -> true
it is StreamAudioDevice.WiredHeadset && selected is StreamAudioDevice.WiredHeadset -> true
it is StreamAudioDevice.Earpiece && selected is StreamAudioDevice.Earpiece -> true
it is StreamAudioDevice.Speakerphone && selected is StreamAudioDevice.Speakerphone -> true
else -> false
}
}
}
}
AudioDeviceUiState(
it,
it.name,
icon,
it.audio.name == selectedMicroPhoneDevice?.audio?.name,
isSelected,
)
}

Expand Down
3 changes: 0 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ coil = "2.6.0"
landscapist = "2.4.2"
accompanist = "0.34.0"
telephoto = "0.3.0"
audioswitch = "1.2.0"
libyuv = "0.36.0"

wire = "4.7.0"
Expand Down Expand Up @@ -139,8 +138,6 @@ accompanist-permission = { group = "com.google.accompanist", name = "accompanist
stream-video-android-noise-cancellation = { module = "io.getstream:stream-video-android-noise-cancellation", version.ref = "streamNoiseCancellation" }
telephoto = { group = "me.saket.telephoto", name = "zoomable", version.ref = "telephoto" }

audioswitch = { group = "com.twilio", name = "audioswitch", version.ref = "audioswitch"}

libyuv = { group = "io.github.crow-misia.libyuv", name = "libyuv-android", version.ref = "libyuv"}

wire-runtime = { group = "com.squareup.wire", name = "wire-runtime", version.ref = "wire" }
Expand Down
85 changes: 34 additions & 51 deletions stream-video-android-core/api/stream-video-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -8503,85 +8503,79 @@ public abstract interface class io/getstream/video/android/core/api/SignalServer
}

public abstract interface class io/getstream/video/android/core/audio/AudioHandler {
public abstract fun selectDevice (Lio/getstream/video/android/core/audio/StreamAudioDevice;)V
public abstract fun start ()V
public abstract fun stop ()V
}

public final class io/getstream/video/android/core/audio/AudioSwitchHandler : io/getstream/video/android/core/audio/AudioHandler {
public static final field Companion Lio/getstream/video/android/core/audio/AudioSwitchHandler$Companion;
public fun <init> (Landroid/content/Context;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V
public final fun selectDevice (Lcom/twilio/audioswitch/AudioDevice;)V
public fun start ()V
public fun stop ()V
}

public final class io/getstream/video/android/core/audio/AudioSwitchHandler$Companion {
}

public abstract class io/getstream/video/android/core/audio/StreamAudioDevice {
public static final field Companion Lio/getstream/video/android/core/audio/StreamAudioDevice$Companion;
public static final fun fromAudio (Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/audio/StreamAudioDevice;
public abstract fun getAudio ()Lcom/twilio/audioswitch/AudioDevice;
public static final fun fromAudioDeviceInfo (Landroid/media/AudioDeviceInfo;)Lio/getstream/video/android/core/audio/StreamAudioDevice;
Copy link
Contributor

Choose a reason for hiding this comment

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

I can see several breaking changes. If we have decided to move forward with this approach, I suggest we draft a migration document to help developers transition to the newer APIs.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup we have breaking changes, and thats why we are making this part of the major version. Will add a migration doc for this. s

public abstract fun getAudioDeviceInfo ()Landroid/media/AudioDeviceInfo;
public abstract fun getName ()Ljava/lang/String;
public static final fun toAudioDevice (Lio/getstream/video/android/core/audio/StreamAudioDevice;)Lcom/twilio/audioswitch/AudioDevice;
public static final fun toAudioDeviceInfo (Lio/getstream/video/android/core/audio/StreamAudioDevice;Landroid/media/AudioManager;)Landroid/media/AudioDeviceInfo;
}

public final class io/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset : io/getstream/video/android/core/audio/StreamAudioDevice {
public fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> ()V
public fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)V
public synthetic fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/twilio/audioswitch/AudioDevice;
public final fun copy (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset;Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset;
public final fun component2 ()Landroid/media/AudioDeviceInfo;
public final fun copy (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)Lio/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset;Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$BluetoothHeadset;
public fun equals (Ljava/lang/Object;)Z
public fun getAudio ()Lcom/twilio/audioswitch/AudioDevice;
public fun getAudioDeviceInfo ()Landroid/media/AudioDeviceInfo;
public fun getName ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/audio/StreamAudioDevice$Companion {
public final fun fromAudio (Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/audio/StreamAudioDevice;
public final fun toAudioDevice (Lio/getstream/video/android/core/audio/StreamAudioDevice;)Lcom/twilio/audioswitch/AudioDevice;
public final fun fromAudioDeviceInfo (Landroid/media/AudioDeviceInfo;)Lio/getstream/video/android/core/audio/StreamAudioDevice;
public final fun toAudioDeviceInfo (Lio/getstream/video/android/core/audio/StreamAudioDevice;Landroid/media/AudioManager;)Landroid/media/AudioDeviceInfo;
}

public final class io/getstream/video/android/core/audio/StreamAudioDevice$Earpiece : io/getstream/video/android/core/audio/StreamAudioDevice {
public fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> ()V
public fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)V
public synthetic fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/twilio/audioswitch/AudioDevice;
public final fun copy (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Earpiece;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$Earpiece;Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Earpiece;
public final fun component2 ()Landroid/media/AudioDeviceInfo;
public final fun copy (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Earpiece;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$Earpiece;Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Earpiece;
public fun equals (Ljava/lang/Object;)Z
public fun getAudio ()Lcom/twilio/audioswitch/AudioDevice;
public fun getAudioDeviceInfo ()Landroid/media/AudioDeviceInfo;
public fun getName ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone : io/getstream/video/android/core/audio/StreamAudioDevice {
public fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> ()V
public fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)V
public synthetic fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/twilio/audioswitch/AudioDevice;
public final fun copy (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone;Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone;
public final fun component2 ()Landroid/media/AudioDeviceInfo;
public final fun copy (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone;Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$Speakerphone;
public fun equals (Ljava/lang/Object;)Z
public fun getAudio ()Lcom/twilio/audioswitch/AudioDevice;
public fun getAudioDeviceInfo ()Landroid/media/AudioDeviceInfo;
public fun getName ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset : io/getstream/video/android/core/audio/StreamAudioDevice {
public fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> ()V
public fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)V
public synthetic fun <init> (Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/twilio/audioswitch/AudioDevice;
public final fun copy (Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset;Ljava/lang/String;Lcom/twilio/audioswitch/AudioDevice;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset;
public final fun component2 ()Landroid/media/AudioDeviceInfo;
public final fun copy (Ljava/lang/String;Landroid/media/AudioDeviceInfo;)Lio/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset;
public static synthetic fun copy$default (Lio/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset;Ljava/lang/String;Landroid/media/AudioDeviceInfo;ILjava/lang/Object;)Lio/getstream/video/android/core/audio/StreamAudioDevice$WiredHeadset;
public fun equals (Ljava/lang/Object;)Z
public fun getAudio ()Lcom/twilio/audioswitch/AudioDevice;
public fun getAudioDeviceInfo ()Landroid/media/AudioDeviceInfo;
public fun getName ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
Expand Down Expand Up @@ -8812,17 +8806,6 @@ public final class io/getstream/video/android/core/call/state/Reaction : io/gets
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/call/state/SelectAudioDevice : io/getstream/video/android/core/call/state/CallAction {
public fun <init> (Lcom/twilio/audioswitch/AudioDevice;)V
public final fun component1 ()Lcom/twilio/audioswitch/AudioDevice;
public final fun copy (Lcom/twilio/audioswitch/AudioDevice;)Lio/getstream/video/android/core/call/state/SelectAudioDevice;
public static synthetic fun copy$default (Lio/getstream/video/android/core/call/state/SelectAudioDevice;Lcom/twilio/audioswitch/AudioDevice;ILjava/lang/Object;)Lio/getstream/video/android/core/call/state/SelectAudioDevice;
public fun equals (Ljava/lang/Object;)Z
public final fun getAudioDevice ()Lcom/twilio/audioswitch/AudioDevice;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/call/state/Settings : io/getstream/video/android/core/call/state/CallAction {
public fun <init> (Z)V
public final fun component1 ()Z
Expand Down
2 changes: 0 additions & 2 deletions stream-video-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ dependencies {
// webrtc
api(libs.stream.webrtc)

implementation(libs.audioswitch)

// video filter dependencies
implementation(libs.libyuv)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class MediaManagerTest : IntegrationTestBase(connectCoordinatorWS = false) {
var runtimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.BLUETOOTH_CONNECT,
)

@Before
Expand Down
3 changes: 2 additions & 1 deletion stream-video-android-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Bluetooth permission for API < 31 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,13 @@ public class Call(
testInstanceProvider.mediaManagerCreator!!.invoke()
} else {
MediaManagerImpl(
clientImpl.context,
this,
scope,
eglBase.eglBaseContext,
clientImpl.callServiceConfigRegistry.get(type).audioUsage,
) { clientImpl.callServiceConfigRegistry.get(type).audioUsage }
context = clientImpl.context,
call = this,
scope = scope,
eglBaseContext = eglBase.eglBaseContext,
audioUsage = clientImpl.callServiceConfigRegistry.get(type).audioUsage,
audioUsageProvider = { clientImpl.callServiceConfigRegistry.get(type).audioUsage },
)
}
}

Expand Down Expand Up @@ -1351,7 +1352,9 @@ public class Call(
private fun monitorHeadset() {
microphone.devices.onEach { availableDevices ->
logger.d {
"[monitorHeadset] new available devices, prev selected: ${microphone.nonHeadsetFallbackDevice}"
"[monitorHeadset] new available devices, prev selected: ${
microphone.nonHeadsetFallbackDevice
}"
}

val bluetoothHeadset =
Expand Down
Loading
Loading