Swift platform behavior
What the SDK does to the audio device on macOS and iOS, and the platform work your app still owns — permissions, prewarming, volume, and route changes.
A voice session on Apple platforms shares one microphone and one output route with the rest of the system. The SDK handles most of it, but the parts that aren't are the ones that produce bug reports: a first session that takes a beat to publish, a volume slider that won't reach silence, or unrelated audio ducking while a session runs.
Who owns the audio session
The SDK doesn't configure AVAudioSession. LiveKit owns the whole audio path — microphone capture, Opus encoding, upstream publication, and remote track playback — through its shared AudioManager. There's no category, mode, or activation call for you to make, and setting one yourself can fight the SDK for the device.
An iOS app that must own AVAudioSession itself — one running its own capture or playback engine alongside the SDK — opts out of that management once, before the first start: RealtimeSession.setAutomaticAudioSessionManagement(enabled: false). From then on the category, mode, route, and activation are the app's to manage; see the session reference. macOS has no AVAudioSession, so the call is compiled out there.
The websocket transport has no LiveKit to hand this to, so on iOS it does the same work itself — .playAndRecord in .voiceChat mode, activated for the call and released at the end. It reads the same opt-out, so an app that took ownership keeps it on either transport.
What that leaves you is the platform-level work Apple requires of the app itself, plus a few knobs the SDK exposes on top.
Microphone permission
Add NSMicrophoneUsageDescription to your app's Info.plist. iOS won't prompt without it, and the app terminates on the first capture attempt.
Request the permission before you start a session or prewarm the engine. A prewarm no-ops when permission isn't yet authorized, so one issued from a start-up path that runs before the prompt does nothing and the first session pays full cold-start cost anyway.
Engine prewarm
Warming the OS audio engine ahead of a connect keeps the first mic publish off the cold-start path.
// OS audio engine, so the first mic publish doesn't pay cold start-up.
// Gate this on the permission state — it no-ops when unauthorized.
MicPrewarmCoordinator.set(true)MicPrewarmCoordinator serializes those transitions FIFO so the last caller wins. That ordering matters when a prewarm issued on one user gesture races a release from another — without the chain, a late-landing prewarm can leave voice processing engaged on an idle app.
If you open the input device through your own engine, await MicPrewarmCoordinator.settle() first. Once it returns, the last requested state is actually in effect, which is the guarantee you need before touching the device yourself.
Echo cancellation differs by platform
On iOS the SDK takes Apple's platform voice processing. On macOS the WebRTC transport forces WebRTC's software echo cancellation, gain control, and noise suppression instead.
The reason is that a live platform Voice Processing I/O unit takes over the shared system audio device. For as long as it's engaged it reroutes every app's output through the voice-comm path, not just yours — so a running session audibly ducks unrelated playback. Software processing is in-process DSP over already-captured samples; it never touches the device route, so it leaves everything else on the machine alone.
This is a property of the platform unit rather than of any one feature's wiring. Ducking level tuning only narrows how much platform voice processing ducks, not whether it takes the device over in the first place.
The websocket transport is the exception: it has no WebRTC stack to borrow software processing from, so its microphone uses platform voice processing on macOS too — echo cancellation with gain control disabled. While one of its sessions captures audio, other apps' playback ducks the way any voice-processing app's would; on macOS 14 and iOS 17 or later the SDK holds that ducking at its minimum, and earlier versions have no control to set.
Playback volume on iOS
Voice sessions live in iOS's call-volume domain, whose hardware slider bottoms out above silence — a call can't be rocker-muted. If your UI promises that dragging a slider to the floor means silent, you have to implement that yourself:
// Observe the system call volume and drive the agent track to match.
session.setAgentPlaybackVolume(volume) // 0.0 ... 1.0The value applies to the agent's track and is re-applied to any track that attaches later, so a reconnect or a late agent join keeps the level you set.
Platform requirements
The package supports macOS 13 and iOS 16 or later, and builds with Swift 5.9. See the Swift quickstart for the package dependency and a first session.
Packaging a macOS app
Embedding LiveKit's binary frameworks, the rpath, signing order, entitlements, and the microphone purpose string — what `swift build` alone does not do.
Handle tool calls
Observe the tool-call, tool-dispatch-started, and tool-result lifecycle. Render a tool-call timeline in TypeScript, Python, and Swift.