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
0f69a09
update webrtc
santhoshvai Dec 4, 2025
60898a0
init enable stereo method
santhoshvai Dec 4, 2025
ccfa4aa
configure stereo
santhoshvai Dec 5, 2025
e7909ad
add methods to js client
santhoshvai Dec 9, 2025
e63e08e
revert install changes
santhoshvai Dec 9, 2025
df4e576
reset installs of alpha
santhoshvai Dec 9, 2025
dd26b82
Merge remote-tracking branch 'origin/main' into stereo
santhoshvai Dec 10, 2025
08fe6a7
replace with global this
santhoshvai Dec 10, 2025
3197339
wip - updating speaker override
santhoshvai Dec 19, 2025
0fe58f1
Merge remote-tracking branch 'origin/main' into stereo
santhoshvai Dec 29, 2025
95ff896
Merge remote-tracking branch 'origin/main' into stereo
santhoshvai Jan 2, 2026
7ff537d
finalise ios config
santhoshvai Jan 9, 2026
b011140
update client
santhoshvai Jan 11, 2026
ae879c0
review fixes
santhoshvai Jan 11, 2026
2dd5d68
remove default callManager usages in dogfood
santhoshvai Jan 11, 2026
d8f0045
code-rabbit review fixes
santhoshvai Jan 12, 2026
cc38180
code-rabbit review fixes
santhoshvai Jan 12, 2026
aed8889
code-rabbit review fixes
santhoshvai Jan 12, 2026
57e0fe0
revert to defaults upon stop
santhoshvai Jan 12, 2026
5bd920a
code-rabbit review fixes
santhoshvai Jan 12, 2026
9e25527
fix package json webrtc alpha
santhoshvai Jan 12, 2026
265abc5
revert krisp updates
santhoshvai Jan 19, 2026
8f93d09
fix: restore recording and playing status
santhoshvai Jan 19, 2026
e4cc7c4
pod update with new alpha
santhoshvai Jan 19, 2026
065b19d
removed duplicate global declaration
santhoshvai Jan 20, 2026
5325474
configure the debounce constant
santhoshvai Jan 20, 2026
30441b7
update to stable
santhoshvai Jan 20, 2026
05eea5d
Merge remote-tracking branch 'origin/main' into stereo
santhoshvai Jan 20, 2026
41788a1
fix: serialize stereo refresh debounce scheduling
santhoshvai Jan 20, 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
6 changes: 6 additions & 0 deletions packages/client/src/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,8 @@ export class Call {
this.cancelAutoDrop();
this.clientStore.unregisterCall(this);

globalThis.streamRNVideoSDK?.callManager.stop();

this.camera.dispose();
this.microphone.dispose();
this.screenShare.dispose();
Expand Down Expand Up @@ -1115,6 +1117,7 @@ export class Call {
// re-apply them on later reconnections or server-side data fetches
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
await this.applyDeviceConfig(this.state.settings, true);
globalThis.streamRNVideoSDK?.callManager.start();
this.deviceSettingsAppliedOnce = true;
}

Expand Down Expand Up @@ -2691,6 +2694,9 @@ export class Call {
settings: CallSettingsResponse,
publish: boolean,
) => {
globalThis.streamRNVideoSDK?.callManager.setup({
default_device: settings.audio.default_device,
});
await this.camera.apply(settings.video, publish).catch((err) => {
this.logger.warn('Camera init failed', err);
});
Expand Down
2 changes: 0 additions & 2 deletions packages/client/src/devices/SpeakerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@ export class SpeakerManager {
/**
* Set the volume of a participant.
*
* Note: This method is not supported in React Native.
*
* @param sessionId the participant's session id.
* @param volume a number between 0 and 1. Set it to `undefined` to use the default volume.
*/
Expand Down
28 changes: 28 additions & 0 deletions packages/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
MemberResponse,
OwnCapability,
ReactionResponse,
AudioSettingsRequestDefaultDeviceEnum,
StartRecordingRequest,
StartRecordingResponse,
} from './gen/coordinator';
Expand Down Expand Up @@ -344,6 +345,33 @@ export type StartCallRecordingFnType = {
): Promise<StartRecordingResponse>;
};

export type StreamRNVideoSDKGlobals = {
callManager: {
/**
* Sets up the in call manager.
*/
setup({
default_device,
}: {
default_device: AudioSettingsRequestDefaultDeviceEnum;
}): void;

/**
* Starts the in call manager.
*/
start(): void;

/**
* Stops the in call manager.
*/
stop(): void;
};
};

declare global {
var streamRNVideoSDK: StreamRNVideoSDKGlobals | undefined;
}

/**
* The options to pass to {@link Call.join} method.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/noise-cancellation-react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"homepage": "https://github.com/GetStream/stream-video-js#readme",
"devDependencies": {
"@stream-io/react-native-webrtc": "137.0.2",
"@stream-io/react-native-webrtc": "137.1.0",
"react": "19.1.0",
"react-native": "^0.81.5",
"react-native-builder-bob": "^0.37.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import com.streamvideo.reactnative.audio.utils.AudioDeviceEndpointUtils
import com.streamvideo.reactnative.audio.utils.AudioFocusUtil
import com.streamvideo.reactnative.audio.utils.AudioManagerUtil
import com.streamvideo.reactnative.audio.utils.AudioManagerUtil.Companion.getAvailableAudioDevices
import com.streamvideo.reactnative.audio.utils.AudioSetupStoreUtil
import com.streamvideo.reactnative.audio.utils.CallAudioRole
import com.streamvideo.reactnative.callmanager.ProximityManager
import com.streamvideo.reactnative.callmanager.StreamInCallManagerModule
Expand Down Expand Up @@ -90,6 +89,8 @@ class AudioDeviceManager(
@EndpointType
private var userSelectedAudioDevice: Int? = null

var enableStereo: Boolean = false

private val mAudioManager =
mReactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager

Expand All @@ -99,7 +100,6 @@ class AudioDeviceManager(
private var audioFocusLost = false

private var audioFocusUtil = AudioFocusUtil(mAudioManager, this)
private var audioSetupStoreUtil = AudioSetupStoreUtil(mReactContext, mAudioManager, this)

var callAudioRole: CallAudioRole = CallAudioRole.Communicator

Expand All @@ -113,44 +113,52 @@ class AudioDeviceManager(
mAudioManager.registerAudioDeviceCallback(this, null)
}

fun setup() {
if (callAudioRole == CallAudioRole.Communicator) {
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
} else {
// Audio routing is handled automatically by the system in normal media mode
// and bluetooth microphones may not work on some devices.
mAudioManager.mode = AudioManager.MODE_NORMAL
}
audioFocusUtil.setup(callAudioRole, mReactContext)
}

fun start(activity: Activity) {
runInAudioThread {
setup()
userSelectedAudioDevice = null
selectedAudioDeviceEndpoint = null
audioSetupStoreUtil.storeOriginalAudioSetup()
if (callAudioRole == CallAudioRole.Communicator) {
// Audio routing is manually controlled by the SDK in communication media mode
// and local microphone can be published
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL
bluetoothManager.start()
mAudioManager.registerAudioDeviceCallback(this, null)
updateAudioDeviceState()
proximityManager.start()
} else {
// Audio routing is handled automatically by the system in normal media mode
// and bluetooth microphones may not work on some devices.
mAudioManager.mode = AudioManager.MODE_NORMAL
activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
}

audioSetupStoreUtil.storeOriginalAudioSetup()
audioFocusUtil.requestFocus(callAudioRole, mReactContext)
}
}

fun stop() {
fun stop(activity: Activity) {
runInAudioThread {
if (callAudioRole == CallAudioRole.Communicator) {
if (Build.VERSION.SDK_INT >= 31) {
mAudioManager.clearCommunicationDevice()
} else {
mAudioManager.setSpeakerphoneOn(false)
}
callAudioRole = CallAudioRole.Communicator
enableStereo = false
defaultAudioDevice = AudioDeviceEndpoint.TYPE_SPEAKER
bluetoothManager.stop()
proximityManager.stop()
}
audioSetupStoreUtil.restoreOriginalAudioSetup()
activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
audioFocusUtil.abandonFocus()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import androidx.annotation.RequiresApi
import com.facebook.react.bridge.ReactContext
import com.oney.WebRTCModule.WebRTCModule
import org.webrtc.audio.JavaAudioDeviceModule
Expand All @@ -25,18 +26,31 @@ class AudioFocusUtil(
private lateinit var request: AudioFocusRequest


@RequiresApi(26)
private fun getAudioAttributes(mode: CallAudioRole): AudioAttributes {
return AudioAttributes.Builder()
.setUsage(if (mode == CallAudioRole.Communicator) AudioAttributes.USAGE_VOICE_COMMUNICATION else AudioAttributes.USAGE_MEDIA)
.setContentType(if (mode == CallAudioRole.Communicator) AudioAttributes.CONTENT_TYPE_SPEECH else AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
}

private fun setup(audioAttributes: AudioAttributes, reactContext: ReactContext) {
val webRTCModule = reactContext.getNativeModule(WebRTCModule::class.java)!!
val adm = webRTCModule.audioDeviceModule as JavaAudioDeviceModule
WebRtcAudioTrackHelper.setAudioOutputAttributes(adm, audioAttributes)
}

fun setup(mode: CallAudioRole, reactContext: ReactContext) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val audioAttributes = getAudioAttributes(mode)
setup(audioAttributes, reactContext)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

fun requestFocus(mode: CallAudioRole, reactContext: ReactContext) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(if (mode == CallAudioRole.Communicator) AudioAttributes.USAGE_VOICE_COMMUNICATION else AudioAttributes.USAGE_MEDIA)
.setContentType(if (mode == CallAudioRole.Communicator) AudioAttributes.CONTENT_TYPE_SPEECH else AudioAttributes.CONTENT_TYPE_MUSIC)
.build()

// 1. set audio attributes to webrtc
val webRTCModule = reactContext.getNativeModule(WebRTCModule::class.java)!!
val adm = webRTCModule.audioDeviceModule as JavaAudioDeviceModule
WebRtcAudioTrackHelper.setAudioOutputAttributes(adm, audioAttributes)
val audioAttributes = getAudioAttributes(mode)
setup(audioAttributes, reactContext)

// 2. request the audio focus with the audio attributes
request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
}
}

@ReactMethod
fun setEnableStereoAudioOutput(enabled: Boolean) {
AudioDeviceManager.runInAudioThread {
if (audioManagerActivated) {
Log.e(TAG, "setEnableStereoAudioOutput(): AudioManager is already activated and so enabling stereo audio output cannot be changed")
return@runInAudioThread
}
mAudioDeviceManager.enableStereo = enabled
}
}
Comment thread
santhoshvai marked this conversation as resolved.

@ReactMethod
fun setup() {
AudioDeviceManager.runInAudioThread {
mAudioDeviceManager.setup()
}
}
Comment thread
santhoshvai marked this conversation as resolved.

@ReactMethod
fun start() {
AudioDeviceManager.runInAudioThread {
Expand All @@ -99,10 +117,12 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
AudioDeviceManager.runInAudioThread {
if (audioManagerActivated) {
Log.d(TAG, "stop() mAudioDeviceManager")
mAudioDeviceManager.stop()
reactApplicationContext.currentActivity?.let {
mAudioDeviceManager.stop(it)
audioManagerActivated = false
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
setMicrophoneMute(false)
setKeepScreenOn(false)
audioManagerActivated = false
}
}
}
Expand All @@ -124,11 +144,16 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
@Suppress("unused")
@ReactMethod
fun setForceSpeakerphoneOn(enable: Boolean) {
if (mAudioDeviceManager.callAudioRole !== CallAudioRole.Communicator) {
Log.e(TAG, "setForceSpeakerphoneOn() is not supported when audio role is not Communicator")
return
AudioDeviceManager.runInAudioThread {
if (mAudioDeviceManager.callAudioRole !== CallAudioRole.Communicator) {
Log.e(
TAG,
"setForceSpeakerphoneOn() is not supported when audio role is not Communicator"
)
return@runInAudioThread
}
mAudioDeviceManager.setSpeakerphoneOn(enable)
}
mAudioDeviceManager.setSpeakerphoneOn(enable)
}

@ReactMethod
Expand All @@ -152,23 +177,32 @@ class StreamInCallManagerModule(reactContext: ReactApplicationContext) :
@Suppress("unused")
@ReactMethod
fun chooseAudioDeviceEndpoint(endpointDeviceName: String) {
if (mAudioDeviceManager.callAudioRole !== CallAudioRole.Communicator) {
Log.e(TAG, "chooseAudioDeviceEndpoint() is not supported when audio role is not Communicator")
return
AudioDeviceManager.runInAudioThread {
if (mAudioDeviceManager.callAudioRole !== CallAudioRole.Communicator) {
Log.e(
TAG,
"chooseAudioDeviceEndpoint() is not supported when audio role is not Communicator"
)
return@runInAudioThread
}
mAudioDeviceManager.switchDeviceFromDeviceName(
endpointDeviceName
)
}
mAudioDeviceManager.switchDeviceFromDeviceName(
endpointDeviceName
)
}

@ReactMethod
fun muteAudioOutput() {
mAudioDeviceManager.muteAudioOutput()
AudioDeviceManager.runInAudioThread {
mAudioDeviceManager.muteAudioOutput()
}
}

@ReactMethod
fun unmuteAudioOutput() {
mAudioDeviceManager.unmuteAudioOutput()
AudioDeviceManager.runInAudioThread {
mAudioDeviceManager.unmuteAudioOutput()
}
}


Expand Down
4 changes: 4 additions & 0 deletions packages/react-native-sdk/ios/StreamInCallManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ @interface RCT_EXTERN_MODULE(StreamInCallManager, RCTEventEmitter)

RCT_EXTERN_METHOD(setDefaultAudioDeviceEndpointType:(NSString *)endpointType)

RCT_EXTERN_METHOD(setEnableStereoAudioOutput:(BOOL)enable)

RCT_EXTERN_METHOD(setup)

RCT_EXTERN_METHOD(start)

RCT_EXTERN_METHOD(stop)
Expand Down
Loading
Loading