AudioPlayback
AudioPlayback is part of the AgentSquadAudio product. It accepts PCM16 @ 24 kHz frames, converts them to float32, and schedules them on an AVAudioPlayerNode for continuous playback. flush() provides an instant barge-in cut by discarding all buffered audio.
import AgentSquadAudiopublic init( sampleRate: Double = 24_000, sessionPolicy: AudioSessionPolicy = .managed, configureEngine: (@Sendable (AVAudioEngine) throws -> Void)? = nil)| Parameter | Default | Notes |
|---|---|---|
sampleRate | 24_000 | Sample rate in Hz for the internal float32 AVAudioFormat. Must match the PCM16 frames the runtime will enqueue. |
sessionPolicy | .managed | Who configures the AVAudioSession — pass the same policy as MicCapture. See AudioSessionPolicy. |
configureEngine | nil | Escape hatch: runs with the raw AVAudioEngine after the player node is attached, before the engine starts. Do not enable voice processing here — it belongs on the capture engine (MicCapture), not playback. |
Public surface
Section titled “Public surface”public func start() async throws // attaches player node, starts engine (iOS: activates audio session)public func enqueue(_ pcm16: Data) async // schedules one PCM16 frame for playbackpublic func flush() async // discards all scheduled/playing buffers — instant barge-in cutpublic func stop() async // stops player and enginepublic func playedMilliseconds() async -> Double? // ms actually played of the current burst (survives flush)playedMilliseconds() is measured from the player’s .dataPlayedBack buffer callbacks and feeds the session’s conversation.item.truncate on barge-in — after a flush() it still reports the interrupted burst’s played total until new audio starts.
start() is idempotent — a second call before stop() is a no-op.
enqueue converts the incoming PCM16 bytes to float32 samples and schedules the resulting buffer via AVAudioPlayerNode.scheduleBuffer(_:completionHandler:). It returns immediately without waiting for the buffer to finish playing, so callers are never serialised to real-time playback speed.
flush() calls player.stop() then player.play() to discard all scheduled buffers and re-arm the player for the next enqueue call — no interruption to the downstream pipeline.
Audio session (iOS only)
Section titled “Audio session (iOS only)”On iOS, start() applies the sessionPolicy to the shared AVAudioSession. With the default .managed policy it configures:
category: .playAndRecordmode: .voiceChatoptions: [.defaultToSpeaker, .allowBluetoothHFP]If MicCapture.start() was already called first, the session is already active and this is a no-op. With .custom your closure runs instead; with .external the session is never touched (your app must configure and activate it before start()). Details on the MicCapture page. On macOS the session handling is compiled out and the system default audio device is used.
let playback = AudioPlayback()try await playback.start()
// Feed frames as they arrive from the realtime runtimeawait playback.enqueue(pcm16Frame)
// User interrupts — discard buffered audio instantlyawait playback.flush()
await playback.stop()Wiring to the voice runtime
Section titled “Wiring to the voice runtime”let runtime = RealtimeRuntime( input: MicCapture(), output: AudioPlayback(), // ... other config)The runtime drives start, enqueue, flush, and stop from its single event pump, so AudioPlayback is never called concurrently by the runtime.
Related pages
Section titled “Related pages”- Audio overview — the
AudioOutputprotocol and how it fits into the runtime - MicCapture — the companion
AudioInputbuilt-in - Custom audio — rolling your own
AudioOutputconformance - Voice overview — the
RealtimeRuntimethat consumesAudioPlayback