Skip to content

feat: add iOS audio output selection methods#2334

Open
santhoshvai wants to merge 13 commits into
mainfrom
speaker-dialog-ios
Open

feat: add iOS audio output selection methods#2334
santhoshvai wants to merge 13 commits into
mainfrom
speaker-dialog-ios

Conversation

@santhoshvai

@santhoshvai santhoshvai commented Jul 15, 2026

Copy link
Copy Markdown
Member

💡 Overview

Adds a single, cross-platform audio output device picker for the React Native SDK.
One API — callManager.audioDevices and the useAudioDeviceStatus() hook

📝 Implementation notes

Changed method names and behaviour:

  • callManager.androidcallManager.audioDevices.
  • getAudioDeviceStatus()getStatus(); selectAudioDevice(name)select(deviceId);
    addAudioDeviceChangeListener()addChangeListener().
  • AudioDeviceStatus type removed → use AudioDevicesState + AudioDevice.
  • Selection is now by stable id, not display name.

🎫 Ticket: https://linear.app/stream/issue/XYZ-123

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

Summary by CodeRabbit

  • New Features
    • Added a cross-platform audio device picker and real-time device state hook via useAudioDeviceStatus.
    • Introduced the didChangeAudioRoute event for audio-route updates.
  • Bug Fixes
    • Improved Bluetooth audio routing/selection by using stable endpoint/device IDs instead of names.
    • Prevented stale route monitoring by managing the iOS route observer lifecycle more reliably.
  • API / Behavior Updates
    • Audio device APIs/state now use AudioDevicesState/AudioDevice with selectedDeviceId.
    • Native/bridge methods now select endpoints by device ID, plus added reapplyAudioRoute.
    • Removed the prior “audio endpoints snapshot” event payload behavior; route events are now used instead.
  • Tests
    • Updated Android Telecom-related tests for the new AudioDevicesState shape.

santhoshvai and others added 4 commits June 12, 2026 09:04
# Conflicts:
#	packages/react-native-callingx/ios/AudioSessionManager.swift
#	packages/react-native-sdk/src/modules/call-manager/CallManager.ts
#	packages/react-native-sdk/src/modules/call-manager/native-module.d.ts
#	sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/AndroidAudioRoutePickerDrawer.tsx
#	sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/AndroidAudioRoutePickerDrawer.tsx
#	sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/AudioRoutePickerDrawer.tsx
Superseded by this branch's iOS audio device selection work; merged with
strategy=ours to preserve his contribution in history without changing the
current implementation.

Co-Authored-By: ajit <ajitpatel7389@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR unifies audio-device state and selection across Android and iOS using stable device IDs. It adds native route-change events, iOS route persistence and reapplication, Android ID-based endpoint handling, a shared CallManager API, a status hook, updated tests, and a cross-platform route picker.

Changes

Audio device contracts and manager

Layer / File(s) Summary
Shared audio-device state and orchestration
packages/react-native-sdk/src/modules/call-manager/*, packages/react-native-sdk/src/hooks/*, packages/react-native-callingx/src/*
Replaces name-based status models with structured device objects, stable IDs, normalized listeners, CallManager.audioDevices, and useAudioDeviceStatus; callingx route events now pass raw parameters and expose updated event typings.

Android stable-id routing

Layer / File(s) Summary
Android endpoint identity and selection
packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/*, packages/react-native-callingx/android/src/main/java/io/getstream/rn/callingx/*
Uses Bluetooth and non-Bluetooth endpoint IDs for lookup, switching, change detection, status payloads, SCO recovery, and signal-only route broadcasts.

iOS route selection and events

Layer / File(s) Summary
iOS native routing and CallKit lifecycle
packages/react-native-sdk/ios/*, packages/react-native-callingx/ios/*
Adds iOS device-picker bridge methods, persistent output routing, route reapplication, native audio-device events, and CallKit route-observer lifecycle management.

Picker integration and validation

Layer / File(s) Summary
Unified picker and CallManager tests
sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/*, packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts
Uses the shared hook and ID-based selection in the picker, removes platform branching, and updates Telecom tests for AudioDevicesState.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Picker
  participant CallManager
  participant NativeManager
  participant AVAudioSession
  Picker->>CallManager: audioDevices.select(deviceId)
  CallManager->>NativeManager: chooseAudioDeviceEndpoint(deviceId)
  NativeManager->>AVAudioSession: apply output routing
  AVAudioSession-->>CallManager: audioDeviceChanged
  CallManager-->>Picker: updated AudioDevicesState
Loading

Possibly related PRs

Suggested reviewers: oliverlaz, greenfrvr

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% 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 matches a real part of the PR, though the change is broader than iOS-only methods.
Description check ✅ Passed The description matches the template with Overview, Implementation notes, Ticket, and Docs sections filled in.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch speaker-dialog-ios

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.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bundle size

Built package output. Sizes in KB; delta vs main@ccb2273.

Package Unminified Minified Δ min vs main
@stream-io/video-react-native-sdk 406.6 KB 193.8 KB +1.1 KB (+0.6%)
↳ install total (+ client + react-bindings) 1179.7 KB 466.4 KB +1.1 KB (+0.2%)
@stream-io/react-native-callingx 16.0 KB 7.0 KB -217 B (-2.9%)

@santhoshvai
santhoshvai marked this pull request as ready for review July 17, 2026 12:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

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/audio/AudioDeviceManager.kt (2)

452-458: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the explicit user selection across device-state updates.

After selecting speaker or earpiece, any later endpoint update recomputes WH → BT → default and overwrites that choice. Retain the selected device ID until it disconnects, then fall back to automatic priority routing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt`
around lines 452 - 458, Update the device selection logic around newAudioDevice
to retain the explicitly selected speaker or earpiece ID across endpoint
updates. Reuse the stored selection while that device remains connected; clear
it when disconnected, then preserve the existing WH → BT → default automatic
priority routing.

257-260: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle setCommunicationDevice() failures before updating the selected device

setCommunicationDevice(...) returns false when the system rejects the route. Don’t assign selectedAudioDeviceEndpoint unless the call succeeds; otherwise JS can show a Bluetooth device that was never activated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt`
around lines 257 - 260, Update the Android 31+ branch in the audio device
selection flow to check the boolean result from setCommunicationDevice before
calling bluetoothManager.updateDevice() or assigning
selectedAudioDeviceEndpoint. Only update and select btDevice when the
communication route is successfully accepted; preserve the existing behavior for
failed calls without marking the device active.
🧹 Nitpick comments (1)
packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts (1)

78-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the non-Telecom and iOS branches of the public API.

Add tests for native getStatus/select, didChangeAudioRoute refresh and cleanup, and rejected status reads. Current coverage validates only the Android Telecom branch of this cross-platform manager.

As per coding guidelines, “add tests for new public APIs, bug fixes, and performance-sensitive utilities.”

Also applies to: 202-226

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts` around
lines 78 - 113, Expand the CallManager audioDevices tests beyond the Android
Telecom path: cover native getStatus and select behavior, iOS routing,
didChangeAudioRoute-triggered refresh and listener cleanup, and rejected status
reads. Reuse the existing loadCallManager, makeNativeManager, and makeCallingx
fixtures while asserting the public API results and relevant native/CallingX
calls.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioManagerUtil.kt`:
- Around line 111-117: The Bluetooth endpoint resolution in the relevant
AudioManagerUtil method must rely exclusively on
audioManager.communicationDevice.id and never fall back to matching
bluetoothManager.getDeviceName(). Return null when the active ID is absent or
not present in endpointMaps.bluetoothEndpoints, while preserving any
caller-provided requested ID in the corresponding logic around the additionally
referenced lines.

In `@packages/react-native-sdk/ios/StreamInCallManager.swift`:
- Around line 547-570: Update buildAudioDevicesState to include active output
routes alongside selectable inputs, deduplicating devices that represent the
same route. Derive selectedDeviceId from the active route’s output when present,
while preserving the speaker identifier and input-based selection for routes
without a distinct output device; ensure wired and other output-only devices
appear in devices.
- Around line 420-434: Update setForceSpeakerphoneOn’s audio-session branch to
route both enabled and disabled states through applyOutputRouting(...), ensuring
that helper also updates selectedOutput consistently. Remove the direct
overrideOutputAudioPort and setPreferredInput calls from this path while
preserving the existing configuration locking and error logging.
- Around line 464-526: Update chooseAudioDeviceEndpoint so the requested routing
is validated and applied before assigning it to selectedOutput. Keep the
previous selectedOutput unchanged when setConfiguration or applyOutputRouting
fails, and commit the new routing only after both operations succeed; ensure
makeAudioConfiguration and applyConfigForEngineEnable continue using only
successfully applied routes.

In `@packages/react-native-sdk/src/modules/call-manager/CallManager.ts`:
- Around line 112-120: Wrap the native calls introduced in
ensureInterruptionReassert and the related handlers around
chooseAudioDeviceEndpoint, reapplyAudioRoute, and default-endpoint forwarding in
try-catch blocks, and handle any returned promise rejections. Keep the existing
event/action behavior while preventing bridge errors from escaping unhandled.
- Around line 204-214: Make asynchronous audio-status updates lifecycle-safe in
packages/react-native-sdk/src/modules/call-manager/CallManager.ts lines 204-214
and packages/react-native-sdk/src/hooks/useAudioDeviceStatus.ts lines 26-35. In
the CallManager route listener, track subscription activity so callbacks after
unsubscribe are ignored, and handle getStatus() rejections. In
useAudioDeviceStatus, prevent the initial request from replacing newer
event-driven state, ignore all updates after unmount, wrap native calls in
try-catch, and handle promise rejections.

In `@packages/react-native-sdk/src/modules/call-manager/types.ts`:
- Around line 12-16: Update the device identity documentation around
AudioDevice.id to distinguish Android sources: specify that Telecom devices use
Callingx endpoint IDs, while AudioDeviceInfo.id applies only to SDK-managed
Android routing. Preserve the existing iOS ID descriptions and the stable-ID
guidance.

---

Outside diff comments:
In
`@packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt`:
- Around line 452-458: Update the device selection logic around newAudioDevice
to retain the explicitly selected speaker or earpiece ID across endpoint
updates. Reuse the stored selection while that device remains connected; clear
it when disconnected, then preserve the existing WH → BT → default automatic
priority routing.
- Around line 257-260: Update the Android 31+ branch in the audio device
selection flow to check the boolean result from setCommunicationDevice before
calling bluetoothManager.updateDevice() or assigning
selectedAudioDeviceEndpoint. Only update and select btDevice when the
communication route is successfully accepted; preserve the existing behavior for
failed calls without marking the device active.

---

Nitpick comments:
In `@packages/react-native-sdk/__tests__/call-manager/CallManager.test.ts`:
- Around line 78-113: Expand the CallManager audioDevices tests beyond the
Android Telecom path: cover native getStatus and select behavior, iOS routing,
didChangeAudioRoute-triggered refresh and listener cleanup, and rejected status
reads. Reuse the existing loadCallManager, makeNativeManager, and makeCallingx
fixtures while asserting the public API results and relevant native/CallingX
calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45b72234-211c-4ca5-b3af-74d69d94eaaf

📥 Commits

Reviewing files that changed from the base of the PR and between 910a5de and 6a31adc.

⛔ Files ignored due to path filters (1)
  • sample-apps/react-native/dogfood/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • packages/react-native-callingx/ios/AudioSessionManager.swift
  • packages/react-native-callingx/ios/CallingxImpl.swift
  • packages/react-native-callingx/src/types.ts
  • packages/react-native-sdk/__tests__/call-manager/CallManager.test.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/AudioManagerUtil.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/hooks/index.ts
  • packages/react-native-sdk/src/hooks/useAudioDeviceStatus.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
  • sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/AudioRoutePickerDrawer.tsx
  • sample-apps/react-native/dogfood/src/components/CallControls/MoreActionsButton/index.tsx

Comment thread packages/react-native-sdk/ios/StreamInCallManager.swift
Comment thread packages/react-native-sdk/ios/StreamInCallManager.swift Outdated
Comment thread packages/react-native-sdk/ios/StreamInCallManager.swift Outdated
Comment thread packages/react-native-sdk/src/modules/call-manager/CallManager.ts
Comment thread packages/react-native-sdk/src/modules/call-manager/CallManager.ts Outdated
Comment thread packages/react-native-sdk/src/modules/call-manager/types.ts Outdated
Comment thread packages/react-native-callingx/src/types.ts
Comment thread packages/react-native-sdk/src/modules/call-manager/types.ts
Comment thread packages/react-native-sdk/src/hooks/useAudioDeviceStatus.ts Outdated
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

# Conflicts:
#	packages/react-native-callingx/ios/CallingxImpl.swift
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