Skip to content

feat: stereo audio output support RN-332#2038

Merged
santhoshvai merged 29 commits intomainfrom
stereo
Jan 20, 2026
Merged

feat: stereo audio output support RN-332#2038
santhoshvai merged 29 commits intomainfrom
stereo

Conversation

@santhoshvai
Copy link
Member

@santhoshvai santhoshvai commented Dec 9, 2025

💡 Overview

Apart from stereo audio support, we also ensure that from video client we make sure that the audio session always configured before joining a call and then audio session configuration is deactivated after leaving the call

depends on: GetStream/react-native-webrtc#23

📝 Implementation notes

🎫 Ticket: https://linear.app/stream/issue/RN-332

📑 Docs: https://github.com/GetStream/docs-content/pull/899

Summary by CodeRabbit

  • New Features

    • Stereo audio output option for listener role
    • In-call manager lifecycle hooks: setup, start, stop
    • Runtime-registered global in-call manager stub
  • Improvements

    • Centralized and unified audio setup across platforms
    • Debounced audio route-change handling and richer audio logging
    • Safer activity-aware teardown and thread-safe audio controls
  • Chores

    • Removed legacy audio-state utility
    • Sample apps updated to opt into new lifecycle APIs (no auto-start)

✏️ Tip: You can customize this high-level summary in your review settings.

# Conflicts:
#	sample-apps/react-native/dogfood/ios/Podfile.lock
# Conflicts:
#	packages/client/src/Call.ts
#	sample-apps/react-native/dogfood/ios/Podfile.lock
@changeset-bot
Copy link

changeset-bot bot commented Jan 9, 2026

⚠️ No Changeset found

Latest commit: 41788a1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds a global in-call facade bound to native APIs; integrates callManager.setup()/start()/stop() into client call lifecycle; introduces optional stereo audio output controls and platform audio refactors on Android and iOS; removes the Android audio setup store utility; samples updated to opt into stereo and bumped WebRTC dep.

Changes

Cohort / File(s) Summary
Client lifecycle & globals
packages/client/src/Call.ts, packages/client/src/types.ts, packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
Declare StreamRNVideoSDKGlobals and global streamRNVideoSDK; client call flow now calls globalThis.streamRNVideoSDK?.callManager.setup()/start()/stop(); registerSDKGlobals wires a native-backed facade to the global.
SDK runtime registration
packages/react-native-sdk/src/index.ts
Call registerSDKGlobals() during non-web initialization to expose the global facade at runtime.
Call manager API & config
packages/react-native-sdk/src/modules/call-manager/native-module.d.ts, packages/react-native-sdk/src/modules/call-manager/CallManager.ts, packages/react-native-sdk/src/modules/call-manager/types.ts
Add native methods setup() and setEnableStereoAudioOutput(enable: boolean); CallManager.start applies stereo for listener when configured; config gains enableStereoAudioOutput?: boolean.
Android: audio refactor & stereo
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt, .../audio/utils/AudioFocusUtil.kt, .../callmanager/StreamInCallManagerModule.kt, deleted .../audio/utils/AudioSetupStoreUtil.kt
Add enableStereo flag and setup() on AudioDeviceManager; centralize audio attribute setup in AudioFocusUtil.setup(mode, reactContext); expose setup() and setEnableStereoAudioOutput() in RN module; move many ops to audio thread; remove AudioSetupStoreUtil and its store/restore logic.
iOS: audio refactor & stereo
packages/react-native-sdk/ios/StreamInCallManager.m, packages/react-native-sdk/ios/StreamInCallManager.swift
Expose setup and setEnableStereoAudioOutput to RN; Swift adds stereo flag, setup() RTCAudioSession flow, debounced stereo refresh on route changes, and updated start/stop sequences using RTCAudioSession and AudioDeviceModule.
Global registration helper
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
New module creating a streamRNVideoSDK global stub delegating callManager.{setup,start,stop} to the native StreamInCallManager native module; registerSDKGlobals() assigns it if undefined.
Samples & dependency bumps
sample-apps/react-native/dogfood/package.json, sample-apps/react-native/dogfood/ios/AppDelegate.swift, various sample package.json files, sample screens
Bump @stream-io/react-native-webrtc to 137.1.0 in multiple packages; enable verbose WebRTC logging in DEBUG for dogfood app; sample listener sets enableStereoAudioOutput: true.
Sample app lifecycle cleanup
sample-apps/react-native/dogfood/src/components/ActiveCall.tsx, .../navigators/Call.tsx, .../screens/AudioRoom/Room.tsx, .../screens/LiveStream/HostLiveStream.tsx
Removed automatic callManager.start()/stop() effects and corresponding imports across several sample components.
Minor client tweaks
packages/client/src/devices/SpeakerManager.ts
JSDoc line removed; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (call flow)
    participant Global as globalThis.streamRNVideoSDK
    participant RNModule as RN StreamInCallManager NativeModule
    participant Native as Native In-Call Manager

    Client->>Global: callManager.setup()
    Global->>RNModule: setup()
    RNModule->>Native: setup()

    Client->>Global: callManager.start()
    Global->>RNModule: start()
    RNModule->>Native: start() / configure role & stereo

    Client->>Global: callManager.stop()
    Global->>RNModule: stop()
    RNModule->>Native: stop() / deactivate audio
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • oliverlaz
  • jdimovska
  • greenfrvr

Poem

🐇 I hopped into code with twitchy ears,

setup hums softly and clears the gears,
Left and right now swirl in stereo cheer,
Native threads whisper, routes reappear,
I twitch my nose — the call is near!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding stereo audio output support and references the ticket RN-332.
Description check ✅ Passed The description follows the template with Overview and Implementation notes sections, includes the ticket and docs references, and explains both stereo audio support and audio session lifecycle management.
Linked Issues check ✅ Passed The PR implements stereo audio output support as specified in RN-332, adding setup, start, and stop lifecycle methods and enableStereoAudioOutput configuration across native and TypeScript layers.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: stereo audio support, audio session lifecycle management (setup/start/stop), and dependency updates. The sample app updates demonstrating the feature and cleanup of redundant callManager imports are in-scope.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

Comment on lines +102 to +113
intendedCategory = .playAndRecord
intendedMode = defaultAudioDevice == .speaker ? .videoChat : .voiceChat

// XCode 16 and older don't expose .allowBluetoothHFP
// https://forums.swift.org/t/xcode-26-avaudiosession-categoryoptions-allowbluetooth-deprecated/80956
#if compiler(>=6.2) // For Xcode 26.0+
let bluetoothOption: AVAudioSession.CategoryOptions = .allowBluetoothHFP
#else
let bluetoothOption: AVAudioSession.CategoryOptions = .allowBluetooth
#endif
intendedOptions = defaultAudioDevice == .speaker ? [bluetoothOption, .defaultToSpeaker] : [bluetoothOption]
}
Copy link
Member Author

Choose a reason for hiding this comment

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

fyi.. default config adjusted here..

Copy link
Contributor

@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.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/react-native-sdk/src/modules/call-manager/CallManager.ts (1)

95-105: Explicitly disable stereo to avoid leaking state across calls.

When audioRole === 'listener' && enableStereoAudioOutput is true, stereo is enabled. But when either condition is false (or config is missing), setEnableStereoAudioOutput is never called to disable it. If native state persists across start() calls, stereo from a listener session will carry over to the next communicator session.

Proposed fix
   start = (config?: StreamInCallManagerConfig): void => {
-    NativeManager.setAudioRole(config?.audioRole ?? 'communicator');
-    if (config?.audioRole === 'communicator') {
+    const audioRole = config?.audioRole ?? 'communicator';
+    NativeManager.setAudioRole(audioRole);
+    if (audioRole === 'communicator') {
       const type = config.deviceEndpointType ?? 'speaker';
       NativeManager.setDefaultAudioDeviceEndpointType(type);
     }
-    if (config?.audioRole === 'listener' && config.enableStereoAudioOutput) {
+    const shouldEnableStereo =
+      audioRole === 'listener' && !!config?.enableStereoAudioOutput;
+    // Explicitly set stereo to avoid leaking previous state across calls.
+    NativeManager.setEnableStereoAudioOutput(shouldEnableStereo);
-      NativeManager.setEnableStereoAudioOutput(true);
-    }
     NativeManager.start();
   };
packages/client/src/Call.ts (1)

879-896: Move callManager.start() to ensure lifecycle pairing with stop().

The callManager.start() call at line 896 is invoked before the join retry loop. If join() fails after all retries, start() will have been called but stop() will not be invoked unless leave() is explicitly called. This creates a lifecycle imbalance.

Either move callManager.start() into the successful join flow (after doJoin() completes successfully) or document that start() is idempotent and callers are responsible for calling leave() on join failure to ensure proper cleanup.

🤖 Fix all issues with AI agents
In
@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt:
- Around line 37-48: The private setup(audioAttributes: AudioAttributes,
reactContext: ReactContext) currently force-unwraps the WebRTCModule via
reactContext.getNativeModule(WebRTCModule::class.java)!! which can NPE; change
it to safely get the module (check for null), bail out or log and return if
missing, then safely obtain audioDeviceModule from the module (check its
type/null) before calling WebRtcAudioTrackHelper.setAudioOutputAttributes(adm,
audioAttributes); ensure you remove the !! and handle both missing WebRTCModule
and missing/invalid audioDeviceModule gracefully.

In
@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt:
- Around line 83-92: The error log in setEnableStereoAudioOutput incorrectly
references "setAudioRole()" due to a copy-paste mistake; update the log call
inside AudioDeviceManager.runInAudioThread (where it checks
audioManagerActivated) to use "setEnableStereoAudioOutput():" (include TAG and
keep same log level) so the message matches the actual method name and context.
- Around line 118-120: The stop() method clears audioManagerActivated
unconditionally which is asymmetric with start() where audioManagerActivated =
true is set only inside reactApplicationContext.currentActivity?.let; update
stop() so the audioManagerActivated = false assignment is moved inside the
reactApplicationContext.currentActivity?.let block that calls
mAudioDeviceManager.stop(it), or alternatively add an explicit log and retain
the flag only when no activity is present; ensure you modify the stop()
implementation (referencing stop(), audioManagerActivated,
mAudioDeviceManager.stop, and reactApplicationContext.currentActivity?.let) so
the flag change mirrors start() behavior or clearly logs skipped cleanup.
- Around line 94-97: The public ReactMethod setup() currently calls
mAudioDeviceManager.setup() directly and must be run on the audio thread for
thread-safety; wrap the body of setup() in AudioDeviceManager.runInAudioThread
(i.e., call mAudioDeviceManager.runInAudioThread { mAudioDeviceManager.setup()
}) so it executes on the audio thread like start() and other configuration
methods (which also guard access to shared state such as callAudioRole and
mAudioManager.mode) and avoid races with setAudioRole().

In @packages/react-native-sdk/ios/StreamInCallManager.swift:
- Around line 215-223: The setForceSpeakerphoneOn(enable:) implementation calls
AVAudioSession.sharedInstance().setActive(true) without RTCAudioSession locking,
causing a race with WebRTC; modify setForceSpeakerphoneOn to run on
audioSessionQueue and use
RTCAudioSession.sharedInstance().lockForConfiguration() /
unlockForConfiguration() around the operation, call
AVAudioSession.sharedInstance().overrideOutputAudioPort(enable ? .speaker :
.none) inside that locked section, and then call session.setActive(true) on the
RTCAudioSession instance, with defer-based unlocking and the existing error
handling preserved.
- Around line 81-132: The setup() method has two fixes: add a safe guard in
getAudioDeviceModule() to avoid a forced cast crash by checking that
self.bridge.module(forName: "WebRTCModule") is a WebRTCModule and, if not, fail
fast with a clear message like "WebRTCModule is required but not registered with
the bridge" before returning audioDeviceModule; and turn the TODO about failing
with .allowBluetoothA2DP (stereo Bluetooth causing OSStatus -50) into an
explicit documented limitation (create an issue/track it and update the comment
near allowBluetoothA2DP to reference that issue and note that iOS WebRTC rejects
.allowBluetoothA2DP for stereo routing).

In @packages/react-native-sdk/src/index.ts:
- Line 19: registerSDKGlobals() is being called unconditionally which exposes
streamRNVideoSDK (that depends on native modules) on web; wrap the
registerSDKGlobals() invocation in a Platform.OS !== 'web' guard to match the
existing registerGlobals() pattern so the global is only registered on native
platforms (use the same conditional style used around registerGlobals()).

In @sample-apps/react-native/dogfood/ios/AppDelegate.swift:
- Around line 150-154: The WebRTC verbose logging is being enabled
unconditionally; wrap the assignment of
WebRTCModuleOptions.sharedInstance().loggingSeverity = .verbose in a DEBUG-only
conditional so verbose logs are only enabled in dev builds: locate the
WebRTCModuleOptions.sharedInstance() usage (and existing
enableMultitaskingCameraAccess setting) in AppDelegate.swift and enclose the
loggingSeverity = .verbose line inside an #if DEBUG / #endif block, leaving
enableMultitaskingCameraAccess unchanged.
🧹 Nitpick comments (3)
packages/react-native-sdk/src/modules/call-manager/CallManager.ts (1)

114-119: setup() should be platform-guarded and version-guarded (new public API).

This is now part of the public surface; calling it on unsupported platforms (or with older native builds) should fail with a clear error rather than “undefined is not a function”.

Proposed fix
   setup = (): void => {
-    NativeManager.setup();
+    invariant(
+      Platform.OS === 'ios' || Platform.OS === 'android',
+      'Supported only on iOS/Android',
+    );
+    invariant(
+      typeof NativeManager.setup === 'function',
+      'StreamInCallManager.setup is not available. Ensure native modules are updated.',
+    );
+    NativeManager.setup();
   };
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt (1)

50-67: Good refactoring to eliminate code duplication.

The extraction of getAudioAttributes and the centralized setup method successfully eliminates duplication while maintaining the same logic flow. The requestFocus method now properly delegates to the shared helpers.

packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (1)

1-26: Consider error handling in facade methods.

The facade methods delegate directly to callManager without error handling. If callManager.setup(), .start(), or .stop() throw errors, they will propagate to the Call class in the client package, which may not have appropriate error handling (as noted in the Call.ts review).

♻️ Add error boundary to facade
 const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
   callManager: {
     setup: () => {
-      callManager.setup();
+      try {
+        callManager.setup();
+      } catch (err) {
+        console.warn('[StreamVideoSDK] Failed to setup call manager', err);
+      }
     },
     start: () => {
-      callManager.start();
+      try {
+        callManager.start();
+      } catch (err) {
+        console.warn('[StreamVideoSDK] Failed to start call manager', err);
+      }
     },
     stop: () => {
-      callManager.stop();
+      try {
+        callManager.stop();
+      } catch (err) {
+        console.warn('[StreamVideoSDK] Failed to stop call manager', err);
+      }
     },
   },
 };

Note: The duplicate global declaration at lines 4-6 is redundant since it's already declared in @stream-io/video-client/src/types.ts. Consider removing it to avoid potential conflicts.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between be82657 and 7ff537d.

⛔ Files ignored due to path filters (7)
  • packages/audio-filters-web/src/krispai/dist/krispsdk.es5.js is excluded by !**/dist/**
  • packages/audio-filters-web/src/krispai/dist/krispsdk.js is excluded by !**/dist/**
  • packages/audio-filters-web/src/krispai/dist/krispsdk.mjs is excluded by !**/dist/**
  • packages/audio-filters-web/src/krispai/dist/usermedia.js is excluded by !**/dist/**
  • packages/audio-filters-web/src/krispai/dist/usermedia.mjs is excluded by !**/dist/**
  • sample-apps/react-native/dogfood/ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (16)
  • packages/client/src/Call.ts
  • packages/client/src/types.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioSetupStoreUtil.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
  • packages/react-native-sdk/ios/StreamInCallManager.m
  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/src/index.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
  • sample-apps/react-native/dogfood/package.json
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
💤 Files with no reviewable changes (1)
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioSetupStoreUtil.kt
🧰 Additional context used
📓 Path-based instructions (14)
packages/react-native-sdk/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

packages/react-native-sdk/src/**/*.{ts,tsx}: Use React hooks from @stream-io/video-react-bindings (via useCall() and useCallStateHooks()) instead of directly accessing RxJS observables from @stream-io/video-client
Use Platform.OS checks and conditional imports to gate platform-specific code (iOS, Android, web) and prevent importing platform-specific libraries on unsupported platforms

Files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
packages/react-native-sdk/src/modules/**/*.ts

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

Structure native module TypeScript interfaces in src/modules/ to match the exact signatures of their native implementations (Android Kotlin and iOS Swift), including parameter types and return types

Files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x)

📄 CodeRabbit inference engine (AGENTS.md)

packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x): Use TypeScript for all source code in packages/client, packages/react-sdk, and packages/react-native-sdk
Mark deprecated APIs with @deprecated JSDoc, including rationale and replacement guidance
Throw descriptive errors or return typed error results consistently with existing patterns in public APIs
Gate internal debug logging behind an environment flag; no console noise in production builds
Never leak credentials or user data in error messages or logs
Check instance IDs and timestamps before state updates to avoid race conditions in async operations
Make public API surfaces explicit with TypeScript types and interfaces

Files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for function and property names
Narrowly scope eslint-disable comments with inline explanatory comments and rationale

Files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use PascalCase for component and type names

Files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
**/*.{sh,js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Make scripts error on missing critical environment variables

Files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
packages/react-native-sdk/src/utils/**/*.ts

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

Implement React Native native modules using NativeModules API and always wrap calls in try-catch blocks to handle promise rejection from native code

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
sample-apps/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use placeholder keys (e.g., YOUR_STREAM_KEY) in code examples and sample apps, never real credentials

Files:

  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
packages/client/**/*.ts

📄 CodeRabbit inference engine (packages/client/CLAUDE.md)

packages/client/**/*.ts: Implement scoped loggers with configurable log levels (debug, info, warn, error) for each component for easier debugging
Use helper functions isReactNative(), isSafari(), isFirefox(), isMobileSafari() to detect platform differences and apply browser/platform-specific quirks

Files:

  • packages/client/src/types.ts
  • packages/client/src/Call.ts
packages/client/src/**/*.ts?(x)

📄 CodeRabbit inference engine (AGENTS.md)

Avoid unguarded web-only APIs in shared code between React and React Native

Files:

  • packages/client/src/types.ts
  • packages/client/src/Call.ts
packages/client/**/Call.ts

📄 CodeRabbit inference engine (packages/client/CLAUDE.md)

packages/client/**/Call.ts: Use withoutConcurrency for serial execution in join/leave operations and critical state changes; use withCancellation for device toggling and switching operations
Use PermissionsContext.canPublish(trackType) and hasPermission() methods to gate operations based on user permissions before publishing or performing actions
Use distinct tag symbols for different concurrency operations to ensure operations with same tag run serially while different tags run in parallel; use settled(tag) to wait for completion

Files:

  • packages/client/src/Call.ts
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding Android native functionality, create Kotlin file in android/src/main/java/com/streamvideo/reactnative/, use @ReactMethod annotation, register in StreamVideoReactNativePackage.kt, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
packages/react-native-sdk/ios/**/*.swift

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding iOS native functionality, create Swift file in ios/, create Objective-C bridge with RCT_EXTERN_METHOD, expose via StreamVideoReactNative.swift, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
packages/react-native-sdk/src/index.ts

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

packages/react-native-sdk/src/index.ts: Always call registerGlobals() from @stream-io/react-native-webrtc before using WebRTC on non-web platforms in the React Native SDK entry point
Export all public SDK APIs (components, hooks, providers, types) from the main src/index.ts file, with re-exports from client and bindings packages and SDK-specific implementations

Files:

  • packages/react-native-sdk/src/index.ts
🧠 Learnings (40)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Handle iOS backgrounding by disabling video tracks to prevent battery drain when `AppState` changes to 'background', while keeping audio enabled
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: For cross-package changes, rebuild in dependency order: client → bindings → react-native-sdk, then test in sample app to verify integration
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Implement Android foreground service via `useAndroidKeepCallAliveEffect()` hook to prevent call termination when app is backgrounded, with automatic permission handling for POST_NOTIFICATIONS (Android 13+)
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Test React Native changes on both iOS and Android for native module or platform-specific UI changes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/ios/StreamInCallManager.m
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/**/*.{ts,tsx} : Never play local participant audio; always check !participant.isLocalParticipant before rendering Audio components

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/src/core/components/{ParticipantView,Video,Audio}/**/*.{ts,tsx} : Use callback refs pattern to support both internal state and external user refs for media elements

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/types.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • sample-apps/react-native/dogfood/package.json
  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/react-native-sdk/ios/StreamInCallManager.m
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • sample-apps/react-native/dogfood/package.json
  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Export all public SDK APIs (components, hooks, providers, types) from the main `src/index.ts` file, with re-exports from client and bindings packages and SDK-specific implementations

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/version.ts : Store generated version information in `src/version.ts` (auto-generated via `yarn copy-version` script) and export for SDK consumers

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • sample-apps/react-native/dogfood/package.json
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt : When adding Android native functionality, create Kotlin file in `android/src/main/java/com/streamvideo/reactnative/`, use `ReactMethod` annotation, register in `StreamVideoReactNativePackage.kt`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • sample-apps/react-native/dogfood/package.json
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/modules/**/*.ts : Structure native module TypeScript interfaces in `src/modules/` to match the exact signatures of their native implementations (Android Kotlin and iOS Swift), including parameter types and return types

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Make public API surfaces explicit with TypeScript types and interfaces

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/**/*.{ts,tsx} : Always use hooks from stream-io/video-react-bindings via useCallStateHooks() factory pattern instead of importing individual hooks directly

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/types.ts
  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Handle iOS backgrounding by disabling video tracks to prevent battery drain when `AppState` changes to 'background', while keeping audio enabled

Applied to files:

  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/**/*.tsx : Use the renderless component pattern (component that returns null) for side-effect-only child components within `StreamCall` and `StreamVideo` providers to keep logic separated and testable

Applied to files:

  • sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Throw descriptive errors or return typed error results consistently with existing patterns in public APIs

Applied to files:

  • packages/client/src/types.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/theme/**/*.ts : Apply deep partial theme overrides using the `DeepPartial<Theme>` type when customizing StreamVideo provider theme to maintain type safety

Applied to files:

  • packages/client/src/types.ts
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/src/**/*.{ts,tsx} : Translations are provided via i18next in bindings; use useI18n() hook and support translationsOverrides prop in StreamVideo

Applied to files:

  • packages/client/src/types.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/Call.ts : Use withoutConcurrency for serial execution in join/leave operations and critical state changes; use withCancellation for device toggling and switching operations

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/*{CallState,stateStore}.ts : Use RxJS observables (BehaviorSubject) for state management and always update state through .next(), never mutate state directly

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/callEventHandlers.ts : Register Coordinator event handlers in callEventHandlers.ts to wire up backend events to state updates; distinguish from SFU events using isSfuEvent()

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/{CallState,Subscriber}.ts : Use sessionId as the unique identifier for participants, not userId; use trackLookupPrefix for track lookups combining participant ID and track type

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/push/**/*.ts : Use RxJS subjects from `src/utils/push/internal/rxSubjects.ts` (like `pushTappedIncomingCallCId$`, `pushAcceptedIncomingCallCId$`, `pushRejectedIncomingCallCId$`, `voipPushNotificationCallCId$`) to bridge native push events to React hooks

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : When adding new React Native hooks, use `useCall()` and `useCallStateHooks()` from bindings layer, handle platform-specific logic with `Platform.OS`, add tests in `__tests__/hooks/`, and export from `src/hooks/index.ts`

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Implement Android foreground service via `useAndroidKeepCallAliveEffect()` hook to prevent call termination when app is backgrounded, with automatic permission handling for POST_NOTIFICATIONS (Android 13+)

Applied to files:

  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt
  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/expo-config-plugin/src/**/*.ts : Configure Expo config plugins in `expo-config-plugin/` with platform-specific modifiers for Android manifest, iOS Info.plist, and app delegate modifications

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Test changes that affect multiple packages in dependency order: client → bindings → react-sdk

Applied to files:

  • sample-apps/react-native/dogfood/package.json
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: For cross-package changes, rebuild in dependency order: client → bindings → react-native-sdk, then test in sample app to verify integration

Applied to files:

  • sample-apps/react-native/dogfood/package.json
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Use the dogfood sample app (`sample-apps/react-native/dogfood/`) for manual testing of all SDK features and integration with push notifications before release

Applied to files:

  • sample-apps/react-native/dogfood/package.json
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/client/src/**/*.ts?(x) : Avoid unguarded web-only APIs in shared code between React and React Native

Applied to files:

  • sample-apps/react-native/dogfood/package.json
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Mark deprecated APIs with deprecated JSDoc, including rationale and replacement guidance

Applied to files:

  • sample-apps/react-native/dogfood/package.json
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Use monorepo build commands (`yarn build:react-native:deps`, `yarn build:react-native:sdk`) to ensure proper dependency ordering when working with workspace packages

Applied to files:

  • sample-apps/react-native/dogfood/package.json
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use Platform.OS checks and conditional imports to gate platform-specific code (iOS, Android, web) and prevent importing platform-specific libraries on unsupported platforms

Applied to files:

  • sample-apps/react-native/dogfood/package.json
  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/src/components/**/*.{ts,tsx} : All public components should be exported from src/components/index.ts and have their own directory with index.ts

Applied to files:

  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Gate internal debug logging behind an environment flag; no console noise in production builds

Applied to files:

  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/utils/**/*.ts : Implement React Native native modules using `NativeModules` API and always wrap calls in try-catch blocks to handle promise rejection from native code

Applied to files:

  • packages/react-native-sdk/src/index.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/utils/push/**/*.ts : Safely detect optional library availability using helper functions like `getFirebaseMessagingLibNoThrow()` and `getExpoNotificationsLib()` before accessing optional peer dependencies

Applied to files:

  • packages/react-native-sdk/src/index.ts
🧬 Code graph analysis (5)
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (2)
packages/client/src/types.ts (1)
  • StreamRNVideoSDKGlobals (333-350)
packages/react-native-sdk/src/modules/call-manager/index.ts (1)
  • callManager (5-5)
sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx (1)
packages/react-native-sdk/src/modules/call-manager/index.ts (1)
  • callManager (5-5)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt (2)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (1)
  • setup (116-125)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt (1)
  • setup (94-97)
packages/react-native-sdk/src/index.ts (2)
packages/react-native-sdk/__mocks__/@stream-io/react-native-webrtc.tsx (1)
  • registerGlobals (19-19)
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (1)
  • registerSDKGlobals (22-26)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (2)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt (2)
  • setup (37-41)
  • setup (43-48)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt (1)
  • setup (94-97)
🪛 SwiftLint (0.57.0)
packages/react-native-sdk/ios/StreamInCallManager.swift

[Warning] 95-95: TODOs should be resolved (for stereo we should disallow ...)

(todo)


[Error] 423-423: Force casts should be avoided

(force_cast)

🔇 Additional comments (21)
sample-apps/react-native/dogfood/src/screens/LiveStream/ViewLiveStream.tsx (1)

45-50: Confirm callManager lifecycle ownership (singleton start/stop).

Given callManager is a singleton, unmounting this screen calls callManager.stop() unconditionally; please confirm this can’t tear down audio for another active call/session in the app.

packages/react-native-sdk/ios/StreamInCallManager.m (1)

10-13: iOS native selector parity confirmed—implementations match across all layers.

The setEnableStereoAudioOutput:(BOOL)enable and setup extern declarations correctly map to their Swift @objc implementations and TypeScript interface definitions. No signature mismatches or runtime failures expected.

packages/react-native-sdk/src/modules/call-manager/types.ts (1)

18-26: LGTM! Clean API addition for stereo audio configuration.

The optional enableStereoAudioOutput field is well-placed in the listener branch of the union type, with clear naming and appropriate typing for enabling high-quality stereo audio output.

packages/client/src/Call.ts (2)

347-350: Verify error handling for callManager.setup().

The call to globalThis.streamRNVideoSDK?.callManager.setup() happens early in the setup flow, which is correct. However, if the native setup can throw errors (e.g., permission issues, audio system initialization failures), those exceptions would propagate and potentially prevent the Call from initializing.

Consider wrapping this in a try-catch block if native setup can fail:

🛡️ Defensive error handling
-      globalThis.streamRNVideoSDK?.callManager.setup();
+      try {
+        globalThis.streamRNVideoSDK?.callManager.setup();
+      } catch (err) {
+        this.logger.warn('Failed to setup native call manager', err);
+      }

596-666: Lifecycle hook placement looks correct.

The callManager.stop() call is appropriately placed after core cleanup (stats, subscribers, publisher, SFU client) and before device disposal, ensuring the native audio session is properly deactivated after the call infrastructure is torn down.

Consistent with the setup() comment, consider adding error handling if native stop can fail.

packages/client/src/types.ts (1)

333-354: LGTM! Clean global API surface for React Native SDK integration.

The StreamRNVideoSDKGlobals type provides a clear, minimal interface for lifecycle management with well-documented methods. The global declaration follows TypeScript conventions and maintains type safety while allowing the Call class to optionally integrate with platform-specific audio management.

As per coding guidelines, this makes the public API surface explicit with TypeScript types.

packages/react-native-sdk/src/modules/call-manager/native-module.d.ts (2)

54-57: LGTM! Interface matches native implementations.

The setup() method is correctly typed and documented. The signature aligns with the Android (StreamInCallManagerModule.kt) and iOS (StreamInCallManager.swift) native implementations.

Based on learnings and coding guidelines.


74-78: LGTM! Stereo audio control interface is well-defined.

The setEnableStereoAudioOutput() method signature and documentation are clear. The boolean parameter type matches the native implementations on both platforms.

Based on learnings and coding guidelines.

packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (4)

92-92: LGTM! Stereo flag is properly initialized.

The enableStereo property is correctly declared with a safe default value of false. It's accessed from the audio thread via the ReactMethod wrapper in StreamInCallManagerModule.


129-129: LGTM! Clean lifecycle separation.

The setup() call at the start of the audio thread execution properly centralizes audio configuration before activation. This separates concerns: setup() configures the session, start() activates it.


147-160: Verify Activity null handling coordination.

The stop() method now requires a non-null Activity parameter to reset volumeControlStream (line 158). In StreamInCallManagerModule.kt (lines 118-120), stop() is only called if currentActivity is non-null. Ensure this coordination is intentional and that skipping stop() when activity is null doesn't cause audio session leaks or resource cleanup issues.

As per coding guidelines: Check instance IDs and timestamps before state updates to avoid race conditions in async operations.


116-125: Use the enableStereo property in the setup() method to match iOS implementation.

The enableStereo property is set via setEnableStereoAudioOutput() but never used in the setup() method. iOS explicitly calls setStereoPlayoutPreference(true) when stereo is enabled, but the Android implementation leaves the property unused. Either the Android setup should apply stereo configuration (similar to iOS), or the property should be removed if stereo is handled solely at the SDP level. Verify whether Android needs an equivalent to setStereoPlayoutPreference() or confirm that stereo output is controlled only through SDP negotiation.

⛔ Skipped due to learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/**/*.{ts,tsx} : Never play local participant audio; always check !participant.isLocalParticipant before rendering Audio components
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`
packages/react-native-sdk/src/index.ts (1)

32-32: LGTM! Call manager module is properly exported.

The export follows the project pattern of exposing public SDK APIs from the main entry point. This allows SDK consumers to access the call manager functionality.

Based on coding guidelines: Export all public SDK APIs from the main src/index.ts file.

packages/react-native-sdk/ios/StreamInCallManager.swift (8)

27-27: LGTM! Stereo flag is properly encapsulated.

The enableStereo private property with a default of false provides safe state management, controlled exclusively through the public setEnableStereoAudioOutput() method.


37-37: LGTM! Debounce work item is properly declared.

The optional stereoRefreshWorkItem provides clean debouncing for stereo refresh operations. It's properly cancelled during cleanup in stop() (lines 184-186).


70-79: LGTM! Stereo control is thread-safe and properly guarded.

The setEnableStereoAudioOutput() implementation correctly:

  • Dispatches on audioSessionQueue for thread safety
  • Guards against changes after audio manager activation
  • Mirrors the Android implementation pattern

140-163: LGTM! Clean lifecycle flow with proper error handling.

The setup() integration follows the correct sequence: configure → update UI → activate. The RTCAudioSession locking/unlocking pattern with defer ensures proper cleanup even on error paths.


184-186: LGTM! Proper cleanup of debounced work.

Cancelling and nilling the stereoRefreshWorkItem prevents execution of stale stereo refresh operations after the call manager stops. DispatchWorkItem.cancel() is thread-safe, so calling it outside the audio queue is acceptable.


233-269: LGTM! Enhanced logging aids debugging.

The expanded logAudioState() provides comprehensive diagnostics by including both native AVAudioSession state and WebRTC's RTCAudioSession wrapper state. This will be valuable for debugging stereo output and audio routing issues.


347-376: LGTM! Proper debouncing for stereo refresh.

The debounced stereo refresh pattern is well-implemented:

  • Cancels pending refreshes to avoid redundant work
  • Uses [weak self] to prevent retain cycles
  • 2-second delay allows the audio route to stabilize
  • Only triggers on .routeConfigurationChange, not all route changes

The refreshStereoPlayoutState() call ensures stereo output remains correctly configured when audio devices change (e.g., connecting/disconnecting headphones or bluetooth).


438-459: LGTM! Comprehensive route change reason mapping.

The routeChangeReasonDescription() helper provides clear, human-readable strings for all AVAudioSession.RouteChangeReason cases. The @unknown default case gracefully handles future enum additions.

Copy link
Contributor

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts:
- Line 5: The StreamInCallManagerNativeModule assignment and subsequent uses
(e.g., any calls to StreamInCallManagerNativeModule methods between the const
declaration and the code around lines 13-18) must guard against the native
module being undefined and wrap calls in try-catch: first check that
StreamInCallManagerNativeModule is not null/undefined before invoking its
methods and log an error or no-op if missing, and for every call to
StreamInCallManagerNativeModule.* wrap the invocation in a try-catch to handle
and log exceptions rather than allowing a crash.
🧹 Nitpick comments (1)
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (1)

19-24: Add error handling for callManager lifecycle methods.

If callManager.start() or callManager.stop() throw, the error will propagate and potentially break the join/leave flow in Call.ts.

♻️ Proposed defensive wrapping
     start: () => {
+      try {
         callManager.start();
+      } catch (error) {
+        console.warn('Failed to start call manager', error);
+      }
     },
     stop: () => {
+      try {
         callManager.stop();
+      } catch (error) {
+        console.warn('Failed to stop call manager', error);
+      }
     },
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff537d and b011140.

📒 Files selected for processing (5)
  • packages/client/src/Call.ts
  • packages/client/src/devices/SpeakerManager.ts
  • packages/client/src/types.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
💤 Files with no reviewable changes (1)
  • packages/client/src/devices/SpeakerManager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • packages/client/src/types.ts
🧰 Additional context used
📓 Path-based instructions (9)
packages/react-native-sdk/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

packages/react-native-sdk/src/**/*.{ts,tsx}: Use React hooks from @stream-io/video-react-bindings (via useCall() and useCallStateHooks()) instead of directly accessing RxJS observables from @stream-io/video-client
Use Platform.OS checks and conditional imports to gate platform-specific code (iOS, Android, web) and prevent importing platform-specific libraries on unsupported platforms

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
packages/react-native-sdk/src/utils/**/*.ts

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

Implement React Native native modules using NativeModules API and always wrap calls in try-catch blocks to handle promise rejection from native code

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x)

📄 CodeRabbit inference engine (AGENTS.md)

packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x): Use TypeScript for all source code in packages/client, packages/react-sdk, and packages/react-native-sdk
Mark deprecated APIs with @deprecated JSDoc, including rationale and replacement guidance
Throw descriptive errors or return typed error results consistently with existing patterns in public APIs
Gate internal debug logging behind an environment flag; no console noise in production builds
Never leak credentials or user data in error messages or logs
Check instance IDs and timestamps before state updates to avoid race conditions in async operations
Make public API surfaces explicit with TypeScript types and interfaces

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for function and property names
Narrowly scope eslint-disable comments with inline explanatory comments and rationale

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use PascalCase for component and type names

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
**/*.{sh,js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Make scripts error on missing critical environment variables

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
packages/client/**/Call.ts

📄 CodeRabbit inference engine (packages/client/CLAUDE.md)

packages/client/**/Call.ts: Use withoutConcurrency for serial execution in join/leave operations and critical state changes; use withCancellation for device toggling and switching operations
Use PermissionsContext.canPublish(trackType) and hasPermission() methods to gate operations based on user permissions before publishing or performing actions
Use distinct tag symbols for different concurrency operations to ensure operations with same tag run serially while different tags run in parallel; use settled(tag) to wait for completion

Files:

  • packages/client/src/Call.ts
packages/client/**/*.ts

📄 CodeRabbit inference engine (packages/client/CLAUDE.md)

packages/client/**/*.ts: Implement scoped loggers with configurable log levels (debug, info, warn, error) for each component for easier debugging
Use helper functions isReactNative(), isSafari(), isFirefox(), isMobileSafari() to detect platform differences and apply browser/platform-specific quirks

Files:

  • packages/client/src/Call.ts
packages/client/src/**/*.ts?(x)

📄 CodeRabbit inference engine (AGENTS.md)

Avoid unguarded web-only APIs in shared code between React and React Native

Files:

  • packages/client/src/Call.ts
🧠 Learnings (20)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt : When adding Android native functionality, create Kotlin file in `android/src/main/java/com/streamvideo/reactnative/`, use `ReactMethod` annotation, register in `StreamVideoReactNativePackage.kt`, and call from TypeScript via `NativeModules.StreamVideoReactNative`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Export all public SDK APIs (components, hooks, providers, types) from the main `src/index.ts` file, with re-exports from client and bindings packages and SDK-specific implementations

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/version.ts : Store generated version information in `src/version.ts` (auto-generated via `yarn copy-version` script) and export for SDK consumers

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt : When adding Android native functionality, create Kotlin file in `android/src/main/java/com/streamvideo/reactnative/`, use `ReactMethod` annotation, register in `StreamVideoReactNativePackage.kt`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/modules/**/*.ts : Structure native module TypeScript interfaces in `src/modules/` to match the exact signatures of their native implementations (Android Kotlin and iOS Swift), including parameter types and return types

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Make public API surfaces explicit with TypeScript types and interfaces

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/theme/**/*.ts : Apply deep partial theme overrides using the `DeepPartial<Theme>` type when customizing StreamVideo provider theme to maintain type safety

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/Call.ts : Use withoutConcurrency for serial execution in join/leave operations and critical state changes; use withCancellation for device toggling and switching operations

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/*{CallState,stateStore}.ts : Use RxJS observables (BehaviorSubject) for state management and always update state through .next(), never mutate state directly

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/callEventHandlers.ts : Register Coordinator event handlers in callEventHandlers.ts to wire up backend events to state updates; distinguish from SFU events using isSfuEvent()

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/{CallState,Subscriber}.ts : Use sessionId as the unique identifier for participants, not userId; use trackLookupPrefix for track lookups combining participant ID and track type

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/utils/push/setupIosVoipPushEvents.ts : Implement VoIP push display logic to call `CallKeep.displayIncomingCall()` within 30 seconds of receiving VoIP push notification on iOS, or the system will terminate the app

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Handle iOS backgrounding by disabling video tracks to prevent battery drain when `AppState` changes to 'background', while keeping audio enabled

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/src/hooks/usePersistedDevicePreferences.ts : Handle device selection state by checking if devices are available before use; use usePersistedDevicePreferences at app level

Applied to files:

  • packages/client/src/Call.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • packages/client/src/Call.ts
🧬 Code graph analysis (2)
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (2)
packages/client/src/types.ts (1)
  • StreamRNVideoSDKGlobals (334-355)
packages/react-native-sdk/src/modules/call-manager/index.ts (1)
  • callManager (5-5)
packages/client/src/Call.ts (2)
packages/client/src/store/CallState.ts (1)
  • settings (938-940)
packages/client/src/devices/ScreenShareState.ts (1)
  • settings (59-61)
🔇 Additional comments (4)
packages/client/src/Call.ts (3)

664-665: LGTM! Call manager cleanup is properly placed in the leave flow.

The stop() call is correctly positioned after call state cleanup and before device disposal, ensuring the audio session is deactivated before devices are released.


1114-1114: LGTM! Call manager start is properly gated.

The start() call is correctly placed after device configuration succeeds and protected by the deviceSettingsAppliedOnce guard, preventing repeated activations on reconnections.


2657-2659: LGTM! Audio session setup precedes device configuration.

The setup() call correctly configures the native audio session with backend settings before applying camera/microphone device configuration. The optional chaining ensures this is a no-op on non-RN platforms.

packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (1)

28-31: LGTM! Global registration pattern is appropriate.

The guard against re-registration (if (!global.streamRNVideoSDK)) ensures the globals are only set once, preventing potential issues with multiple registrations.

@santhoshvai santhoshvai changed the title feat: stereo audio output support feat: stereo audio output support RN-332 Jan 11, 2026
Copy link
Contributor

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/react-native-sdk/src/modules/call-manager/CallManager.ts:
- Around line 103-105: The stereo flag is only set true but never cleared, so
update CallManager (around the block that checks config?.audioRole ===
'listener') to explicitly call NativeManager.setEnableStereoAudioOutput(false)
when starting in listener role without enableStereoAudioOutput; alternatively
ensure the native modules' stop() implementations reset their stored stereo
state. Specifically modify the logic that currently only calls
setEnableStereoAudioOutput(true) to call false when
config.enableStereoAudioOutput is falsy, or add resets in the native stop()
methods to clear the instance stereo flag.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b011140 and 2dd5d68.

📒 Files selected for processing (7)
  • packages/react-native-sdk/src/index.ts
  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
  • sample-apps/react-native/dogfood/src/components/ActiveCall.tsx
  • sample-apps/react-native/dogfood/src/navigators/Call.tsx
  • sample-apps/react-native/dogfood/src/screens/AudioRoom/Room.tsx
  • sample-apps/react-native/dogfood/src/screens/LiveStream/HostLiveStream.tsx
💤 Files with no reviewable changes (4)
  • sample-apps/react-native/dogfood/src/navigators/Call.tsx
  • sample-apps/react-native/dogfood/src/components/ActiveCall.tsx
  • sample-apps/react-native/dogfood/src/screens/LiveStream/HostLiveStream.tsx
  • sample-apps/react-native/dogfood/src/screens/AudioRoom/Room.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
  • packages/react-native-sdk/src/index.ts
🧰 Additional context used
📓 Path-based instructions (6)
packages/react-native-sdk/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

packages/react-native-sdk/src/**/*.{ts,tsx}: Use React hooks from @stream-io/video-react-bindings (via useCall() and useCallStateHooks()) instead of directly accessing RxJS observables from @stream-io/video-client
Use Platform.OS checks and conditional imports to gate platform-specific code (iOS, Android, web) and prevent importing platform-specific libraries on unsupported platforms

Files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
packages/react-native-sdk/src/modules/**/*.ts

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

Structure native module TypeScript interfaces in src/modules/ to match the exact signatures of their native implementations (Android Kotlin and iOS Swift), including parameter types and return types

Files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x)

📄 CodeRabbit inference engine (AGENTS.md)

packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x): Use TypeScript for all source code in packages/client, packages/react-sdk, and packages/react-native-sdk
Mark deprecated APIs with @deprecated JSDoc, including rationale and replacement guidance
Throw descriptive errors or return typed error results consistently with existing patterns in public APIs
Gate internal debug logging behind an environment flag; no console noise in production builds
Never leak credentials or user data in error messages or logs
Check instance IDs and timestamps before state updates to avoid race conditions in async operations
Make public API surfaces explicit with TypeScript types and interfaces

Files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for function and property names
Narrowly scope eslint-disable comments with inline explanatory comments and rationale

Files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use PascalCase for component and type names

Files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
**/*.{sh,js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Make scripts error on missing critical environment variables

Files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications

Applied to files:

  • packages/react-native-sdk/src/modules/call-manager/CallManager.ts
🧬 Code graph analysis (1)
packages/react-native-sdk/src/modules/call-manager/CallManager.ts (1)
packages/react-native-sdk/src/modules/call-manager/types.ts (1)
  • StreamInCallManagerConfig (18-26)
🔇 Additional comments (1)
packages/react-native-sdk/src/modules/call-manager/CallManager.ts (1)

94-95: LGTM!

The JSDoc correctly documents the new enableStereoAudioOutput parameter and its scope limitation to the listener audio role, matching the discriminated union type definition.

Copy link
Contributor

@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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt (1)

115-128: State inconsistency when currentActivity is null.

If currentActivity is null when stop() is called, the audioManagerActivated flag remains true while microphone mute and screen flags are still reset. This leaves the module in an inconsistent state where:

  1. The audio manager isn't actually stopped
  2. audioManagerActivated stays true, preventing future start() calls
  3. Side effects (mute/screen flags) are still cleared
Suggested fix: Move flag reset outside the let block or handle the null case
 @ReactMethod
 fun stop() {
     AudioDeviceManager.runInAudioThread {
         if (audioManagerActivated) {
             Log.d(TAG, "stop() mAudioDeviceManager")
-            reactApplicationContext.currentActivity?.let {
-                mAudioDeviceManager.stop(it)
-                audioManagerActivated = false
+            val activity = reactApplicationContext.currentActivity
+            if (activity != null) {
+                mAudioDeviceManager.stop(activity)
+            } else {
+                Log.w(TAG, "stop(): currentActivity is null, stopping without activity context")
+                // Consider: mAudioDeviceManager.stopWithoutActivity() or similar
             }
+            audioManagerActivated = false
             setMicrophoneMute(false)
             setKeepScreenOn(false)
         }
     }
 }
packages/react-native-sdk/ios/StreamInCallManager.swift (1)

352-381: Debounce logic is sound, but stereoRefreshWorkItem access needs synchronization.

The 2-second debounce for routeConfigurationChange is a reasonable approach to let iOS audio routing settle. However, stereoRefreshWorkItem is accessed directly in this notification callback (which may fire on arbitrary threads) without synchronization. This relates to the race condition noted in stop().

Consider wrapping the debounce logic in audioSessionQueue.async:

🔧 Proposed fix
         if reason == .routeConfigurationChange {
-            // Cancel any pending debounced refresh
-            stereoRefreshWorkItem?.cancel()
-            // Create a new debounced work item
-            let workItem = DispatchWorkItem { [weak self] in
-                self?.getAudioDeviceModule().refreshStereoPlayoutState()
-                self?.log("Executed debounced refreshStereoPlayoutState")
-            }
-            stereoRefreshWorkItem = workItem
-            // Schedule the work item after 2 seconds
-            audioSessionQueue.asyncAfter(deadline: .now() + 2.0, execute: workItem)
+            audioSessionQueue.async { [weak self] in
+                guard let self = self else { return }
+                // Cancel any pending debounced refresh
+                self.stereoRefreshWorkItem?.cancel()
+                // Create a new debounced work item
+                let workItem = DispatchWorkItem { [weak self] in
+                    self?.getAudioDeviceModule().refreshStereoPlayoutState()
+                    self?.log("Executed debounced refreshStereoPlayoutState")
+                }
+                self.stereoRefreshWorkItem = workItem
+                // Schedule the work item after 2 seconds
+                self.audioSessionQueue.asyncAfter(deadline: .now() + 2.0, execute: workItem)
+            }
         }
🤖 Fix all issues with AI agents
In
@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt:
- Around line 94-99: The setup() method is missing the audioManagerActivated
guard used by other configuration methods; update
StreamInCallManagerModule.setup to check the audioManagerActivated flag before
calling AudioDeviceManager.runInAudioThread and return early if activation has
already occurred (matching the pattern used in setAudioRole,
setDefaultAudioDeviceEndpointType, setEnableStereoAudioOutput); perform the
guard in setup(), then call mAudioDeviceManager.setup() inside
AudioDeviceManager.runInAudioThread only when not activated to ensure consistent
API behavior.

In @packages/react-native-sdk/ios/StreamInCallManager.swift:
- Around line 184-186: Access to stereoRefreshWorkItem is not synchronized here
and can race with handleAudioRouteChange which uses audioSessionQueue; move the
two lines that cancel and nil out stereoRefreshWorkItem so they run inside the
same audioSessionQueue.async block used for audio session state changes (i.e.,
perform stereoRefreshWorkItem?.cancel() and stereoRefreshWorkItem = nil within
audioSessionQueue.async) to ensure thread-safe access consistent with
handleAudioRouteChange.
- Around line 81-132: setup() reads shared mutable properties (callAudioRole,
defaultAudioDevice, enableStereo) without synchronization while setters
(setAudioRole(), setDefaultAudioDeviceEndpointType(),
setEnableStereoAudioOutput()) modify them on audioSessionQueue, creating a data
race; to fix, wrap the entire body of setup() in audioSessionQueue.sync { ... }
so all reads occur on the same serial queue used by the setters, preserving
current behavior and thread-safety when accessing callAudioRole,
defaultAudioDevice, enableStereo and when configuring RTCAudioSession.
🧹 Nitpick comments (2)
packages/react-native-sdk/ios/StreamInCallManager.swift (2)

95-97: Address or track the TODO comment.

The static analysis tool flagged this TODO. The comment indicates that stereo mode should disallow BluetoothHFP and only allow BluetoothA2DP (matching iOS native SDK behavior), but this currently fails with OSStatus error -50.

Consider either resolving this limitation or creating a tracking issue if it's a known platform constraint.

Would you like me to open an issue to track investigating the BluetoothA2DP-only option for stereo mode?


427-437: Consider whether fatalError is appropriate for an SDK.

Using fatalError will crash the host app if the bridge or WebRTCModule is unavailable. While this guards against programming errors (the module should always be registered), crashing may be undesirable for a library SDK.

If this is intentional as a precondition check (the SDK cannot function without WebRTCModule), this is acceptable. Otherwise, consider returning an optional or using a softer failure mode with logging.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd5d68 and d8f0045.

📒 Files selected for processing (2)
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
  • packages/react-native-sdk/ios/StreamInCallManager.swift
🧰 Additional context used
📓 Path-based instructions (2)
packages/react-native-sdk/ios/**/*.swift

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding iOS native functionality, create Swift file in ios/, create Objective-C bridge with RCT_EXTERN_METHOD, expose via StreamVideoReactNative.swift, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding Android native functionality, create Kotlin file in android/src/main/java/com/streamvideo/reactnative/, use @ReactMethod annotation, register in StreamVideoReactNativePackage.kt, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt : When adding Android native functionality, create Kotlin file in `android/src/main/java/com/streamvideo/reactnative/`, use `ReactMethod` annotation, register in `StreamVideoReactNativePackage.kt`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Handle iOS backgrounding by disabling video tracks to prevent battery drain when `AppState` changes to 'background', while keeping audio enabled

Applied to files:

  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt
🪛 SwiftLint (0.57.0)
packages/react-native-sdk/ios/StreamInCallManager.swift

[Warning] 95-95: TODOs should be resolved (for stereo we should disallow ...)

(todo)

🔇 Additional comments (10)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt (5)

83-92: LGTM!

The implementation correctly follows the established pattern of other configuration methods (setAudioRole, setDefaultAudioDeviceEndpointType): runs on the audio thread, guards against modification after activation, and logs clearly on invalid state.


144-157: LGTM - Good defensive programming with role validation.

The addition of audio thread execution and role validation is appropriate. Speakerphone control is only relevant for Communicator role.

Minor note: For enum comparison in Kotlin, != (structural equality) is more idiomatic than !== (referential equality), though both work correctly for enum constants.


177-192: LGTM!

Consistent implementation with setForceSpeakerphoneOn(): audio thread execution and role validation for Communicator-only functionality.


194-199: LGTM!

Wrapping in runInAudioThread ensures thread-safe audio operations.


201-206: LGTM!

Symmetric implementation with muteAudioOutput(), ensuring thread-safe execution.

packages/react-native-sdk/ios/StreamInCallManager.swift (5)

69-79: LGTM!

The method follows the established pattern of other configuration setters in this class, using the audioSessionQueue for thread-safe property access and preventing changes after activation.


134-164: LGTM!

The start() method properly calls setup() within the audioSessionQueue.async block ensuring thread safety. The RTCAudioSession lock/unlock pattern with defer is correctly implemented.


213-229: LGTM!

Good improvement to use audioSessionQueue.async with [weak self] for thread safety and proper RTCAudioSession locking pattern.


237-274: LGTM!

The enhanced logging provides valuable diagnostic information including ADM and RTCAudioSession state. Good use of the lock pattern for safe access.


450-471: LGTM!

Clean helper for logging with good use of @unknown default for forward compatibility with future iOS SDK additions.

Copy link
Contributor

@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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/react-native-sdk/ios/StreamInCallManager.swift (2)

95-100: Track the TODO for stereo Bluetooth configuration.

The TODO indicates that for proper stereo playback, BluetoothHFP should be disallowed in favor of BluetoothA2DP (as done in the iOS native SDK), but this currently fails with OSStatus error -50. Consider creating an issue to track this limitation, as it may affect stereo quality over Bluetooth headsets.

Would you like me to open an issue to track this Bluetooth stereo configuration limitation?


213-229: Consider whether setActive(true) is necessary.

The session should already be active when setForceSpeakerphoneOn is called during a call. The overrideOutputAudioPort call alone should be sufficient. If keeping setActive(true) as a safeguard, consider that it could potentially interfere with other audio apps if the session wasn't actually active.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d8f0045 and cc38180.

📒 Files selected for processing (1)
  • packages/react-native-sdk/ios/StreamInCallManager.swift
🧰 Additional context used
📓 Path-based instructions (1)
packages/react-native-sdk/ios/**/*.swift

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding iOS native functionality, create Swift file in ios/, create Objective-C bridge with RCT_EXTERN_METHOD, expose via StreamVideoReactNative.swift, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Check instance IDs and timestamps before state updates to avoid race conditions in async operations

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/**/{Video,Audio,ParticipantView}/**/*.{ts,tsx} : Use useLayoutEffect instead of useEffect for video element binding to call.bindVideoElement() to ensure timing before paint

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : Handle iOS backgrounding by disabling video tracks to prevent battery drain when `AppState` changes to 'background', while keeping audio enabled

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
🪛 SwiftLint (0.57.0)
packages/react-native-sdk/ios/StreamInCallManager.swift

[Warning] 95-95: TODOs should be resolved (for stereo we should disallow ...)

(todo)

🔇 Additional comments (6)
packages/react-native-sdk/ios/StreamInCallManager.swift (6)

69-79: LGTM!

The setEnableStereoAudioOutput method follows the established pattern of other configuration setters, with proper thread safety via audioSessionQueue.async and appropriate guard against changes after activation.


134-164: LGTM!

The start() method properly:

  • Guards against double activation
  • Calls setup() within the queue context
  • Uses the lock/unlock pattern with defer for RTCAudioSession
  • Handles UI operations (wake lock, route observer) on the main thread

166-196: LGTM!

Good cleanup pattern - cancelling the pending stereoRefreshWorkItem before clearing audioManagerActivated prevents stale callbacks from executing after the session is stopped.


352-381: LGTM!

The debouncing pattern for refreshStereoPlayoutState is well-implemented:

  • Cancels any pending work before scheduling new work
  • Uses [weak self] to prevent retain cycles
  • 2-second delay provides stability for rapid route changes

427-437: LGTM!

Using fatalError is appropriate here since a missing bridge or WebRTCModule represents a fatal integration error that cannot be recovered from. The error messages are clear enough to help developers identify the issue.


450-471: LGTM!

Good use of @unknown default to handle potential future RouteChangeReason cases added in newer iOS versions.

Copy link
Contributor

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/react-native-sdk/ios/StreamInCallManager.swift:
- Around line 428-438: Change getAudioDeviceModule() to return an optional
AudioDeviceModule? instead of crashing: remove the fatalError calls, log a clear
error when bridge is nil or WebRTCModule isn’t found (using NSLog/os_log or the
project logger), and return nil; return webrtcModule.audioDeviceModule as an
optional when present. Update all callers (e.g., sites that call
getAudioDeviceModule().reset()) to safely unwrap or use optional chaining like
getAudioDeviceModule()?.reset() so the code fails gracefully during teardown or
missing-module edge cases.
🧹 Nitpick comments (2)
packages/react-native-sdk/ios/StreamInCallManager.swift (2)

84-86: Consider using regular optionals or direct initialization.

Implicitly unwrapped optionals (!) can be avoided here since both branches of the conditional fully initialize these variables. A cleaner approach would use direct assignment.

♻️ Suggested refactor
-            let intendedCategory: AVAudioSession.Category!
-            let intendedMode: AVAudioSession.Mode!
-            let intendedOptions: AVAudioSession.CategoryOptions!
-            
-            let adm = getAudioDeviceModule()
-            adm.reset()
-
-            if (callAudioRole == .listener) {
+            let adm = getAudioDeviceModule()
+            adm.reset()
+
+            let intendedCategory: AVAudioSession.Category
+            let intendedMode: AVAudioSession.Mode
+            let intendedOptions: AVAudioSession.CategoryOptions
+            
+            if callAudioRole == .listener {

363-374: Consider extracting debounce delay as a named constant.

The 2.0 second debounce delay for stereo state refresh is a magic number. Extracting it as a named constant would improve readability and make it easier to tune.

♻️ Suggested refactor

Add a constant at the class level:

private let stereoRefreshDebounceInterval: TimeInterval = 2.0

Then update line 373:

-            audioSessionQueue.asyncAfter(deadline: .now() + 2.0, execute: workItem)
+            audioSessionQueue.asyncAfter(deadline: .now() + stereoRefreshDebounceInterval, execute: workItem)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cc38180 and aed8889.

📒 Files selected for processing (1)
  • packages/react-native-sdk/ios/StreamInCallManager.swift
🧰 Additional context used
📓 Path-based instructions (1)
packages/react-native-sdk/ios/**/*.swift

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding iOS native functionality, create Swift file in ios/, create Objective-C bridge with RCT_EXTERN_METHOD, expose via StreamVideoReactNative.swift, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Check instance IDs and timestamps before state updates to avoid race conditions in async operations

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
🪛 SwiftLint (0.57.0)
packages/react-native-sdk/ios/StreamInCallManager.swift

[Warning] 96-96: TODOs should be resolved (for stereo we should disallow ...)

(todo)

🔇 Additional comments (7)
packages/react-native-sdk/ios/StreamInCallManager.swift (7)

27-37: LGTM!

The new private properties for stereo state management are well-scoped with appropriate default values.


69-79: LGTM!

The method follows the established pattern for configuration setters, correctly guarding against changes after activation and dispatching to the serial queue for thread safety.


96-98: Acknowledged limitation: Stereo Bluetooth A2DP configuration.

The TODO documents a known limitation where enabling .allowBluetoothA2DP without .allowBluetoothHFP fails with OSStatus error -50. This matches the iOS native SDK behavior limitation. Consider linking to a tracking issue if one exists.


135-165: LGTM!

The start() method correctly delegates setup configuration before activation. Since audioSessionQueue is a serial queue, the async block from setup() is guaranteed to complete before this method's async block executes. The wake lock and observer registration appropriately run on the main thread.


167-197: LGTM!

The stop() method properly cleans up by canceling pending debounced work items, deactivating the audio session, and disabling wake lock and proximity monitoring on the main thread.


214-230: Verify locking strategy for speaker override.

The method locks RTCAudioSession but then operates directly on AVAudioSession.sharedInstance(). While this may be intentional to prevent conflicts with WebRTC's audio session management, consider whether the port override should go through RTCAudioSession for consistency with the rest of the refactored code.


451-472: LGTM!

The helper method provides a clean, exhaustive mapping of route change reasons with proper handling of unknown future values via @unknown default.

Copy link
Contributor

@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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts:
- Around line 10-25: streamRNVideoSDKGlobals.callManager methods currently call
native APIs directly and can throw if the native module is missing; wrap each
native call in try-catch blocks inside callManager.setup, callManager.start, and
callManager.stop (i.e., around
StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType, .setup,
.start, and .stop) and handle errors by logging them (console.warn or existing
logger) and failing gracefully instead of letting exceptions propagate.
- Line 4: The code directly assigns NativeModules.StreamInCallManager to
StreamInCallManagerNativeModule which can be undefined on some platforms; add a
defensive guard where StreamInCallManagerNativeModule is declared/used: check
NativeModules.StreamInCallManager for truthiness, and if absent assign a safe
no-op stub object (implementing the same method names used elsewhere) or set the
variable to undefined and early-return from callers; also emit a console.warn or
logger warning when the native module is missing so we fail gracefully instead
of causing runtime crashes (refer to the StreamInCallManagerNativeModule symbol
and any functions that call its methods).
🧹 Nitpick comments (5)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (1)

116-125: Consider documenting thread-safety requirements.

The setup() function is public and modifies audio manager state. While current callers correctly invoke it within runInAudioThread, the function itself doesn't enforce this. Consider adding a KDoc comment indicating this must be called on the audio thread, or adding an assertion to catch misuse during development.

/**
 * Initializes audio mode and focus settings based on the current [callAudioRole].
 * Must be called on the audio thread (via [runInAudioThread]).
 */
fun setup() {
packages/react-native-sdk/ios/StreamInCallManager.swift (4)

84-86: Consider using explicit non-optional types with direct initialization.

Using implicitly unwrapped optionals (!) works here since all branches assign values, but it's more idiomatic Swift to initialize variables directly within a switch/if expression or declare them as non-optional with immediate assignment.

♻️ Suggested refactor
-            let intendedCategory: AVAudioSession.Category!
-            let intendedMode: AVAudioSession.Mode!
-            let intendedOptions: AVAudioSession.CategoryOptions!
-            
-            let adm = getAudioDeviceModule()
-            adm.reset()
-
-            if (callAudioRole == .listener) {
+            let adm = getAudioDeviceModule()
+            adm.reset()
+
+            let intendedCategory: AVAudioSession.Category
+            let intendedMode: AVAudioSession.Mode
+            let intendedOptions: AVAudioSession.CategoryOptions
+            
+            if callAudioRole == .listener {

96-98: Track the TODO for stereo Bluetooth A2DP support.

The static analysis flagged this TODO comment. The comment indicates a known limitation where enabling Bluetooth A2DP for stereo playback (matching iOS native SDK behavior) fails with OSStatus error -50. This should be tracked in your issue tracker to ensure it's not forgotten.

Would you like me to help open an issue to track this Bluetooth A2DP limitation for stereo audio?


366-379: Consider dispatching logAudioState() to the audio session queue.

The logAudioState() call at line 379 acquires the RTCAudioSession lock synchronously from whatever thread delivers the route change notification. While likely fine in practice, dispatching it to audioSessionQueue would be more consistent with the rest of the audio session management in this class.

♻️ Optional refactor
-        logAudioState()
-        
+        audioSessionQueue.async { [weak self] in
+            self?.logAudioState()
+        }

431-441: Consider graceful handling instead of fatalError.

While missing RCTBridge or WebRTCModule are genuine programming errors, using fatalError will crash the app. This could occur during edge cases like app teardown. Consider returning an optional or logging an error and handling the failure gracefully.

♻️ Suggested approach with optional return
-    private func getAudioDeviceModule() -> AudioDeviceModule {
-        guard let bridge = self.bridge else {
-            fatalError("StreamInCallManager: RCTBridge is not available yet.")
-        }
-
-        guard let webrtcModule = bridge.module(forName: "WebRTCModule") as? WebRTCModule else {
-            fatalError("WebRTCModule is required but not registered with the bridge")
-        }
-
-        return webrtcModule.audioDeviceModule
-    }
+    private func getAudioDeviceModule() -> AudioDeviceModule? {
+        guard let bridge = self.bridge else {
+            log("Error: RCTBridge is not available")
+            return nil
+        }
+
+        guard let webrtcModule = bridge.module(forName: "WebRTCModule") as? WebRTCModule else {
+            log("Error: WebRTCModule is not registered with the bridge")
+            return nil
+        }
+
+        return webrtcModule.audioDeviceModule
+    }

Then update call sites to use optional chaining (e.g., getAudioDeviceModule()?.reset()).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between aed8889 and 57e0fe0.

📒 Files selected for processing (3)
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
🧰 Additional context used
📓 Path-based instructions (8)
packages/react-native-sdk/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

packages/react-native-sdk/src/**/*.{ts,tsx}: Use React hooks from @stream-io/video-react-bindings (via useCall() and useCallStateHooks()) instead of directly accessing RxJS observables from @stream-io/video-client
Use Platform.OS checks and conditional imports to gate platform-specific code (iOS, Android, web) and prevent importing platform-specific libraries on unsupported platforms

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
packages/react-native-sdk/src/utils/**/*.ts

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

Implement React Native native modules using NativeModules API and always wrap calls in try-catch blocks to handle promise rejection from native code

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x)

📄 CodeRabbit inference engine (AGENTS.md)

packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x): Use TypeScript for all source code in packages/client, packages/react-sdk, and packages/react-native-sdk
Mark deprecated APIs with @deprecated JSDoc, including rationale and replacement guidance
Throw descriptive errors or return typed error results consistently with existing patterns in public APIs
Gate internal debug logging behind an environment flag; no console noise in production builds
Never leak credentials or user data in error messages or logs
Check instance IDs and timestamps before state updates to avoid race conditions in async operations
Make public API surfaces explicit with TypeScript types and interfaces

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for function and property names
Narrowly scope eslint-disable comments with inline explanatory comments and rationale

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use PascalCase for component and type names

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
**/*.{sh,js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Make scripts error on missing critical environment variables

Files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
packages/react-native-sdk/ios/**/*.swift

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding iOS native functionality, create Swift file in ios/, create Objective-C bridge with RCT_EXTERN_METHOD, expose via StreamVideoReactNative.swift, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt

📄 CodeRabbit inference engine (packages/react-native-sdk/CLAUDE.md)

When adding Android native functionality, create Kotlin file in android/src/main/java/com/streamvideo/reactnative/, use @ReactMethod annotation, register in StreamVideoReactNativePackage.kt, and call from TypeScript via NativeModules.StreamVideoReactNative

Files:

  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
🧠 Learnings (20)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Export all public SDK APIs (components, hooks, providers, types) from the main `src/index.ts` file, with re-exports from client and bindings packages and SDK-specific implementations

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/version.ts : Store generated version information in `src/version.ts` (auto-generated via `yarn copy-version` script) and export for SDK consumers

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/**/*.kt : When adding Android native functionality, create Kotlin file in `android/src/main/java/com/streamvideo/reactnative/`, use `ReactMethod` annotation, register in `StreamVideoReactNativePackage.kt`, and call from TypeScript via `NativeModules.StreamVideoReactNative`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/modules/**/*.ts : Structure native module TypeScript interfaces in `src/modules/` to match the exact signatures of their native implementations (Android Kotlin and iOS Swift), including parameter types and return types

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Make public API surfaces explicit with TypeScript types and interfaces

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/utils/**/*.ts : Implement React Native native modules using `NativeModules` API and always wrap calls in try-catch blocks to handle promise rejection from native code

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.tsx : Always request media permissions via `usePermissionRequest()` hook before calling device enable methods like `call.camera.enable()` or `call.microphone.enable()`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/**/*.tsx : Use the renderless component pattern (component that returns null) for side-effect-only child components within `StreamCall` and `StreamVideo` providers to keep logic separated and testable

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/hooks/**/*.ts : When adding new React Native hooks, use `useCall()` and `useCallStateHooks()` from bindings layer, handle platform-specific logic with `Platform.OS`, add tests in `__tests__/hooks/`, and export from `src/hooks/index.ts`

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/StreamVideoClient.ts : Implement SafePromise pattern for long-lived promises that may reject to prevent unhandled rejection errors; use checkPending() to check if promise is still pending

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/client/src/**/*.ts?(x) : Avoid unguarded web-only APIs in shared code between React and React Native

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:08:06.856Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:08:06.856Z
Learning: Applies to packages/react-sdk/**/*.{ts,tsx} : Always use hooks from stream-io/video-react-bindings via useCallStateHooks() factory pattern instead of importing individual hooks directly

Applied to files:

  • packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts
📚 Learning: 2026-01-09T11:07:06.761Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/client/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:06.761Z
Learning: Applies to packages/client/**/rtc/helpers/sdp.ts : Use removeCodecsExcept() helper for codec selection and enableStereo() for high-quality audio in music/DJ applications

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
  • packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Check instance IDs and timestamps before state updates to avoid race conditions in async operations

Applied to files:

  • packages/react-native-sdk/ios/StreamInCallManager.swift
🧬 Code graph analysis (3)
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (1)
packages/client/src/types.ts (1)
  • StreamRNVideoSDKGlobals (334-355)
packages/react-native-sdk/ios/StreamInCallManager.swift (2)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt (2)
  • setEnableStereoAudioOutput (83-92)
  • setup (94-99)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (1)
  • setup (116-125)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (3)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt (1)
  • setup (94-99)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt (2)
  • setup (37-41)
  • setup (43-48)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/BluetoothManager.kt (1)
  • updateAudioDeviceState (78-81)
🪛 SwiftLint (0.57.0)
packages/react-native-sdk/ios/StreamInCallManager.swift

[Warning] 96-96: TODOs should be resolved (for stereo we should disallow ...)

(todo)

🔇 Additional comments (11)
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt (3)

92-93: LGTM!

The new enableStereo property follows the existing pattern for configuration properties in this class. It's reset to false in stop(), ensuring clean state between calls.


127-144: LGTM!

The refactoring to call setup() at the start of start() centralizes audio initialization logic and keeps it properly within the audio thread context.


147-164: Verify conditional state reset behavior.

The state resets at lines 155-157 (callAudioRole, enableStereo, defaultAudioDevice) only occur when callAudioRole == CallAudioRole.Communicator. This means:

  • If stopping a non-Communicator call, these values retain their previous state.

If a non-Communicator call could have modified enableStereo or defaultAudioDevice, those values won't be reset. Verify this is the intended behavior, or consider moving the resets outside the conditional block:

Suggested fix if resets should apply to all call types
     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()
             }
+            // Reset to defaults for all call types
+            callAudioRole = CallAudioRole.Communicator
+            enableStereo = false
+            defaultAudioDevice = AudioDeviceEndpoint.TYPE_SPEAKER
             activity.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
             audioFocusUtil.abandonFocus()
         }
     }
packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (2)

6-8: LGTM!

The global declaration correctly extends the global namespace with proper TypeScript typing, allowing globalThis.streamRNVideoSDK to be accessed safely with the appropriate type.


27-31: LGTM!

The idempotent registration pattern correctly prevents overwriting an existing global, which is important if multiple SDK instances or re-initializations occur.

packages/react-native-sdk/ios/StreamInCallManager.swift (6)

70-79: LGTM!

The implementation correctly mirrors the Android pattern, preventing stereo configuration changes after activation and using the audio session queue for thread safety.


135-165: LGTM!

The start() method correctly chains setup() and activation on the same serial queue, ensuring proper ordering. The RTCAudioSession lock/unlock pattern with defer is correctly implemented.


183-189: LGTM!

Good defensive cleanup: cancelling the pending debounced work item and resetting all configuration state to defaults ensures a clean slate for the next call.


226-228: Verify necessity of setActive(true) after output port override.

The setActive(true) call after overrideOutputAudioPort may be unnecessary if the session is already active (from start()). This could also cause unexpected behavior if called when the audio session is intentionally inactive.

Consider whether this activation is needed, or if the port override alone is sufficient when the session is already active.


241-278: LGTM!

The enhanced logging now includes both AVAudioSession and RTCAudioSession state, which will be valuable for debugging stereo and audio routing issues. The locking pattern is correctly applied.


454-475: LGTM!

Clean helper implementation with proper @unknown default handling for future iOS versions.

Copy link
Contributor

@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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
sample-apps/react-native/dogfood/ios/AppDelegate.swift (1)

152-155: LGTM! Debug-only logging is correctly gated.

The #if DEBUG block properly ensures verbose WebRTC logs are only active in debug builds, preventing console noise in production. Based on learnings, this aligns with the guideline to gate internal debug logging behind an environment flag.

Minor nit: The comment at lines 132-133 ("Uncomment the next line to enable verbose WebRTC logs") is now outdated since verbose logging is already enabled automatically in DEBUG builds. Consider removing or updating that comment to avoid confusion.

🧹 Optional cleanup
-    // Uncomment the next line to enable verbose WebRTC logs
-    // WebRTCModuleOptions.sharedInstance().loggingSeverity = .verbose
-    
     let localizedAppName = Bundle.main.localizedInfoDictionary?["CFBundleDisplayName"] as? String
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 57e0fe0 and 5bd920a.

📒 Files selected for processing (1)
  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamCall/**/*.tsx : Disable local video in background on iOS to save battery, but maintain audio connection for ongoing calls using AVAudioSession background modes
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/providers/StreamVideo.tsx : Use `StreamVideoRN.configure()` static method to set up SDK configuration including foreground service, push notification providers, and notification callbacks before mounting StreamVideo provider
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/**/*.{ts,tsx} : Use React hooks from `stream-io/video-react-bindings` (via `useCall()` and `useCallStateHooks()`) instead of directly accessing RxJS observables from `stream-io/video-client`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/ios/**/*.swift : When adding iOS native functionality, create Swift file in `ios/`, create Objective-C bridge with `RCT_EXTERN_METHOD`, expose via `StreamVideoReactNative.swift`, and call from TypeScript via `NativeModules.StreamVideoReactNative`
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Do not instantiate multiple `StreamVideo` providers at the same hierarchy level; use only one provider at app root and nest `StreamCall` providers under it
📚 Learning: 2026-01-09T11:08:35.312Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-09T11:08:35.312Z
Learning: Applies to packages/{client,react-sdk,react-native-sdk}/src/**/*.ts?(x) : Gate internal debug logging behind an environment flag; no console noise in production builds

Applied to files:

  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Use the dogfood sample app (`sample-apps/react-native/dogfood/`) for manual testing of all SDK features and integration with push notifications before release

Applied to files:

  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/expo-config-plugin/src/**/*.ts : Configure Expo config plugins in `expo-config-plugin/` with platform-specific modifiers for Android manifest, iOS Info.plist, and app delegate modifications

Applied to files:

  • sample-apps/react-native/dogfood/ios/AppDelegate.swift
📚 Learning: 2026-01-09T11:07:38.535Z
Learnt from: CR
Repo: GetStream/stream-video-js PR: 0
File: packages/react-native-sdk/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:38.535Z
Learning: Applies to packages/react-native-sdk/src/index.ts : Always call `registerGlobals()` from `stream-io/react-native-webrtc` before using WebRTC on non-web platforms in the React Native SDK entry point

Applied to files:

  • sample-apps/react-native/dogfood/ios/AppDelegate.swift

Copy link
Contributor

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/react-native-sdk/ios/StreamInCallManager.swift`:
- Around line 379-390: The notification handler accesses and mutates
stereoRefreshWorkItem off-thread which races with stop() that touches the same
property on audioSessionQueue; wrap the cancel, creation/assignment of
stereoRefreshWorkItem and scheduling into audioSessionQueue (use
audioSessionQueue.async or asyncIfNeeded) so all reads/writes to
stereoRefreshWorkItem happen on audioSessionQueue, keep the DispatchWorkItem
closure body unchanged (calling
getAudioDeviceModule().refreshStereoPlayoutState() and log) and ensure you
capture self weakly as before.
🧹 Nitpick comments (2)
packages/react-native-sdk/ios/StreamInCallManager.swift (2)

84-86: Remove implicitly unwrapped optionals.

These variables are assigned in all code paths, so IUOs are unnecessary and can mask potential issues.

♻️ Suggested fix
-        let intendedCategory: AVAudioSession.Category!
-        let intendedMode: AVAudioSession.Mode!
-        let intendedOptions: AVAudioSession.CategoryOptions!
+        let intendedCategory: AVAudioSession.Category
+        let intendedMode: AVAudioSession.Mode
+        let intendedOptions: AVAudioSession.CategoryOptions

98-100: Consider creating an issue to track the Bluetooth A2DP limitation.

The TODO indicates that stereo playback with Bluetooth A2DP-only mode fails with OSStatus error -50. This is a known iOS limitation worth tracking externally.

Would you like me to open an issue to track resolving this Bluetooth A2DP stereo limitation?

Copy link
Contributor

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/react-native-sdk/ios/StreamInCallManager.swift`:
- Around line 111-113: The TODO about stereo/Bluetooth handling in
StreamInCallManager.swift must be resolved or tracked: implement the stereo
behavior by setting intendedOptions = self.enableStereo ? [.allowBluetoothA2DP]
: [] where intendedOptions is used to configure the AVAudioSession (e.g., when
calling setCategory/setMode/setActive), and add a fallback path that catches the
OSStatus -50 error and retries with [.allowBluetoothHFP] (log the error).
Alternatively, if you don't implement now, replace the TODO with a TODO/FIXME
that includes a link/ID to a new issue and add a swiftlint:disable comment so
SwiftLint stops failing; reference the enableStereo property and the
intendedOptions assignment so reviewers can find the change.
♻️ Duplicate comments (4)
packages/react-native-sdk/ios/StreamInCallManager.swift (3)

243-255: Use RTCAudioSession activation to keep WebRTC state consistent.

Calling AVAudioSession.setActive(true) bypasses RTCAudioSession’s activation bookkeeping (activationCount, isActive). Use session.setActive(true) under the RTC lock instead.

🔧 Proposed fix
             do {
                 try avAudioSession.overrideOutputAudioPort(enable ? .speaker : .none)
-                try avAudioSession.setActive(true)
+                try session.setActive(true)
             } catch {
                 self?.log("Error setting speakerphone: \(error)")
             }
RTCAudioSession iOS setActive vs AVAudioSession setActive when using WebRTC

382-402: Race on stereoRefreshWorkItem outside audioSessionQueue.

This reintroduces the earlier race: the notification handler mutates stereoRefreshWorkItem off-queue while stop() also touches it on the queue. Please move cancel/assign/schedule into audioSessionQueue.async.

🔒 Proposed fix
         if reason == .routeConfigurationChange {
-            // Cancel any pending debounced refresh
-            stereoRefreshWorkItem?.cancel()
-            // Create a new debounced work item
-            let workItem = DispatchWorkItem { [weak self] in
-                self?.getAudioDeviceModule().refreshStereoPlayoutState()
-                self?.log("Executed debounced refreshStereoPlayoutState")
-            }
-            stereoRefreshWorkItem = workItem
-            // Schedule the work item after debounce interval
-            audioSessionQueue.asyncAfter(deadline: .now() + Constants.stereoRefreshDebounceSeconds, execute: workItem)
+            audioSessionQueue.async { [weak self] in
+                guard let self = self else { return }
+                // Cancel any pending debounced refresh
+                self.stereoRefreshWorkItem?.cancel()
+                // Create a new debounced work item
+                let workItem = DispatchWorkItem { [weak self] in
+                    self?.getAudioDeviceModule().refreshStereoPlayoutState()
+                    self?.log("Executed debounced refreshStereoPlayoutState")
+                }
+                self.stereoRefreshWorkItem = workItem
+                // Schedule the work item after debounce interval
+                self.audioSessionQueue.asyncAfter(
+                    deadline: .now() + Constants.stereoRefreshDebounceSeconds,
+                    execute: workItem
+                )
+            }
         }

457-466: Avoid fatalError in teardown/bridge edge cases.

A missing bridge/module during teardown will crash. Consider returning an optional and logging, then safely no-op at call sites.

🧯 Proposed approach
-    private func getAudioDeviceModule() -> AudioDeviceModule {
-        guard let bridge = self.bridge else {
-            fatalError("StreamInCallManager: RCTBridge is not available yet.")
-        }
-
-        guard let webrtcModule = bridge.module(forName: "WebRTCModule") as? WebRTCModule else {
-            fatalError("WebRTCModule is required but not registered with the bridge")
-        }
-
-        return webrtcModule.audioDeviceModule
-    }
+    private func getAudioDeviceModule() -> AudioDeviceModule? {
+        guard let bridge = self.bridge else {
+            log("StreamInCallManager: RCTBridge is not available yet.")
+            return nil
+        }
+
+        guard let webrtcModule = bridge.module(forName: "WebRTCModule") as? WebRTCModule else {
+            log("WebRTCModule is required but not registered with the bridge")
+            return nil
+        }
+
+        return webrtcModule.audioDeviceModule
+    }

Then update call sites (e.g., getAudioDeviceModule()?.reset()).

packages/react-native-sdk/src/utils/internal/registerSDKGlobals.ts (1)

6-19: Wrap native module calls in try/catch to prevent native exceptions from crashing JS.

Even if the module is bundled, native calls can still throw (e.g., audio session failures). The utils guidelines require try/catch for native module calls; consider routing errors through the existing logger (or gating console output with __DEV__) to avoid production noise.

🛠️ Suggested defensive handling
 const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
   callManager: {
     setup: ({ default_device }) => {
-      StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
-        default_device,
-      );
-      StreamInCallManagerNativeModule.setup();
+      try {
+        StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
+          default_device,
+        );
+        StreamInCallManagerNativeModule.setup();
+      } catch (error) {
+        if (__DEV__) {
+          console.warn(
+            '[StreamVideoRN] Failed to setup in-call manager:',
+            error,
+          );
+        }
+      }
     },
     start: () => {
-      StreamInCallManagerNativeModule.start();
+      try {
+        StreamInCallManagerNativeModule.start();
+      } catch (error) {
+        if (__DEV__) {
+          console.warn('[StreamVideoRN] Failed to start in-call manager:', error);
+        }
+      }
     },
     stop: () => {
-      StreamInCallManagerNativeModule.stop();
+      try {
+        StreamInCallManagerNativeModule.stop();
+      } catch (error) {
+        if (__DEV__) {
+          console.warn('[StreamVideoRN] Failed to stop in-call manager:', error);
+        }
+      }
     },
   },
 };

As per coding guidelines.

Run this to review existing native-module error-handling patterns in utils and align with the preferred logger:

#!/bin/bash
rg -n "NativeModules" packages/react-native-sdk/src/utils
🧹 Nitpick comments (1)
packages/react-native-sdk/ios/StreamInCallManager.swift (1)

97-100: Prefer non-optional audio session config variables.

These values are always assigned in each branch; IUOs add avoidable runtime risk and deviate from idiomatic Swift. Consider plain let for compile-time safety.

♻️ Proposed change
-            let intendedCategory: AVAudioSession.Category!
-            let intendedMode: AVAudioSession.Mode!
-            let intendedOptions: AVAudioSession.CategoryOptions!
+            let intendedCategory: AVAudioSession.Category
+            let intendedMode: AVAudioSession.Mode
+            let intendedOptions: AVAudioSession.CategoryOptions

# Conflicts:
#	packages/client/src/types.ts
#	sample-apps/react-native/dogfood/ios/Podfile.lock
Run cancel/create/schedule on audioSessionQueue to avoid races with stop.
@santhoshvai santhoshvai merged commit 2938037 into main Jan 20, 2026
19 checks passed
@santhoshvai santhoshvai deleted the stereo branch January 20, 2026 17:13
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.

3 participants