Skip to content

Keep the user's audio route when the session configuration is unchanged - #1166

Closed
longwQaQ wants to merge 2 commits into
livekit:mainfrom
longwQaQ:audio-session-skip-redundant-configuration
Closed

longwQaQ wants to merge 2 commits into
livekit:mainfrom
longwQaQ:audio-session-skip-redundant-configuration

Conversation

@longwQaQ

@longwQaQ longwQaQ commented Oct 22, 2026 •

Copy link
Copy Markdown

AudioSessionEngineObserver.configureAudioSession calls setCategory(_:mode:options:) and setPreferredIOBufferDuration(_:) on every requirement change: muting or unmuting the mic, enabling or disabling it, engine restarts and reconnects. It also calls them on every isSpeakerOutputPreferred or voice processing change.

Setting the category again makes iOS re-evaluate the route, even when the category, mode and options are the same as before. So an output the user picked with AVRoutePickerView or Control Center (receiver, speaker, a Bluetooth device) gets dropped the next time the requirements change. For example, the user switches to the receiver during a call, mutes the mic, and the audio goes back to the speaker. The speaker presets include .defaultToSpeaker, so it is most visible there.

client-sdk-flutter#1227 describes a similar conflict between a user-selected route and the engine re-applying its session configuration.

Changes

  • Only call setCategory when the session's current category, mode or options differ from the selected configuration (needsCategoryUpdate).
  • Voice Processing I/O can switch the speaker preset's .videoChat to .voiceChat by itself (noted in AudioSessionConfiguration), so the two chat modes count as the same mode in that check. A speaker preference change still differs in .defaultToSpeaker, and a voice processing path change still differs in mode (.default vs the chat modes), so both are still applied.
  • Only call setPreferredIOBufferDuration when the preferred duration differs from WebRTC's value.

The selected configuration, requirement tracking, activation and deactivation are unchanged, and there is no public API change. If iOS reports the options differently from how they were set, the check sees a difference and the category is set again, which is the current behaviour.

Test

  • AudioSessionCategorySelectionTests and the new AudioSessionCategoryUpdateTests pass on the iOS Simulator.
  • Steps to verify on a device (iPhone 17 Pro, iOS 27.0.1), in a room with the mic published:
    1. Pick the receiver (or a Bluetooth headset) in AVRoutePickerView.
    2. Mute and unmute the mic a few times.
    3. Expected: the output stays on the picked route instead of going back to the speaker.

devin-ai-integration[bot]

This comment was marked as resolved.

@longwQaQ

Copy link
Copy Markdown
Author

Device test results:

Tested on an iPhone 17 Pro running iOS 27.0.1, using the default audio processing options and an AirPods Pro route selected through AVRoutePickerView. Across three repetitions, main logged a category configuration for each mute/unmute in .restart mode and each microphone unpublish/republish; this branch skipped those redundant writes. .voiceProcessing and .inputMixer mute/unmute did not reconfigure the session on either version. Changing isSpeakerOutputPreferred still applied the new mode/options on this branch. Three brief Wi-Fi interruptions recovered through quick reconnect without additional category writes or changing the Bluetooth route on either version. I did not reproduce a switch back to the speaker on main, and the mode stayed .videoChat until I explicitly changed the speaker preference. The route picker did not expose a separate receiver option with the default configuration. This confirms the reduction in redundant writes, but does not establish the full route-reset reproduction on this device.

@longwQaQ

Copy link
Copy Markdown
Author

Closing this for now. On a device the redundant writes go away, but I couldn't reproduce the route actually being reset on main, so there's no user-facing problem to point at yet. I'll reopen with a reproduction if I find one.

@longwQaQ longwQaQ closed this Oct 22, 2026
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.

1 participant