Skip to content

feat: configure the iOS audio session natively in the default path#434

Merged
davidliu merged 9 commits into
mainfrom
hiroshi/native-audio-session
Jul 23, 2026
Merged

feat: configure the iOS audio session natively in the default path#434
davidliu merged 9 commits into
mainfrom
hiroshi/native-audio-session

Conversation

@hiroshihorie

@hiroshihorie hiroshihorie commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

setupIOSAudioManagement's default path now pushes the audio-session policy to native via AudioDeviceModule.setAutomaticAudioSessionConfiguration() instead of registering willEnable/didDisable JS handlers, so the audio engine's worker thread no longer round-trips to JS in the stock path. That round trip could stall or deadlock engine operations when the JS thread was itself blocked in a synchronous bridge call (livekit/react-native-webrtc#89). Custom configurations via onConfigureNativeAudio keep the JS handler path, still bounded by the native wait timeout.

  • Repeated setup calls supersede the previous setup by claiming a token (stale cleanup functions become no-ops), and each path arms its own mechanism before tearing down the other's so the engine hooks stay owned throughout a switch.
  • Switching paths mid-call is documented as unsupported: switching away from a custom setup mid-call abandons the audio session activation its handlers took, and with the shared activation count nobody releases it. Switch while disconnected.
  • The onConfigureNativeAudio contract is documented: it runs under a bounded native wait and must not call into the WebRTC engine or a peer connection.
  • Example Podfile.lock synced with the WebRTC-SDK 144.7559.10 pin. Changeset included (minor).

Dependency

Depends on livekit/react-native-webrtc#96. CI here installs @livekit/react-native-webrtc from the registry, which does not yet ship setAutomaticAudioSessionConfiguration, so typecheck stays red until that PR merges, a release is cut, and the dependency here is bumped. Local verification was done against the linked fork.

Testing

  • tsc clean for src/ against the linked fork branch (including the fork's literal-union types).
  • Device smoke test (iPhone, iOS 26.5.2) with the example app: connect, publish mic (duplex engine recreate), mute toggles, disconnect. No freeze, zero bounded-wait timeouts or native config failures in the unified log, and the system audio records show the session ran with the default policy and ended inactive at teardown.

Before undrafting

setupIOSAudioManagement's default path now pushes the audio-session
policy to native via
AudioDeviceModule.setAutomaticAudioSessionConfiguration() instead of
registering willEnable/didDisable JS handlers, so the audio engine's
worker thread no longer round-trips to JS in the stock path. That round
trip could stall or deadlock engine operations when the JS thread was
itself blocked in a synchronous bridge call. Custom configurations
(onConfigureNativeAudio) keep the JS handler path, still bounded by the
native wait timeout.

Repeated setup calls and switches between the default and custom paths
are safe: each setup supersedes the previous one by claiming a token,
stale cleanup functions become no-ops, and each path arms its own
mechanism before tearing down the other's so the engine hooks stay
owned throughout a switch.
Spell out that the custom callback runs inside the audio engine's
lifecycle callbacks under a bounded native wait, so it must return
quickly and must not call into the WebRTC engine or a peer connection.
Doing so can block on the very engine operation the callback is holding
up, stalling it until the wait times out.

Also note that the default path involves no JavaScript per transition,
that repeated setup calls supersede the previous one, and that switching
paths is best done while disconnected.
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0b0339e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@livekit/react-native Minor

Not sure what this means? Click here to learn what changesets are.

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

@hiroshihorie
hiroshihorie requested a review from davidliu July 9, 2026 16:14
The previous wording claimed a switch during an active call takes full
effect from the next engine transition. That undersold the failure
mode: switching away from a custom setup mid-call abandons the audio
session activation its handlers took, and with the shared activation
count nobody releases it, so the session can stay active after the call
ends. State it as unsupported with the concrete consequence.
davidliu added a commit to livekit/react-native-webrtc that referenced this pull request Jul 23, 2026
…er (#96)

## Summary

Adds an iOS-only API that pushes the default audio-session policy to
native once. When no custom JS handlers are registered,
`willEnableEngine` and `didDisableEngine` now configure `AVAudioSession`
on the native worker thread. This removes the JS round trip from the
default path and the circular-wait risk described in #89.

Custom JS handlers still take precedence and keep the bounded timeout.
Native activation tracking handles normal stop, interruptions, and
external deactivation. Switching between native and custom management
during an active call remains unsupported. Switch while disconnected.

## Testing

- Built for iOS device and simulator with Xcode 26.6 and WebRTC-SDK
144.7559.10
- Smoke tested on device with CallKit and RNCallKeep through connect,
mic publish, mute, and disconnect
- Observed no freeze, bridge-wait timeout, or native configuration
failure
- Confirmed the audio session returned inactive after teardown

Current iOS CI failure is unrelated to this change. The React Native
0.71 example fails while compiling Yoga after `macos-latest` moved to
macOS 26, before the native code from this PR is compiled.

## Before undrafting

- [ ] Add a regression test for the #89 circular-wait shape
- [ ] Repeat the device smoke test with the final decision logs

Companion PR: livekit/client-sdk-react-native#434

---------

Co-authored-by: davidliu <davidliu@deviange.net>
@davidliu
davidliu marked this pull request as ready for review July 23, 2026 14:02
@davidliu
davidliu requested a review from xianshijing-lk as a code owner July 23, 2026 14:02
devin-ai-integration[bot]

This comment was marked as resolved.

@davidliu
davidliu merged commit 548794c into main Jul 23, 2026
5 checks passed
@davidliu
davidliu deleted the hiroshi/native-audio-session branch July 23, 2026 15:00
@github-actions github-actions Bot mentioned this pull request Jul 23, 2026
davidliu pushed a commit that referenced this pull request Jul 23, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @livekit/react-native@2.12.0

### Minor Changes

- Configure the iOS audio session natively in the default path, removing
the audio engine's JS round trip and its deadlock window. Deprecate the
unsafe `onConfigureNativeAudio` callback form of
`setupIOSAudioManagement`. Developers can instead pass in a static
`IOSAudioSessionPolicy`. -
[#434](#434)
([@hiroshihorie](https://github.com/hiroshihorie))

### Patch Changes

- Update @livekit/react-native-webrtc to 144.1.2 -
[#434](#434)
([@hiroshihorie](https://github.com/hiroshihorie))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

2 participants