From 5bf8198dafdf4274bdacada9680a8aeb9b80f87c Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Tue, 14 Apr 2026 10:55:53 +0200 Subject: [PATCH 1/3] feat(rn): remove peer connection usage in speech detection --- packages/client/index.ts | 1 - .../client/src/devices/MicrophoneManager.ts | 14 +- .../__tests__/MicrophoneManagerRN.test.ts | 57 +++-- .../client/src/helpers/RNSpeechDetector.ts | 224 ------------------ .../__tests__/RNSpeechDetector.test.ts | 52 ---- packages/client/src/types.ts | 9 + packages/react-native-sdk/CLAUDE.md | 1 - packages/react-native-sdk/package.json | 2 +- packages/react-native-sdk/src/hooks/index.ts | 1 - .../src/hooks/useSpeechDetection.ts | 35 --- .../src/utils/internal/registerSDKGlobals.ts | 13 + .../react-native/dogfood/ios/Podfile.lock | 20 +- sample-apps/react-native/dogfood/package.json | 2 +- yarn.lock | 17 +- 14 files changed, 86 insertions(+), 362 deletions(-) delete mode 100644 packages/client/src/helpers/RNSpeechDetector.ts delete mode 100644 packages/client/src/helpers/__tests__/RNSpeechDetector.test.ts delete mode 100644 packages/react-native-sdk/src/hooks/useSpeechDetection.ts diff --git a/packages/client/index.ts b/packages/client/index.ts index aad592a887..55f9b6d1d1 100644 --- a/packages/client/index.ts +++ b/packages/client/index.ts @@ -23,7 +23,6 @@ export * from './src/helpers/DynascaleManager'; export * from './src/helpers/ViewportTracker'; export * from './src/helpers/sound-detector'; export * from './src/helpers/participantUtils'; -export * from './src/helpers/RNSpeechDetector'; export * as Browsers from './src/helpers/browsers'; export * from './src/logger'; diff --git a/packages/client/src/devices/MicrophoneManager.ts b/packages/client/src/devices/MicrophoneManager.ts index 318cf91244..70e7a68b44 100644 --- a/packages/client/src/devices/MicrophoneManager.ts +++ b/packages/client/src/devices/MicrophoneManager.ts @@ -24,7 +24,6 @@ import { createSafeAsyncSubscription, createSubscription, } from '../store/rxUtils'; -import { RNSpeechDetector } from '../helpers/RNSpeechDetector'; import { withoutConcurrency } from '../helpers/concurrency'; import { disposeOfMediaStream } from './utils'; import { promiseWithResolvers } from '../helpers/promise'; @@ -36,7 +35,6 @@ export class MicrophoneManager extends AudioDeviceManager Promise; private soundDetectorDeviceId?: string; private noAudioDetectorCleanup?: () => Promise; - private rnSpeechDetector: RNSpeechDetector | undefined; private noiseCancellation: INoiseCancellation | undefined; private noiseCancellationChangeUnsubscribe: (() => void) | undefined; private noiseCancellationRegistration?: Promise; @@ -422,13 +420,19 @@ export class MicrophoneManager extends AudioDeviceManager { + const speechActivity = + globalThis.streamRNVideoSDK?.nativeEvents?.speechActivity; + if (!speechActivity) { + this.logger.warn( + 'Native speech activity not available, make sure the "@stream-io/react-native-webrtc" peer dependency version is satisfied', + ); + return; + } + const unsubscribe = speechActivity.subscribe((event) => { this.state.setSpeakingWhileMuted(event.isSoundDetected); }); this.soundDetectorCleanup = async () => { unsubscribe(); - this.rnSpeechDetector = undefined; }; } else { // Need to start a new stream that's not connected to publisher diff --git a/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts b/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts index b947829de1..5805f4fcac 100644 --- a/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts +++ b/packages/client/src/devices/__tests__/MicrophoneManagerRN.test.ts @@ -12,11 +12,12 @@ import { import { of } from 'rxjs'; import '../../rtc/__tests__/mocks/webrtc.mocks'; import { OwnCapability } from '../../gen/coordinator'; -import { SoundStateChangeHandler } from '../../helpers/sound-detector'; import { settled, withoutConcurrency } from '../../helpers/concurrency'; -let handler: SoundStateChangeHandler = () => {}; -let unsubscribeHandlers: ReturnType[] = []; +let speechActivityCallback: + | ((state: { isSoundDetected: boolean }) => void) + | null = null; +let unsubscribeMocks: ReturnType[] = []; vi.mock('../../helpers/platforms.ts', () => { return { @@ -46,28 +47,21 @@ vi.mock('../../Call.ts', () => { }; }); -vi.mock('../../helpers/RNSpeechDetector.ts', () => { - console.log('MOCKING RNSpeechDetector'); - return { - RNSpeechDetector: vi.fn().mockImplementation(() => ({ - start: vi.fn((callback) => { - handler = callback; - const unsubscribe = vi.fn(); - unsubscribeHandlers.push(unsubscribe); - return unsubscribe; - }), - stop: vi.fn(), - onSpeakingDetectedStateChange: vi.fn(), - })), - }; -}); - describe('MicrophoneManager React Native', () => { let manager: MicrophoneManager; let checkPermissionMock: ReturnType; + let subscribeMock: ReturnType; + beforeEach(() => { - unsubscribeHandlers = []; + speechActivityCallback = null; + unsubscribeMocks = []; checkPermissionMock = vi.fn(async () => true); + subscribeMock = vi.fn((cb) => { + speechActivityCallback = cb; + const unsub = vi.fn(); + unsubscribeMocks.push(unsub); + return unsub; + }); globalThis.streamRNVideoSDK = { callManager: { @@ -78,6 +72,11 @@ describe('MicrophoneManager React Native', () => { permissions: { check: checkPermissionMock, }, + nativeEvents: { + speechActivity: { + subscribe: subscribeMock, + }, + }, }; const devicePersistence = { enabled: false, storageKey: '' }; @@ -100,7 +99,7 @@ describe('MicrophoneManager React Native', () => { await vi.waitUntil(() => fn.mock.calls.length > 0, { timeout: 100 }); expect(fn).toHaveBeenCalled(); - expect(manager['rnSpeechDetector']?.start).toHaveBeenCalled(); + expect(subscribeMock).toHaveBeenCalled(); }); it('should check native microphone permission before starting detection', async () => { @@ -146,15 +145,15 @@ describe('MicrophoneManager React Native', () => { it('should update speaking while muted state', async () => { await manager['startSpeakingWhileMutedDetection'](); - expect(manager['rnSpeechDetector']?.start).toHaveBeenCalled(); + expect(subscribeMock).toHaveBeenCalled(); expect(manager.state.speakingWhileMuted).toBe(false); - handler!({ isSoundDetected: true, audioLevel: 2 }); + speechActivityCallback!({ isSoundDetected: true }); expect(manager.state.speakingWhileMuted).toBe(true); - handler!({ isSoundDetected: false, audioLevel: 0 }); + speechActivityCallback!({ isSoundDetected: false }); expect(manager.state.speakingWhileMuted).toBe(false); }); @@ -163,21 +162,21 @@ describe('MicrophoneManager React Native', () => { await manager['startSpeakingWhileMutedDetection']('device-1'); await manager['startSpeakingWhileMutedDetection']('device-1'); - expect(unsubscribeHandlers).toHaveLength(1); + expect(unsubscribeMocks).toHaveLength(1); await manager['stopSpeakingWhileMutedDetection'](); - expect(unsubscribeHandlers[0]).toHaveBeenCalledTimes(1); + expect(unsubscribeMocks[0]).toHaveBeenCalledTimes(1); }); it('should cleanup previous speech detector before starting a new one', async () => { await manager['startSpeakingWhileMutedDetection']('device-1'); await manager['startSpeakingWhileMutedDetection']('device-2'); - expect(unsubscribeHandlers).toHaveLength(2); - expect(unsubscribeHandlers[0]).toHaveBeenCalledTimes(1); + expect(unsubscribeMocks).toHaveLength(2); + expect(unsubscribeMocks[0]).toHaveBeenCalledTimes(1); await manager['stopSpeakingWhileMutedDetection'](); - expect(unsubscribeHandlers[1]).toHaveBeenCalledTimes(1); + expect(unsubscribeMocks[1]).toHaveBeenCalledTimes(1); }); it('should stop speaking while muted notifications if user loses permission to send audio', async () => { diff --git a/packages/client/src/helpers/RNSpeechDetector.ts b/packages/client/src/helpers/RNSpeechDetector.ts deleted file mode 100644 index ba0ef2f354..0000000000 --- a/packages/client/src/helpers/RNSpeechDetector.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { BaseStats, flatten } from '../stats'; -import { SoundStateChangeHandler } from './sound-detector'; -import { videoLoggerSystem } from '../logger'; - -export class RNSpeechDetector { - private readonly pc1 = new RTCPeerConnection({}); - private readonly pc2 = new RTCPeerConnection({}); - private audioStream: MediaStream | undefined; - private externalAudioStream: MediaStream | undefined; - private isStopped = false; - - constructor(externalAudioStream?: MediaStream) { - this.externalAudioStream = externalAudioStream; - } - - /** - * Starts the speech detection. - */ - public async start(onSoundDetectedStateChanged: SoundStateChangeHandler) { - let detachListeners: (() => void) | undefined; - let unsubscribe: (() => void) | undefined; - - try { - this.isStopped = false; - const audioStream = - this.externalAudioStream != null - ? this.externalAudioStream - : await navigator.mediaDevices.getUserMedia({ audio: true }); - this.audioStream = audioStream; - - const onPc1IceCandidate = (e: RTCPeerConnectionIceEvent) => { - this.forwardIceCandidate(this.pc2, e.candidate); - }; - const onPc2IceCandidate = (e: RTCPeerConnectionIceEvent) => { - this.forwardIceCandidate(this.pc1, e.candidate); - }; - const onTrackPc2 = (e: RTCTrackEvent) => { - e.streams[0].getTracks().forEach((track) => { - // In RN, the remote track is automatically added to the audio output device - // so we need to mute it to avoid hearing the audio back - // @ts-expect-error _setVolume is a private method in react-native-webrtc - track._setVolume(0); - }); - }; - - this.pc1.addEventListener('icecandidate', onPc1IceCandidate); - this.pc2.addEventListener('icecandidate', onPc2IceCandidate); - this.pc2.addEventListener('track', onTrackPc2); - detachListeners = () => { - this.pc1.removeEventListener('icecandidate', onPc1IceCandidate); - this.pc2.removeEventListener('icecandidate', onPc2IceCandidate); - this.pc2.removeEventListener('track', onTrackPc2); - }; - - audioStream - .getTracks() - .forEach((track) => this.pc1.addTrack(track, audioStream)); - const offer = await this.pc1.createOffer({}); - await this.pc2.setRemoteDescription(offer); - await this.pc1.setLocalDescription(offer); - const answer = await this.pc2.createAnswer(); - await this.pc1.setRemoteDescription(answer); - await this.pc2.setLocalDescription(answer); - unsubscribe = this.onSpeakingDetectedStateChange( - onSoundDetectedStateChanged, - ); - return () => { - detachListeners?.(); - unsubscribe?.(); - this.stop(); - }; - } catch (error) { - detachListeners?.(); - unsubscribe?.(); - this.stop(); - - const logger = videoLoggerSystem.getLogger('RNSpeechDetector'); - logger.error('error handling permissions: ', error); - return () => {}; - } - } - - /** - * Stops the speech detection and releases all allocated resources. - */ - private stop() { - if (this.isStopped) return; - this.isStopped = true; - - this.pc1.close(); - this.pc2.close(); - - if (this.externalAudioStream != null) { - this.externalAudioStream = undefined; - } else { - this.cleanupAudioStream(); - } - } - - /** - * Public method that detects the audio levels and returns the status. - */ - private onSpeakingDetectedStateChange( - onSoundDetectedStateChanged: SoundStateChangeHandler, - ) { - const initialBaselineNoiseLevel = 0.13; - let baselineNoiseLevel = initialBaselineNoiseLevel; - let speechDetected = false; - let speechTimer: NodeJS.Timeout | undefined; - let silenceTimer: NodeJS.Timeout | undefined; - const audioLevelHistory: number[] = []; // Store recent audio levels for smoother detection - const historyLength = 10; - const silenceThreshold = 1.1; - const resetThreshold = 0.9; - const speechTimeout = 500; // Speech is set to true after 500ms of audio detection - const silenceTimeout = 5000; // Reset baseline after 5 seconds of silence - - const checkAudioLevel = async () => { - try { - const stats = await this.pc1.getStats(); - const report = flatten(stats); - // Audio levels are present inside stats of type `media-source` and of kind `audio` - const audioMediaSourceStats = report.find( - (stat) => - stat.type === 'media-source' && - (stat as RTCRtpStreamStats).kind === 'audio', - ) as BaseStats; - if (audioMediaSourceStats) { - const { audioLevel } = audioMediaSourceStats; - if (audioLevel) { - // Update audio level history (with max historyLength sized array) - audioLevelHistory.push(audioLevel); - if (audioLevelHistory.length > historyLength) { - audioLevelHistory.shift(); - } - - // Calculate average audio level - const avgAudioLevel = - audioLevelHistory.reduce((a, b) => a + b, 0) / - audioLevelHistory.length; - - // Update baseline (if necessary) based on silence detection - if (avgAudioLevel < baselineNoiseLevel * silenceThreshold) { - if (!silenceTimer) { - silenceTimer = setTimeout(() => { - baselineNoiseLevel = Math.min( - avgAudioLevel * resetThreshold, - initialBaselineNoiseLevel, - ); - }, silenceTimeout); - } - } else { - clearTimeout(silenceTimer); - silenceTimer = undefined; - } - - // Speech detection with hysteresis - if (avgAudioLevel > baselineNoiseLevel * 1.5) { - if (!speechDetected) { - speechDetected = true; - onSoundDetectedStateChanged({ - isSoundDetected: true, - audioLevel, - }); - } - - clearTimeout(speechTimer); - - speechTimer = setTimeout(() => { - speechDetected = false; - onSoundDetectedStateChanged({ - isSoundDetected: false, - audioLevel: 0, - }); - }, speechTimeout); - } - } - } - } catch (error) { - const logger = videoLoggerSystem.getLogger('RNSpeechDetector'); - logger.error('error checking audio level from stats', error); - } - }; - - const intervalId = setInterval(checkAudioLevel, 250); - return () => { - clearInterval(intervalId); - clearTimeout(speechTimer); - clearTimeout(silenceTimer); - }; - } - - private cleanupAudioStream() { - if (!this.audioStream) { - return; - } - this.audioStream.getTracks().forEach((track) => track.stop()); - if ( - // @ts-expect-error release() is present in react-native-webrtc - typeof this.audioStream.release === 'function' - ) { - // @ts-expect-error called to dispose the stream in RN - this.audioStream.release(); - } - } - - private forwardIceCandidate( - destination: RTCPeerConnection, - candidate: RTCIceCandidate | null, - ) { - if ( - this.isStopped || - !candidate || - destination.signalingState === 'closed' - ) { - return; - } - destination.addIceCandidate(candidate).catch(() => { - // silently ignore the error - const logger = videoLoggerSystem.getLogger('RNSpeechDetector'); - logger.info('cannot add ice candidate - ignoring'); - }); - } -} diff --git a/packages/client/src/helpers/__tests__/RNSpeechDetector.test.ts b/packages/client/src/helpers/__tests__/RNSpeechDetector.test.ts deleted file mode 100644 index 4aec55d4da..0000000000 --- a/packages/client/src/helpers/__tests__/RNSpeechDetector.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import '../../rtc/__tests__/mocks/webrtc.mocks'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { RNSpeechDetector } from '../RNSpeechDetector'; - -describe('RNSpeechDetector', () => { - // Shared test setup stubs RTCPeerConnection with a vi.fn constructor. - // We keep a typed handle to that constructor to inspect created instances. - let rtcPeerConnectionMockCtor: ReturnType; - - beforeEach(() => { - rtcPeerConnectionMockCtor = - globalThis.RTCPeerConnection as unknown as ReturnType; - rtcPeerConnectionMockCtor.mockClear(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('ignores late ICE candidates after cleanup', async () => { - const stream = { - getTracks: () => [], - } as unknown as MediaStream; - const detector = new RNSpeechDetector(stream); - - const cleanup = await detector.start(() => {}); - cleanup(); - - // start() creates two peer connections (pc1 and pc2). We pull them from - // constructor call results to inspect listener wiring and ICE forwarding. - const [pc1, pc2] = rtcPeerConnectionMockCtor.mock.results.map( - (result) => result.value, - ); - - // Find the registered ICE callback and invoke it manually after cleanup to - // simulate a late ICE event arriving during teardown. - const onIceCandidate = pc1.addEventListener.mock.calls.find( - ([eventName]: [string]) => eventName === 'icecandidate', - )?.[1] as ((e: RTCPeerConnectionIceEvent) => void) | undefined; - - expect(onIceCandidate).toBeDefined(); - onIceCandidate?.({ - candidate: { candidate: 'candidate:1 1 UDP 0 127.0.0.1 11111 typ host' }, - } as unknown as RTCPeerConnectionIceEvent); - - expect(pc1.removeEventListener).toHaveBeenCalledWith( - 'icecandidate', - onIceCandidate, - ); - expect(pc2.addIceCandidate).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 4c544ac1bb..f7fb3c23e1 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -462,6 +462,15 @@ export type StreamRNVideoSDKGlobals = { */ check(permission: 'microphone' | 'camera'): Promise; }; + nativeEvents: { + speechActivity: { + /** + * Subscribes to native speech activity events. + * Returns an unsubscribe function. + */ + subscribe(cb: (state: { isSoundDetected: boolean }) => void): () => void; + }; + }; }; declare global { diff --git a/packages/react-native-sdk/CLAUDE.md b/packages/react-native-sdk/CLAUDE.md index 51f0c83a16..3871a311bf 100644 --- a/packages/react-native-sdk/CLAUDE.md +++ b/packages/react-native-sdk/CLAUDE.md @@ -353,7 +353,6 @@ Observable streams for push events: - `useScreenShareButton` - Screen share button logic - `useScreenshot` - Screenshot detection (iOS) - `usePaginatedLayoutSortPreset` - Participant sorting for paginated layouts -- `useSpeechDetection` - Audio level detection - `useTrackDimensions` - Track video dimensions - `usePermissionRequest` - Media permission requests - `usePermissionNotification` - Permission request notifications diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 1bbf9f2859..4a8605731c 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -127,7 +127,7 @@ "@react-native/babel-preset": "^0.81.5", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "137.1.3", + "@stream-io/react-native-webrtc": "137.1.4-alpha.5", "@stream-io/video-filters-react-native": "workspace:^", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "13.3.3", diff --git a/packages/react-native-sdk/src/hooks/index.ts b/packages/react-native-sdk/src/hooks/index.ts index deeebd622b..572299f2e7 100644 --- a/packages/react-native-sdk/src/hooks/index.ts +++ b/packages/react-native-sdk/src/hooks/index.ts @@ -9,5 +9,4 @@ export * from './useScreenShareButton'; export * from './useScreenShareAudioMixing'; export * from './useTrackDimensions'; export * from './useScreenshot'; -export * from './useSpeechDetection'; export * from './useModeration'; diff --git a/packages/react-native-sdk/src/hooks/useSpeechDetection.ts b/packages/react-native-sdk/src/hooks/useSpeechDetection.ts deleted file mode 100644 index cda61b9761..0000000000 --- a/packages/react-native-sdk/src/hooks/useSpeechDetection.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useEffect, useState } from 'react'; -import { - type SoundDetectorState, - RNSpeechDetector, -} from '@stream-io/video-client'; -import { useCallStateHooks } from '@stream-io/video-react-bindings'; - -/** - * Hook that provides speech detection info using the RNSpeechDetector. - * - * @returns An object containing the current audio level (0 - 1) and whether sound is detected. - */ -export function useSpeechDetection() { - const [audioState, setAudioState] = useState({ - isSoundDetected: false, - audioLevel: 0, - }); - const { useMicrophoneState } = useCallStateHooks(); - const { isEnabled, mediaStream } = useMicrophoneState(); - - useEffect(() => { - if (!isEnabled) return; - - const detector = new RNSpeechDetector(mediaStream); - const start = detector.start((state: SoundDetectorState) => { - setAudioState(state); - }); - - return () => { - start.then((stop) => stop()); - }; - }, [mediaStream, isEnabled]); - - return audioState; -} diff --git a/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts b/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts index 5a6e5bfdb3..7c9f7b5b30 100644 --- a/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts +++ b/packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts @@ -1,5 +1,6 @@ import { StreamRNVideoSDKGlobals } from '@stream-io/video-client'; import { NativeModules, PermissionsAndroid, Platform } from 'react-native'; +import { audioDeviceModuleEvents } from '@stream-io/react-native-webrtc'; import { getCallingxLibIfAvailable } from '../push/libs/callingx'; import { endCallingxCall, @@ -85,6 +86,18 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = { ); }, }, + nativeEvents: { + speechActivity: { + subscribe(cb) { + const subscription = audioDeviceModuleEvents.addSpeechActivityListener( + (data) => { + cb({ isSoundDetected: data.event === 'started' }); + }, + ); + return () => subscription.remove(); + }, + }, + }, }; // Note: The global type declaration for `streamRNVideoSDK` is defined in diff --git a/sample-apps/react-native/dogfood/ios/Podfile.lock b/sample-apps/react-native/dogfood/ios/Podfile.lock index f9049ecf16..6cc862cdf6 100644 --- a/sample-apps/react-native/dogfood/ios/Podfile.lock +++ b/sample-apps/react-native/dogfood/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.84.0) - - Callingx (0.1.0): + - Callingx (0.1.1): - boost - DoubleConversion - fast_float @@ -3213,7 +3213,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - stream-io-noise-cancellation-react-native (0.6.0): + - stream-io-noise-cancellation-react-native (0.7.0): - boost - DoubleConversion - fast_float @@ -3243,7 +3243,7 @@ PODS: - stream-react-native-webrtc - StreamVideoNoiseCancellation - Yoga - - stream-io-video-filters-react-native (0.11.0): + - stream-io-video-filters-react-native (0.12.0): - boost - DoubleConversion - fast_float @@ -3272,10 +3272,10 @@ PODS: - SocketRocket - stream-react-native-webrtc - Yoga - - stream-react-native-webrtc (137.1.3): + - stream-react-native-webrtc (137.1.4-alpha.3): - React-Core - StreamWebRTC (~> 137.0.54) - - stream-video-react-native (1.31.0): + - stream-video-react-native (1.32.2): - boost - DoubleConversion - fast_float @@ -3633,7 +3633,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - Callingx: 509e3924c64821ef1b42fae50934def0b987b9ff + Callingx: 29285fe9e6d19be1ba3b321859b71b01942e13b0 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: 82d1d7996af4c5850242966eb81e73f9a6dfab1e @@ -3729,10 +3729,10 @@ SPEC CHECKSUMS: RNWorklets: 944dddd0eef13006b658e653abbb3ee8365c3809 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 stream-chat-react-native: 362e74c743dd34d750e1878f9e479707e9edc794 - stream-io-noise-cancellation-react-native: 3ad4c3df18d6048f5dc107a93e875184959ee59c - stream-io-video-filters-react-native: 9b5f1c93ccbed9b3a8dfec557bfead7282dff949 - stream-react-native-webrtc: 98f68f17acc6bd95b5cc417dfdc0953e0120e696 - stream-video-react-native: f180cac19f111a38d55ef8a42047d2803a37c280 + stream-io-noise-cancellation-react-native: ea8ca1d50e305f2a0ffa027ff36c345aa5278237 + stream-io-video-filters-react-native: 43d4e9901cf478a1340a599242226d024c2eb1a5 + stream-react-native-webrtc: 6671f418ac4539206350e23ff863f12cd22b6041 + stream-video-react-native: 8ff5e47e91d89cb1219a340dd4df4d66521ad056 StreamVideoNoiseCancellation: 41f5a712aba288f9636b64b17ebfbdff52c61490 StreamWebRTC: 57bd35729bcc46b008de4e741a5b23ac28b8854d VisionCamera: 891edb31806dd3a239c8a9d6090d6ec78e11ee80 diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index afa7bdfeab..17dca0e4c9 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -22,7 +22,7 @@ "@react-navigation/native-stack": "^7.3.27", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "137.1.3", + "@stream-io/react-native-webrtc": "137.1.4-alpha.5", "@stream-io/video-filters-react-native": "workspace:^", "@stream-io/video-react-native-sdk": "workspace:^", "axios": "^1.12.2", diff --git a/yarn.lock b/yarn.lock index 3ef84e2b53..056bfa32e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8514,6 +8514,19 @@ __metadata: languageName: node linkType: hard +"@stream-io/react-native-webrtc@npm:137.1.4-alpha.5": + version: 137.1.4-alpha.5 + resolution: "@stream-io/react-native-webrtc@npm:137.1.4-alpha.5" + dependencies: + base64-js: "npm:1.5.1" + debug: "npm:4.3.4" + event-target-shim: "npm:6.0.2" + peerDependencies: + react-native: ">=0.73.0" + checksum: 10/8b31f8f3a988a941587dd30f069b74055b0e4f4fe424f333cb1c8fe88055b39419d9298a480dd4e84ab4d340e4b32b49da1159980348455d17a1caf3a41da9ca + languageName: node + linkType: hard + "@stream-io/stream-video-react-tutorial@workspace:sample-apps/react/stream-video-react-tutorial": version: 0.0.0-use.local resolution: "@stream-io/stream-video-react-tutorial@workspace:sample-apps/react/stream-video-react-tutorial" @@ -8742,7 +8755,7 @@ __metadata: "@rnx-kit/metro-resolver-symlinks": "npm:^0.2.6" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:137.1.3" + "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.5" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-native-sdk": "workspace:^" "@types/react": "npm:^19.2.0" @@ -8839,7 +8852,7 @@ __metadata: "@react-native/babel-preset": "npm:^0.81.5" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:137.1.3" + "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.5" "@stream-io/video-client": "workspace:*" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-bindings": "workspace:*" From aadf81bd15eff3bd25f7b185cb78ab6653b29cf9 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Thu, 16 Apr 2026 14:00:05 +0200 Subject: [PATCH 2/3] update version --- packages/react-native-sdk/package.json | 2 +- .../react-native/dogfood/ios/Podfile.lock | 4 ++-- sample-apps/react-native/dogfood/package.json | 2 +- yarn.lock | 24 ++++++++++++++----- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 4a8605731c..d7b1363be9 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -127,7 +127,7 @@ "@react-native/babel-preset": "^0.81.5", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "137.1.4-alpha.5", + "@stream-io/react-native-webrtc": "137.1.4-alpha.9", "@stream-io/video-filters-react-native": "workspace:^", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "13.3.3", diff --git a/sample-apps/react-native/dogfood/ios/Podfile.lock b/sample-apps/react-native/dogfood/ios/Podfile.lock index 6cc862cdf6..c2eee5877d 100644 --- a/sample-apps/react-native/dogfood/ios/Podfile.lock +++ b/sample-apps/react-native/dogfood/ios/Podfile.lock @@ -3272,7 +3272,7 @@ PODS: - SocketRocket - stream-react-native-webrtc - Yoga - - stream-react-native-webrtc (137.1.4-alpha.3): + - stream-react-native-webrtc (137.1.4-alpha.8): - React-Core - StreamWebRTC (~> 137.0.54) - stream-video-react-native (1.32.2): @@ -3731,7 +3731,7 @@ SPEC CHECKSUMS: stream-chat-react-native: 362e74c743dd34d750e1878f9e479707e9edc794 stream-io-noise-cancellation-react-native: ea8ca1d50e305f2a0ffa027ff36c345aa5278237 stream-io-video-filters-react-native: 43d4e9901cf478a1340a599242226d024c2eb1a5 - stream-react-native-webrtc: 6671f418ac4539206350e23ff863f12cd22b6041 + stream-react-native-webrtc: d51b263d7b8214589c7aeee526675c0b7b7b0c0d stream-video-react-native: 8ff5e47e91d89cb1219a340dd4df4d66521ad056 StreamVideoNoiseCancellation: 41f5a712aba288f9636b64b17ebfbdff52c61490 StreamWebRTC: 57bd35729bcc46b008de4e741a5b23ac28b8854d diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index 17dca0e4c9..2214ed231d 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -22,7 +22,7 @@ "@react-navigation/native-stack": "^7.3.27", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "137.1.4-alpha.5", + "@stream-io/react-native-webrtc": "137.1.4-alpha.8", "@stream-io/video-filters-react-native": "workspace:^", "@stream-io/video-react-native-sdk": "workspace:^", "axios": "^1.12.2", diff --git a/yarn.lock b/yarn.lock index 056bfa32e2..86b3e11893 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8514,16 +8514,28 @@ __metadata: languageName: node linkType: hard -"@stream-io/react-native-webrtc@npm:137.1.4-alpha.5": - version: 137.1.4-alpha.5 - resolution: "@stream-io/react-native-webrtc@npm:137.1.4-alpha.5" +"@stream-io/react-native-webrtc@npm:137.1.4-alpha.8": + version: 137.1.4-alpha.8 + resolution: "@stream-io/react-native-webrtc@npm:137.1.4-alpha.8" dependencies: base64-js: "npm:1.5.1" debug: "npm:4.3.4" event-target-shim: "npm:6.0.2" peerDependencies: react-native: ">=0.73.0" - checksum: 10/8b31f8f3a988a941587dd30f069b74055b0e4f4fe424f333cb1c8fe88055b39419d9298a480dd4e84ab4d340e4b32b49da1159980348455d17a1caf3a41da9ca + checksum: 10/af9ab39a04be10ae7f1ec356ff65e2aba970c2186197ae335f9d76594de3879d5540e54df8c180460c133e6ba9eff96c8e484be27bfd3b6574a1c8919d378d35 + languageName: node + linkType: hard + +"@stream-io/react-native-webrtc@npm:137.1.4-alpha.9": + version: 137.1.4-alpha.9 + resolution: "@stream-io/react-native-webrtc@npm:137.1.4-alpha.9" + dependencies: + base64-js: "npm:1.5.1" + debug: "npm:4.3.4" + peerDependencies: + react-native: ">=0.73.0" + checksum: 10/10e3e6bb4ab9eb46210411a7956a8fa43afd3f04ff129dac86115636efa138de309b5350a511c2d1f53359066d3bf3ecb10a5b25eccd46103241ab0268e1e10b languageName: node linkType: hard @@ -8755,7 +8767,7 @@ __metadata: "@rnx-kit/metro-resolver-symlinks": "npm:^0.2.6" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.5" + "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.8" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-native-sdk": "workspace:^" "@types/react": "npm:^19.2.0" @@ -8852,7 +8864,7 @@ __metadata: "@react-native/babel-preset": "npm:^0.81.5" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.5" + "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.9" "@stream-io/video-client": "workspace:*" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-bindings": "workspace:*" From b5849233596972690cf4a08d18327391a30a15c1 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Wed, 22 Apr 2026 13:06:13 +0200 Subject: [PATCH 3/3] chore: update webrtc --- packages/react-native-sdk/package.json | 2 +- .../react-native/dogfood/ios/Podfile.lock | 4 +-- sample-apps/react-native/dogfood/package.json | 2 +- yarn.lock | 25 +++++-------------- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index d7b1363be9..06afbcc59e 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -127,7 +127,7 @@ "@react-native/babel-preset": "^0.81.5", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "137.1.4-alpha.9", + "@stream-io/react-native-webrtc": "137.2.0-alpha.1", "@stream-io/video-filters-react-native": "workspace:^", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "13.3.3", diff --git a/sample-apps/react-native/dogfood/ios/Podfile.lock b/sample-apps/react-native/dogfood/ios/Podfile.lock index c2eee5877d..0561a598b3 100644 --- a/sample-apps/react-native/dogfood/ios/Podfile.lock +++ b/sample-apps/react-native/dogfood/ios/Podfile.lock @@ -3272,7 +3272,7 @@ PODS: - SocketRocket - stream-react-native-webrtc - Yoga - - stream-react-native-webrtc (137.1.4-alpha.8): + - stream-react-native-webrtc (137.2.0-alpha.1): - React-Core - StreamWebRTC (~> 137.0.54) - stream-video-react-native (1.32.2): @@ -3731,7 +3731,7 @@ SPEC CHECKSUMS: stream-chat-react-native: 362e74c743dd34d750e1878f9e479707e9edc794 stream-io-noise-cancellation-react-native: ea8ca1d50e305f2a0ffa027ff36c345aa5278237 stream-io-video-filters-react-native: 43d4e9901cf478a1340a599242226d024c2eb1a5 - stream-react-native-webrtc: d51b263d7b8214589c7aeee526675c0b7b7b0c0d + stream-react-native-webrtc: bc77c7f12d478f7589bf4899e08064a6052042e8 stream-video-react-native: 8ff5e47e91d89cb1219a340dd4df4d66521ad056 StreamVideoNoiseCancellation: 41f5a712aba288f9636b64b17ebfbdff52c61490 StreamWebRTC: 57bd35729bcc46b008de4e741a5b23ac28b8854d diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index 2214ed231d..68f85baac4 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -22,7 +22,7 @@ "@react-navigation/native-stack": "^7.3.27", "@stream-io/noise-cancellation-react-native": "workspace:^", "@stream-io/react-native-callingx": "workspace:^", - "@stream-io/react-native-webrtc": "137.1.4-alpha.8", + "@stream-io/react-native-webrtc": "137.2.0-alpha.1", "@stream-io/video-filters-react-native": "workspace:^", "@stream-io/video-react-native-sdk": "workspace:^", "axios": "^1.12.2", diff --git a/yarn.lock b/yarn.lock index 86b3e11893..2a388cb4a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8514,28 +8514,15 @@ __metadata: languageName: node linkType: hard -"@stream-io/react-native-webrtc@npm:137.1.4-alpha.8": - version: 137.1.4-alpha.8 - resolution: "@stream-io/react-native-webrtc@npm:137.1.4-alpha.8" - dependencies: - base64-js: "npm:1.5.1" - debug: "npm:4.3.4" - event-target-shim: "npm:6.0.2" - peerDependencies: - react-native: ">=0.73.0" - checksum: 10/af9ab39a04be10ae7f1ec356ff65e2aba970c2186197ae335f9d76594de3879d5540e54df8c180460c133e6ba9eff96c8e484be27bfd3b6574a1c8919d378d35 - languageName: node - linkType: hard - -"@stream-io/react-native-webrtc@npm:137.1.4-alpha.9": - version: 137.1.4-alpha.9 - resolution: "@stream-io/react-native-webrtc@npm:137.1.4-alpha.9" +"@stream-io/react-native-webrtc@npm:137.2.0-alpha.1": + version: 137.2.0-alpha.1 + resolution: "@stream-io/react-native-webrtc@npm:137.2.0-alpha.1" dependencies: base64-js: "npm:1.5.1" debug: "npm:4.3.4" peerDependencies: react-native: ">=0.73.0" - checksum: 10/10e3e6bb4ab9eb46210411a7956a8fa43afd3f04ff129dac86115636efa138de309b5350a511c2d1f53359066d3bf3ecb10a5b25eccd46103241ab0268e1e10b + checksum: 10/0623a6cfcd3e7db75bb452d139902491ce18f8423b2c6bffec68102fab618ad413c2bda541c637c7af686b9f043c58fcba9491eb7a0ad82034736849286490aa languageName: node linkType: hard @@ -8767,7 +8754,7 @@ __metadata: "@rnx-kit/metro-resolver-symlinks": "npm:^0.2.6" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.8" + "@stream-io/react-native-webrtc": "npm:137.2.0-alpha.1" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-native-sdk": "workspace:^" "@types/react": "npm:^19.2.0" @@ -8864,7 +8851,7 @@ __metadata: "@react-native/babel-preset": "npm:^0.81.5" "@stream-io/noise-cancellation-react-native": "workspace:^" "@stream-io/react-native-callingx": "workspace:^" - "@stream-io/react-native-webrtc": "npm:137.1.4-alpha.9" + "@stream-io/react-native-webrtc": "npm:137.2.0-alpha.1" "@stream-io/video-client": "workspace:*" "@stream-io/video-filters-react-native": "workspace:^" "@stream-io/video-react-bindings": "workspace:*"