Skip to content

Add isAutomaticActivationEnabled for CallKit and LiveCommunicationKit - #1167

Open
longwQaQ wants to merge 2 commits into
livekit:mainfrom
longwQaQ:audio-session-automatic-activation
Open

longwQaQ wants to merge 2 commits into
livekit:mainfrom
longwQaQ:audio-session-automatic-activation

Conversation

@longwQaQ

Copy link
Copy Markdown

isAutomaticConfigurationEnabled controls two things at once: configuring the audio session (category, mode, options, IO buffer duration) and activating it. Deactivation already has its own switch, isAutomaticDeactivationEnabled. Activation does not.

With CallKit or LiveCommunicationKit the system activates and deactivates the session (provider(_:didActivate:), conversationManager(_:didActivate:)). Today the only option for these apps is to turn off automatic configuration completely and set the category themselves in didActivate, as the README shows with .playAndRecord / .voiceChat.

That hard-coded configuration can't follow what the SDK selects internally. Since #1072 the SDK uses the media-tuned presets when software voice processing is used, and the selection depends on internal state (isPlatformVoiceProcessingExpected, hasRecorded) that is not public (see the discussion in #1086). So CallKit apps can end up with a different mode than the SDK expects, for example quiet remote audio with software voice processing.

Changes

  • Add AudioSessionEngineObserver.isAutomaticActivationEnabled, default true. When false, the SDK still configures category, mode, options and IO buffer duration, but does not call setActive(true). The skip is logged the same way as the existing deactivation skip.
  • Update the CallKit section in the README and Docs/audio.md: keep automatic configuration on, set isAutomaticActivationEnabled = false and isAutomaticDeactivationEnabled = false, and only call setEngineAvailability from the activation callbacks. Mention that the same setup works with LiveCommunicationKit.
  • isAutomaticConfigurationEnabled = false stays documented for apps that want full manual control.

The default is true, so nothing changes for existing apps. This only adds API.

Making activation a separate switch, rather than exposing the voice processing state, keeps the configuration logic internal, and it mirrors the existing deactivation switch.

Test

  • AudioSessionCategorySelectionTests and the new AudioSessionAutomaticActivationTests pass on the iOS Simulator.
  • Steps to verify on a device (iPhone 17 Pro, iOS 27.0.1) with LiveCommunicationKit or CallKit, using the setup from the updated README:
    • incoming call answered from the lock screen and in the foreground
    • outgoing call
    • mute / unmute, speaker / receiver switch
    • call ended by either side; the session is deactivated by the system and other audio resumes

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +50 to +52
public var isAutomaticActivationEnabled: Bool {
get { _state.isAutomaticActivationEnabled }
set { _state.mutate { $0.isAutomaticActivationEnabled = newValue } }

@devin-ai-integration devin-ai-integration Bot Oct 22, 2026 •

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.

🟡 Active calls cannot regain SDK activation

When an app restores isAutomaticActivationEnabled during an active call, the SDK does not activate the session after CallKit releases it. The activation gate runs only when requirements change from empty to nonempty, so audio stays silent until they fully reset.

Learn more

Session requirements describe whether the SDK's audio engine needs an active session. The activation gate in configureAudioSession runs only on a transition from no requirements to some. Changing the public flag while requirements persist changes the stored value but does not invoke configuration or activate the session. If an app transfers an ongoing call from CallKit-managed activation to SDK-managed activation, CallKit can deactivate the session while the SDK still has requirements, leaving the engine without audio.

Example: A call starts with isAutomaticActivationEnabled = false; its playback requirement remains held. The app sets the flag to true to continue playback after ending the CallKit call. CallKit deactivates the session, but the SDK never calls setActive(true) for that requirement.

Recommended fix: Decide and document whether activation ownership can change while requirements are held. If supported, track actual activation ownership and handle the handoff explicitly; a setter alone cannot reactivate after a later external deactivation without a handoff signal.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@longwQaQ longwQaQ Oct 22, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

On activation during a call: like isAutomaticConfigurationEnabled, this is meant to be set before connecting, and handing activation over in the middle of a call is not supported. Documented in cc7f717.

On Objective-C: AudioSessionEngineObserver is not exposed to Objective-C today, including the existing isAutomaticConfigurationEnabled and isAutomaticDeactivationEnabled, so this follows the same pattern. Happy to look at an Objective-C entry point separately if that is wanted.

Comment on lines +50 to +52
public var isAutomaticActivationEnabled: Bool {
get { _state.isAutomaticActivationEnabled }
set { _state.mutate { $0.isAutomaticActivationEnabled = newValue } }

@devin-ai-integration devin-ai-integration Bot Oct 22, 2026 •

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.

🟡 Objective-C apps cannot configure activation ownership

Objective-C CallKit apps cannot set isAutomaticActivationEnabled, because its containing class is not exposed to Objective-C. The new public switch has no Objective-C-accessible alternative.

Learn more

The repository's public API rule calls for Objective-C support. This property is declared on AudioSessionEngineObserver, a Swift-only class, and AudioManager.audioSession also belongs to a Swift-only class. Merely adding @objc to the property cannot expose the containing object to Objective-C.

Example: An Objective-C app using CXProviderDelegate cannot obtain AudioManager.shared.audioSession to disable automatic activation, although a Swift app can.

Recommended fix: Expose a small Objective-C-compatible facade for the audio session switches and make it reachable from Objective-C, or document an approved exception to the public API rule.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@longwQaQ

Copy link
Copy Markdown
Author

Device test results:

Tested on an iPhone 17 Pro running iOS 27.0.1 with three outgoing CallKit calls per version, using the default .voiceProcessing mute mode and an unmuted microphone. On this branch I set both automatic activation and automatic deactivation to false before connecting, kept automatic configuration enabled, and only called setEngineAvailability(.default) from didActivate. All three calls used the SDK-selected .playAndRecord / .videoChat configuration, produced capture and playback callbacks, and logged AudioSession activation skipped... without an SDK activation attempt. The test tone was confirmed audible through AirPods Pro during the first call. Each hangup logged AudioSession deactivation skipped..., received the system didDeactivate callback, and stopped recording and playback. Main also produced audio callbacks in three calls using its existing README setup, with the app configuring the session itself. This validates the new separation of configuration from activation; it does not demonstrate that the existing integration was unable to make calls.

@pblazej

pblazej commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@hiroshihorie deferring that as you're working on this API part.

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