From 3b74084e065207a179e156f053f82c53896b0729 Mon Sep 17 00:00:00 2001 From: mattie ruth backman Date: Fri, 17 Apr 2026 16:41:14 -0400 Subject: [PATCH 1/2] Add Android views to guides --- client/concepts/events-and-callbacks.mdx | 108 ++++++++++++ client/concepts/media-management.mdx | 175 +++++++++++++++++++ client/concepts/session-lifecycle.mdx | 153 ++++++++++++++++- client/guides/building-a-voice-ui.mdx | 205 +++++++++++++++++++++++ client/guides/custom-messaging.mdx | 68 ++++++++ 5 files changed, 705 insertions(+), 4 deletions(-) diff --git a/client/concepts/events-and-callbacks.mdx b/client/concepts/events-and-callbacks.mdx index 0c921413..e5190db7 100644 --- a/client/concepts/events-and-callbacks.mdx +++ b/client/concepts/events-and-callbacks.mdx @@ -23,6 +23,11 @@ You are currently viewing the React Native version of this page. Use the dropdow You are currently viewing the iOS version of this page. Use the dropdown to the right to customize this page for your client framework. + + +You are currently viewing the Android version of this page. Use the dropdown to the right to customize this page for your client framework. + + The Pipecat client emits events throughout the session lifecycle — when the bot connects, when the user speaks, when a transcript arrives, and more. @@ -84,6 +89,12 @@ iOS uses the delegate pattern rather than constructor callbacks — see [Event l + + +Android uses `RTVIEventCallbacks` rather than a callbacks map — see [Event listeners](#event-listeners) below. + + + ### Event listeners @@ -175,6 +186,29 @@ Delegate callbacks may arrive off the main actor — always use `Task { @MainAct + + +Extend `RTVIEventCallbacks` and pass it to the `RTVIClient` constructor. Override only the methods you need: + +```kotlin +val callbacks = object : RTVIEventCallbacks() { + override fun onBotReady(data: BotReadyData) { + Log.d("Bot", "Bot is ready") + } + + override fun onUserTranscript(data: Transcript) { + if (data.final) setTranscript(data.text) + } +} + +val options = RTVIClientOptions(params = RTVIClientParams(baseUrl = null), enableMic = true) +val client = RTVIClient(SmallWebRTCTransport.Factory(context, botUrl), callbacks, options) +``` + +Callbacks run on the main thread and can update Compose `mutableStateOf` directly. + + + --- ## Event reference @@ -297,6 +331,20 @@ func onUserTranscript(data: Transcript) { + + +```kotlin +override fun onUserTranscript(data: Transcript) { + if (data.final) { + addMessage(data.text) // committed + } else { + updatePartial(data.text) // still in progress + } +} +``` + + + `BotOutput` is the recommended way to display the bot's response text. It provides the best possible representation of what the bot is saying — supporting interruptions and unspoken responses. By default, Pipecat aggregates output by sentences and words (assuming your TTS supports streaming), but custom aggregation strategies are supported too - like breaking out code snippets or other structured content: @@ -356,6 +404,18 @@ func onBotOutput(data: BotOutputData) { + + +`onBotTranscript` receives the bot's text output as it accumulates: + +```kotlin +override fun onBotTranscript(text: String) { + appendSentence(text) +} +``` + + + ### Errors | Event | Callback | When it fires | @@ -427,6 +487,16 @@ func onError(message: RTVIMessageInbound) { + + +```kotlin +override fun onBackendError(message: String) { + showError(message) +} +``` + + + ### Devices and tracks | Event | Callback | When it fires | @@ -508,6 +578,34 @@ client.registerFunctionCallHandler(functionName: "get_weather") { data, onResult + + +Override `onLLMFunctionCall` in your `RTVIEventCallbacks` to handle function calls. Invoke `onResult` with the function's return value: + +```kotlin +val callbacks = object : RTVIEventCallbacks() { + override fun onLLMFunctionCall(data: LLMFunctionCallData, onResult: (Value) -> Unit) { + if (data.functionName == "get_weather") { + val city = (data.args as? Value.Object)?.value?.get("city") + ?.let { (it as? Value.Str)?.value } ?: "" + onResult(Value.Str(fetchWeather(city))) + } + } +} +``` + +For multiple functions, use `registerFunctionCallHandler` to register per-function handlers: + +```kotlin +client.registerFunctionCallHandler("get_weather") { data, onResult -> + val city = (data.args as? Value.Object)?.value?.get("city") + ?.let { (it as? Value.Str)?.value } ?: "" + onResult(Value.Str(fetchWeather(city))) +} +``` + + + ### Other | Event | Callback | When it fires | @@ -563,3 +661,13 @@ For custom server\<-\>client messaging, see [Custom Messaging](/client/guides/cu + + + + + + Full `RTVIEventCallbacks` class and API reference + + + + diff --git a/client/concepts/media-management.mdx b/client/concepts/media-management.mdx index 0f72c9f1..bc8f2de9 100644 --- a/client/concepts/media-management.mdx +++ b/client/concepts/media-management.mdx @@ -23,6 +23,11 @@ You are currently viewing the React Native version of this page. Use the dropdow You are currently viewing the iOS version of this page. Use the dropdown to the right to customize this page for your client framework. + + +You are currently viewing the Android version of this page. Use the dropdown to the right to customize this page for your client framework. + + The Pipecat client handles media at two levels: **local devices** (the user's mic, camera, and speakers) and **media tracks** (the live audio/video streams flowing between client and bot). This page covers how to work with both. @@ -90,6 +95,12 @@ Audio output is handled automatically by the SDK — no additional setup require + + +Audio output is handled automatically by the transport — no additional setup required. The bot's audio plays through the device speaker as soon as the session connects. + + + --- ## Microphone @@ -147,6 +158,20 @@ let client = PipecatClient(options: options) + + +Pass `enableMic = true` to `RTVIClientOptions` to start the session with the mic active. This is the default: + +```kotlin +val options = RTVIClientOptions( + params = RTVIClientParams(baseUrl = null), + enableMic = true +) +val client = RTVIClient(SmallWebRTCTransport.Factory(context, botUrl), callbacks, options) +``` + + + Use [`usePipecatClientMicControl`](/api-reference/client/react/hooks#usepipecatclientmiccontrol) to mute and unmute within a component: @@ -227,6 +252,22 @@ Check current state with `client.isMicEnabled`. + + +Call `enableMic()` to mute and unmute. It returns a `Future` you can chain: + +```kotlin +fun toggleMic() { + client.enableMic(!client.isMicEnabled).withCallback { result -> + result.errorOrNull?.let { Log.e("Bot", "Mic error: ${it.description}") } + } +} +``` + +Check current state with `client.isMicEnabled`. + + + ### Switching microphones @@ -302,6 +343,27 @@ func onAvailableMicsUpdated(mics: [MediaDeviceInfo]) { + + +Enumerate available microphones with `getAllMics()` and switch with `updateMic()`: + +```kotlin +client.getAllMics().withCallback { result -> + val mics = result.valueOrNull ?: return@withCallback + client.updateMic(mics[1].id) +} +``` + +Listen for device changes via the callback: + +```kotlin +override fun onAvailableMicsUpdated(mics: List) { + this@MyManager.availableMics.value = mics +} +``` + + + --- ## Camera @@ -466,6 +528,41 @@ func onTrackStarted(track: MediaStreamTrack, participant: Participant?) { + + +Enable the camera via `RTVIClientOptions` and toggle it with `enableCam()`: + +```kotlin +val options = RTVIClientOptions( + params = RTVIClientParams(baseUrl = null), + enableCam = true +) + +client.enableCam(true) +val isOn = client.isCamEnabled +``` + +Access the camera feed via `onTracksUpdated`: + +```kotlin +override fun onTracksUpdated(tracks: Tracks) { + this@MyManager.tracks.value = tracks + // tracks.local?.video — local camera track + // tracks.bot?.video — bot video track +} +``` + +Switch cameras with `getAllCams()` / `updateCam()`: + +```kotlin +client.getAllCams().withCallback { result -> + val cams = result.valueOrNull ?: return@withCallback + client.updateCam(cams[1].id) +} +``` + + + --- ## Speakers @@ -513,6 +610,17 @@ self.client.updateSpeaker(speakers[0].deviceId); + + +Audio routing (speaker vs. earpiece) is managed by the Android `AudioManager`. Use it to force speaker output if needed: + +```kotlin +val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager +audioManager.isSpeakerphoneOn = true +``` + + + --- ## Device initialization before connecting @@ -621,6 +729,22 @@ func onTrackStarted(track: MediaStreamTrack, participant: Participant?) { + + +Override `onTracksUpdated` to be notified when tracks change: + +```kotlin +override fun onTracksUpdated(tracks: Tracks) { + this@MyManager.tracks.value = tracks + // tracks.local?.audio — local mic track + // tracks.local?.video — local camera track + // tracks.bot?.audio — bot audio track + // tracks.bot?.video — bot video track +} +``` + + + --- ## Audio visualization @@ -723,6 +847,47 @@ GeometryReader { geo in + + +Override the audio level callbacks and drive your own Compose visualization: + +```kotlin +val localAudioLevel = mutableFloatStateOf(0f) +val remoteAudioLevel = mutableFloatStateOf(0f) + +// In RTVIEventCallbacks: +override fun onUserAudioLevel(level: Float) { + localAudioLevel.floatValue = level +} + +override fun onRemoteAudioLevel(level: Float, participant: Participant) { + remoteAudioLevel.floatValue = level +} +``` + +```kotlin +// In your Composable: +@Composable +fun AudioViz(manager: MyManager) { + val level = manager.localAudioLevel.floatValue + Box( + Modifier + .fillMaxWidth() + .height(8.dp) + .background(Color.LightGray) + ) { + Box( + Modifier + .fillMaxWidth(level) + .fillMaxHeight() + .background(Color.Blue) + ) + } +} +``` + + + --- ## API reference @@ -772,3 +937,13 @@ GeometryReader { geo in + + + + + + Full API reference including `RTVIEventCallbacks` and device management + + + + diff --git a/client/concepts/session-lifecycle.mdx b/client/concepts/session-lifecycle.mdx index ae31f893..c755ae59 100644 --- a/client/concepts/session-lifecycle.mdx +++ b/client/concepts/session-lifecycle.mdx @@ -23,6 +23,11 @@ You are currently viewing the React Native version of this page. Use the Dropdow You are currently viewing the iOS version of this page. Use the Dropdown to the right to customize this page for your client framework. + + +You are currently viewing the Android version of this page. Use the Dropdown to the right to customize this page for your client framework. + + A session is the span of time from when your client connects to a bot until it disconnects. Understanding the lifecycle helps you build correct connection flows, handle errors gracefully, and clean up reliably. @@ -111,6 +116,27 @@ await client?.startBotAndConnect(startBotParams: startParams) + + +```kotlin +client.startBotAndConnect( + APIRequest( + endpoint = "/api/start", + requestData = Value.Object(mapOf( + // Optional data forwarded to your server + "initialPrompt" to Value.Str("You are a helpful assistant.") + )) + ) +).withCallback { result -> + result.errorOrNull?.let { error -> + Log.e("Bot", "Connection failed: ${error.description}") + } +} +// Completes when the bot reaches 'ready' state +``` + + + Use this when you control the server and need to create a session before connecting. ### Option 2: `connect()` with direct params @@ -155,6 +181,21 @@ await client.connect(transportParams: transportParams) + + +With SmallWebRTC, pass the endpoint URL directly to the transport factory and call `connect()`: + +```kotlin +val client = RTVIClient( + SmallWebRTCTransport.Factory(context, "/api/offer"), + callbacks, + options +) +client.connect() +``` + + + ### Option 3: `startBot()` + `connect()` separately Gives you explicit control over each phase — useful if you need to inspect the server response before connecting: @@ -191,14 +232,27 @@ await client.connect(connectionParams); -```tsx - +```swift let startParams = APIRequest.init( endpoint: URL(string: "/api/start")! ) -const connectionParams = await client.startBot(startBotParams: startParams) +let connectionParams = await client.startBot(startBotParams: startParams) // inspect or modify connectionParams here -await client.connect(connectionParams); +await client.connect(connectionParams) +``` + + + + + +```kotlin +client.startBot( + APIRequest(endpoint = "/api/start") +).withCallback { result -> + val connectionParams = result.valueOrNull ?: return@withCallback + // inspect connectionParams here + client.connect(connectionParams) +} ``` @@ -282,6 +336,20 @@ extension MyModel: PipecatClientDelegate { + + +Override `onBotReady` in your `RTVIEventCallbacks`: + +```kotlin +val callbacks = object : RTVIEventCallbacks() { + override fun onBotReady(data: BotReadyData) { + Log.d("Bot", "Bot ready, RTVI version: ${data.version}") + } +} +``` + + + ## Ending a session ### Client-initiated disconnect @@ -406,6 +474,25 @@ After a bot disconnect, call `release()` on the client before creating a new one + + +Override `onBotDisconnected` in your `RTVIEventCallbacks`. Call `release()` in `onDisconnected` to free resources: + +```kotlin +val callbacks = object : RTVIEventCallbacks() { + override fun onBotDisconnected(participant: Participant) { + // Update UI to show the session has ended + } + + override fun onDisconnected() { + client?.release() + client = null + } +} +``` + + + After a bot disconnect, you can call `connect()` again to start a new session. ## Error handling @@ -492,6 +579,29 @@ func onError(message: RTVIMessageInbound) { + + +Override `onBackendError` in your `RTVIEventCallbacks`. For startup errors, handle the `Future` result: + +```kotlin +val callbacks = object : RTVIEventCallbacks() { + override fun onBackendError(message: String) { + Log.e("Bot", "Backend error: $message") + // Update UI to show the error + } +} + +// Startup errors surface via the Future callback: +client.startBotAndConnect(APIRequest(endpoint = "/api/start", requestData = Value.Object())) + .withCallback { result -> + result.errorOrNull?.let { error -> + Log.e("Bot", "Connection failed: ${error.description}") + } + } +``` + + + ## Reconnecting The client does not automatically reconnect. After a disconnect (whether client-initiated, bot-initiated, or due to a fatal error), you need to call `startBotAndConnect()` or `connect()` again to start a new session. The same `PipecatClient` instance can be reused. @@ -616,6 +726,41 @@ var isConnecting: Bool { [.authenticating, .connecting, .connected].contains(mod + + +Hold state in a `mutableStateOf` that Compose can observe: + +```kotlin +import androidx.compose.runtime.mutableStateOf +import ai.pipecat.client.types.TransportState + +class VoiceClientManager { + val transportState = mutableStateOf(null) + + private val callbacks = object : RTVIEventCallbacks() { + override fun onTransportStateChanged(state: TransportState) { + this@VoiceClientManager.transportState.value = state + } + } +} +``` + +Read it in your Composable: + +```kotlin +@Composable +fun StatusBar(manager: VoiceClientManager) { + val state = manager.transportState.value + val stateStr = state?.toString()?.lowercase() ?: "disconnected" + val isReady = stateStr == "ready" + val isConnecting = state != null && !isReady + + Text(if (isReady) "Connected" else if (isConnecting) "Connecting…" else "Disconnected") +} +``` + + + ## Lifecycle summary ``` diff --git a/client/guides/building-a-voice-ui.mdx b/client/guides/building-a-voice-ui.mdx index f30d8847..e54fb27f 100644 --- a/client/guides/building-a-voice-ui.mdx +++ b/client/guides/building-a-voice-ui.mdx @@ -23,6 +23,11 @@ You are currently viewing the React Native version of this page. Use the dropdow You are currently viewing the iOS version of this page. Use the dropdown to the right to customize this page for your client framework. + + +You are currently viewing the Android version of this page. Use the dropdown to the right to customize this page for your client framework. + + This guide walks through building a React voice app without any UI abstractions — just the Pipecat React SDK directly. You'll see exactly how the client, provider, hooks, and audio output fit together, which is useful whether you're building a fully custom UI or want to understand what the [CLI-generated app](/client/get-started/quickstart) is doing under the hood. @@ -39,6 +44,9 @@ This guide walks through building a React Native voice app using the Pipecat Jav This guide walks through building a SwiftUI voice app using the Pipecat iOS SDK. You'll see how to create the client, implement the delegate, and connect to your bot — with audio handled automatically by the SDK. + +This guide walks through building an Android voice app using the Pipecat Android SDK. You'll see how to set up a client manager, implement the event callbacks in Kotlin, and build a Jetpack Compose UI — with audio handled automatically by the transport. + ## Prerequisites @@ -59,6 +67,10 @@ This guide walks through building a SwiftUI voice app using the Pipecat iOS SDK. - Xcode 15+ - A running Pipecat bot + +- Android Studio +- A running Pipecat bot + Follow the Pipecat server quickstart to get a bot running locally at `http://localhost:7860`. @@ -165,6 +177,26 @@ Then add `NSMicrophoneUsageDescription` to your `Info.plist`: + + +In Android Studio, create a new project using the **Empty Activity** template (Kotlin, Jetpack Compose). Then add the SmallWebRTC transport to your `app/build.gradle.kts`: + +```kotlin +dependencies { + implementation("ai.pipecat:small-webrtc-transport:0.3.7") +} +``` + +Add the microphone permission to `AndroidManifest.xml`: + +```xml + +``` + +You'll also need to request the permission at runtime before connecting. The simplest approach is via the [Accompanist Permissions](https://google.github.io/accompanist/permissions/) library or the standard `ActivityResultContracts.RequestPermission` API. + + + SmallWebRTC is ideal for development — no third-party account needed. For production, swap in the [Daily transport](/api-reference/client/js/transports/daily), @@ -278,6 +310,37 @@ Audio output is handled automatically — no additional setup required. + + +Create a `VoiceClientManager.kt` class that holds state Compose can observe. This is analogous to the SwiftUI `ObservableObject`: + +```kotlin +import ai.pipecat.client.RTVIClient +import ai.pipecat.client.RTVIClientOptions +import ai.pipecat.client.RTVIClientParams +import ai.pipecat.client.RTVIEventCallbacks +import ai.pipecat.client.small_webrtc_transport.SmallWebRTCTransport +import ai.pipecat.client.types.Transcript +import ai.pipecat.client.types.TransportState +import android.content.Context +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf + +data class Message(val role: String, val text: String) + +@Stable +class VoiceClientManager(private val context: Context) { + private var client: RTVIClient? = null + val state = mutableStateOf(null) + val messages = mutableStateListOf() +} +``` + +Audio output is handled automatically — no additional setup required. + + + ## Step 2: Build the app @@ -622,6 +685,119 @@ struct ContentView: View { + + +Add the connect/disconnect logic and event callbacks to `VoiceClientManager.kt`, then create `MainActivity.kt` with the Compose UI. + +Add these methods to `VoiceClientManager`: + +```kotlin +// Android emulators can't reach the host via `localhost` — use 10.0.2.2 instead. +// On a physical device, replace this with your machine's local network IP. +private val BOT_URL = "http://10.0.2.2:7860/api/offer" + +fun connect() { + val callbacks = object : RTVIEventCallbacks() { + override fun onTransportStateChanged(state: TransportState) { + this@VoiceClientManager.state.value = state + } + override fun onUserTranscript(data: Transcript) { + if (data.final) messages.add(Message("user", data.text)) + } + override fun onBotTranscript(text: String) { + messages.add(Message("bot", text)) + } + override fun onDisconnected() { + client?.release() + client = null + state.value = null + } + override fun onBackendError(message: String) { + // log or show the error + } + } + + val options = RTVIClientOptions( + params = RTVIClientParams(baseUrl = null), + enableMic = true + ) + + client = RTVIClient(SmallWebRTCTransport.Factory(context, BOT_URL), callbacks, options) + client?.connect() +} + +fun disconnect() { + client?.disconnect() +} +``` + +Create `MainActivity.kt`: + +```kotlin +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val manager = VoiceClientManager(this) + setContent { + MaterialTheme { + VoiceScreen(manager) + } + } + } +} + +@Composable +fun VoiceScreen(manager: VoiceClientManager) { + val state = manager.state.value + val stateStr = state?.toString()?.lowercase() ?: "disconnected" + val isConnected = stateStr == "ready" + val isConnecting = state != null && !isConnected + + Column(Modifier.fillMaxSize().padding(40.dp)) { + Text("Pipecat Voice App", style = MaterialTheme.typography.headlineMedium) + Spacer(Modifier.height(16.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = { if (isConnected) manager.disconnect() else manager.connect() }, + enabled = !isConnecting + ) { + Text(when { isConnected -> "Disconnect"; isConnecting -> "Connecting…"; else -> "Connect" }) + } + Text(stateStr, color = MaterialTheme.colorScheme.outline) + } + Spacer(Modifier.height(16.dp)) + LazyColumn { + items(manager.messages) { msg -> + Row(Modifier.padding(bottom = 8.dp)) { + Text( + if (msg.role == "user") "You: " else "Bot: ", + fontWeight = FontWeight.Bold + ) + Text(msg.text) + } + } + } + } +} +``` + + + ## Step 3: Run it Start your Pipecat bot (if it isn't already running), then start the app: @@ -684,6 +860,20 @@ Tap **Connect**, allow microphone access, and start talking. + + +In Android Studio, press **Run** (▶) and select your emulator or connected device. + +The `BOT_URL` in `VoiceClientManager` is set to `http://10.0.2.2:7860/api/offer`, which is correct for Android emulators (the emulator's alias for the host machine). For a physical device, replace it with your Mac's local network IP: + +```bash +ipconfig getifaddr en0 +``` + +Tap **Connect**, grant microphone access when prompted, and start talking. + + + ## What each piece does @@ -741,6 +931,21 @@ Tap **Connect**, allow microphone access, and start talking. + + +| Piece | Role | +|---|---| +| `RTVIClient` | Manages the connection and session lifecycle | +| `SmallWebRTCTransport.Factory` | Creates the WebRTC peer connection to your bot | +| `RTVIEventCallbacks` | Abstract class — override the methods you care about | +| `mutableStateOf` / `mutableStateListOf` | Compose-observable state for UI reactivity | +| `onTransportStateChanged` | Drives button state and status display | +| `onBotTranscript` | Receives the bot's text output | +| `onUserTranscript` | Receives finalized speech-to-text from the user | +| `onDisconnected` | Cleanup hook — call `release()` here | + + + ## Next steps diff --git a/client/guides/custom-messaging.mdx b/client/guides/custom-messaging.mdx index 6a34b423..fc0b46f1 100644 --- a/client/guides/custom-messaging.mdx +++ b/client/guides/custom-messaging.mdx @@ -23,6 +23,11 @@ You are currently viewing the React Native version of this page. Use the dropdow You are currently viewing the iOS version of this page. Use the dropdown to the right to customize this page for your client framework. + + +You are currently viewing the Android version of this page. Use the dropdown to the right to customize this page for your client framework. + + The Pipecat client can send and receive arbitrary messages to and from the server running the bot. This is useful for passing configuration at startup, triggering actions mid-session, querying server state, and receiving notifications from the bot. @@ -117,6 +122,26 @@ client.startBotAndConnect(startBotParams: startParams) { (result: Result + + +Pass data via the `requestData` field of `APIRequest`: + +```kotlin +client.startBotAndConnect( + APIRequest( + endpoint = "https://your-server.com/api/start", + requestData = Value.Object( + mapOf( + "initial_prompt" to Value.Str("You are a pirate captain"), + "preferred_language" to Value.Str("en-US"), + ) + ) + ) +) +``` + + + On the server, your endpoint receives this data as the request body and can forward relevant values to the bot process or use them to configure the pipeline: @@ -204,6 +229,17 @@ try client.sendClientMessage( + + +```kotlin +client.sendClientMessage( + "set-language", + Value.Object(mapOf("language" to Value.Str("en-US"))) +) +``` + + + On the server, handle it via the `on_client_message` event handler or from inside a `FrameProcessor`: @@ -292,6 +328,21 @@ Task { + + +```kotlin +client.sendClientRequest("get-language", Value.Object()) + .withCallback { result -> + val response = result.valueOrNull + val language = (response?.data as? Value.Object) + ?.value?.get("language") + ?.let { (it as? Value.Str)?.value } + Log.d("Bot", "Current language: $language") + } +``` + + + On the server, respond with `send_server_response()` or push a `RTVIServerResponseFrame`: @@ -417,6 +468,23 @@ func onServerMessage(data: Any) { + + +Override `onServerMessage` in your `RTVIEventCallbacks` to receive unsolicited server messages: + +```kotlin +override fun onServerMessage(data: Value) { + val obj = data as? Value.Object ?: return + val msg = (obj.value["msg"] as? Value.Str)?.value + if (msg == "language-updated") { + val language = (obj.value["language"] as? Value.Str)?.value + Log.d("Bot", "Language updated to: $language") + } +} +``` + + + On the server, send messages via `send_server_message()` or by pushing a `RTVIServerMessageFrame`: From d7280f2bab349a985e7e1feb1d9767b81078ee69 Mon Sep 17 00:00:00 2001 From: mattie ruth backman Date: Thu, 14 May 2026 13:41:04 -0400 Subject: [PATCH 2/2] Update all Android docs to latest transport versions --- .../android/transports/gemini-websocket.mdx | 66 +++++++++-- .../android/transports/openai-webrtc.mdx | 111 ++++++++++++++---- .../android/transports/small-webrtc.mdx | 91 +++++++++++--- client/concepts/events-and-callbacks.mdx | 36 +++--- client/concepts/media-management.mdx | 20 ++-- client/concepts/session-lifecycle.mdx | 30 ++--- client/guides/building-a-voice-ui.mdx | 37 +++--- client/guides/custom-messaging.mdx | 2 +- 8 files changed, 281 insertions(+), 112 deletions(-) diff --git a/api-reference/client/android/transports/gemini-websocket.mdx b/api-reference/client/android/transports/gemini-websocket.mdx index cd924ae5..6f05da83 100644 --- a/api-reference/client/android/transports/gemini-websocket.mdx +++ b/api-reference/client/android/transports/gemini-websocket.mdx @@ -14,24 +14,49 @@ The Gemini Live Websocket transport implementation enables real-time audio commu ## Installation -Add the transport dependency to your `build.gradle`: +Add the transport dependency to your `app/build.gradle.kts`: -```gradle -implementation "ai.pipecat:gemini-live-websocket-transport:0.3.7" +```kotlin +dependencies { + implementation("ai.pipecat:gemini-live-websocket-transport:0.3.7") +} ``` -## Usage +Add the microphone permission to `AndroidManifest.xml`: + +```xml + +``` -Create a client: +## Usage ```kotlin +import ai.pipecat.client.RTVIClient +import ai.pipecat.client.RTVIEventCallbacks +import ai.pipecat.client.gemini_live_websocket.GeminiLiveWebsocketTransport +import ai.pipecat.client.types.BotReadyData +import ai.pipecat.client.types.RTVIClientOptions +import ai.pipecat.client.types.RTVIClientParams +import ai.pipecat.client.types.Value + +val callbacks = object : RTVIEventCallbacks() { + override fun onBackendError(message: String) { + Log.e(TAG, "Backend error: $message") + } + + override fun onBotReady(data: BotReadyData) { + Log.d(TAG, "Bot is ready") + } +} + val transport = GeminiLiveWebsocketTransport.Factory(context) val options = RTVIClientOptions( params = RTVIClientParams( baseUrl = null, config = GeminiLiveWebsocketTransport.buildConfig( - apiKey = "", + apiKey = "your-gemini-api-key", + model = "models/gemini-2.0-flash-exp", generationConfig = Value.Object( "speech_config" to Value.Object( "voice_config" to Value.Object( @@ -48,11 +73,36 @@ val options = RTVIClientOptions( val client = RTVIClient(transport, callbacks, options) -client.start().withCallback { - // ... +client.connect().withCallback { result -> + result.errorOrNull?.let { Log.e(TAG, "Connection failed: $it") } } ``` +## Configuration + +### buildConfig + +`GeminiLiveWebsocketTransport.buildConfig()` accepts the following parameters: + +| Parameter | Type | Description | +|---|---|---| +| `apiKey` | `String` | Your Gemini API key | +| `model` | `String` | Model name (default: `"models/gemini-2.0-flash-exp"`) | +| `initialUserMessage` | `String?` | Optional message to send at session start | +| `generationConfig` | `Value.Object` | Generation config (voice, language, etc.) | +| `systemInstruction` | `Value?` | Optional system instruction | +| `tools` | `Value.Array` | Optional tools/function definitions | + +### Audio devices + +The transport exposes static constants for audio routing: + +```kotlin +// Route audio to speakerphone (default) or earpiece +client.updateMic(GeminiLiveWebsocketTransport.AudioDevices.Speakerphone.id) +client.updateMic(GeminiLiveWebsocketTransport.AudioDevices.Earpiece.id) +``` + ## Resources diff --git a/api-reference/client/android/transports/openai-webrtc.mdx b/api-reference/client/android/transports/openai-webrtc.mdx index 93f2a40a..6f000a5f 100644 --- a/api-reference/client/android/transports/openai-webrtc.mdx +++ b/api-reference/client/android/transports/openai-webrtc.mdx @@ -5,44 +5,103 @@ description: "WebRTC implementation for Android using OpenAI" The OpenAI Realtime WebRTC transport implementation enables real-time audio communication with the OpenAI Realtime service, using a direct WebRTC connection. + + Transports of this type connect directly to OpenAI's API from the client, + which exposes your API key. This is designed primarily for development and + testing. For production applications, proxy through a server component to + keep credentials secure. + + ## Installation -Add the transport dependency to your `build.gradle`: +Add the transport dependency to your `app/build.gradle.kts`: -```gradle -implementation "ai.pipecat:openai-realtime-webrtc-transport:0.3.7" +```kotlin +dependencies { + implementation("ai.pipecat:openai-realtime-webrtc-transport:1.2.0") +} ``` -## Usage +Add the microphone permission to `AndroidManifest.xml`: -Create a client: +```xml + +``` + +## Usage ```kotlin -val transport = OpenAIRealtimeWebRTCTransport.Factory(context) - -val options = RTVIClientOptions( - params = RTVIClientParams( - baseUrl = null, - config = OpenAIRealtimeWebRTCTransport.buildConfig( - apiKey = apiKey, - initialMessages = listOf( - LLMContextMessage(role = "user", content = "How tall is the Eiffel Tower?") - ), - initialConfig = OpenAIRealtimeSessionConfig( - voice = "ballad", - turnDetection = Value.Object("type" to Value.Str("semantic_vad")), - inputAudioNoiseReduction = Value.Object("type" to Value.Str("near_field")), - inputAudioTranscription = Value.Object("model" to Value.Str("gpt-4o-transcribe")) - ) +import ai.pipecat.client.PipecatClientOptions +import ai.pipecat.client.PipecatEventCallbacks +import ai.pipecat.client.openai_realtime_webrtc.OpenAIRealtimeSessionConfig +import ai.pipecat.client.openai_realtime_webrtc.OpenAIServiceOptions +import ai.pipecat.client.openai_realtime_webrtc.PipecatClientOpenAIRealtimeWebRTC +import ai.pipecat.client.openai_realtime_webrtc.OpenAIRealtimeWebRTCTransport +import ai.pipecat.client.types.BotReadyData +import ai.pipecat.client.types.Value + +val callbacks = object : PipecatEventCallbacks() { + override fun onBackendError(message: String) { + Log.e(TAG, "Backend error: $message") + } + + override fun onBotReady(data: BotReadyData) { + Log.d(TAG, "Bot is ready") + } +} + +val options = PipecatClientOptions(callbacks = callbacks, enableMic = true) +val client = PipecatClientOpenAIRealtimeWebRTC(OpenAIRealtimeWebRTCTransport(context), options) + +client.connect( + OpenAIServiceOptions( + apiKey = "your-openai-api-key", + model = "gpt-4o-realtime-preview", + sessionConfig = OpenAIRealtimeSessionConfig( + voice = "alloy", + instructions = "You are a helpful assistant.", + turnDetection = Value.Object("type" to Value.Str("semantic_vad")), + inputAudioTranscription = Value.Object("model" to Value.Str("gpt-4o-transcribe")) ) ) -) +).withCallback { result -> + result.errorOrNull?.let { Log.e(TAG, "Connection failed: $it") } +} +``` -val client = RTVIClient(transport, callbacks, options) +## Configuration -client.start().withCallback { - // ... -} +### OpenAIServiceOptions + +| Parameter | Type | Description | +|---|---|---| +| `apiKey` | `String` | Your OpenAI API key | +| `sessionConfig` | `OpenAIRealtimeSessionConfig` | Session configuration | +| `model` | `String?` | Model name (default: `"gpt-realtime"`) | +| `initialMessages` | `List` | Messages to inject at session start | + +### OpenAIRealtimeSessionConfig + +| Parameter | Type | Description | +|---|---|---| +| `modalities` | `List?` | Output modalities (e.g. `["audio", "text"]`) | +| `instructions` | `String?` | System instructions for the model | +| `voice` | `String?` | Voice name (e.g. `"alloy"`, `"ballad"`) | +| `turnDetection` | `Value?` | Turn detection config | +| `inputAudioNoiseReduction` | `Value?` | Noise reduction config | +| `inputAudioTranscription` | `Value?` | Transcription model config | +| `tools` | `Value?` | Tool/function definitions | +| `toolChoice` | `String?` | Tool choice strategy | +| `temperature` | `Float?` | Sampling temperature | + +### Audio devices + +The transport exposes static constants for audio routing: + +```kotlin +// Route audio to speakerphone (default) or earpiece +client.updateMic(OpenAIRealtimeWebRTCTransport.AudioDevices.Speakerphone.id) +client.updateMic(OpenAIRealtimeWebRTCTransport.AudioDevices.Earpiece.id) ``` ## Resources diff --git a/api-reference/client/android/transports/small-webrtc.mdx b/api-reference/client/android/transports/small-webrtc.mdx index 8e434426..fa6fa8c4 100644 --- a/api-reference/client/android/transports/small-webrtc.mdx +++ b/api-reference/client/android/transports/small-webrtc.mdx @@ -3,34 +3,95 @@ title: "Small WebRTC Transport" description: "WebRTC implementation for Android" --- -The Small WebRTC transport implementation enables real-time audio communication with the Small WebRTC Pipecat transport, using a direct WebRTC connection. +The Small WebRTC transport enables real-time audio communication with a Pipecat bot over a direct WebRTC connection, with no third-party account required. ## Installation -Add the transport dependency to your `build.gradle`: +Add the transport dependency to your `app/build.gradle.kts`: -```gradle -implementation "ai.pipecat:small-webrtc-transport:0.3.7" +```kotlin +dependencies { + implementation("ai.pipecat:small-webrtc-transport:1.2.0") +} ``` -## Usage +Add the microphone permission to `AndroidManifest.xml`: -Create a client: +```xml + +``` + +## Usage ```kotlin -val transport = SmallWebRTCTransport.Factory(context, baseUrl) +import ai.pipecat.client.PipecatClientOptions +import ai.pipecat.client.PipecatEventCallbacks +import ai.pipecat.client.small_webrtc_transport.PipecatClientSmallWebRTC +import ai.pipecat.client.small_webrtc_transport.SmallWebRTCTransport +import ai.pipecat.client.types.APIRequest +import ai.pipecat.client.types.BotReadyData +import ai.pipecat.client.types.Value -val options = RTVIClientOptions( - params = RTVIClientParams(baseUrl = null), - enableMic = true, - enableCam = true -) +val callbacks = object : PipecatEventCallbacks() { + override fun onBackendError(message: String) { + Log.e(TAG, "Backend error: $message") + } -val client = RTVIClient(transport, callbacks, options) + override fun onBotReady(data: BotReadyData) { + Log.d(TAG, "Bot is ready") + } +} + +val options = PipecatClientOptions(callbacks = callbacks, enableMic = true) +val client = PipecatClientSmallWebRTC(SmallWebRTCTransport(context), options) -client.start().withCallback { - // ... +// Connect via your server endpoint (recommended) +client.startBotAndConnect( + APIRequest(endpoint = "https://your-server.com/api/offer", requestData = Value.Object()) +).withCallback { result -> + result.errorOrNull?.let { Log.e(TAG, "Connection failed: $it") } } + +// Or connect with coroutines +// client.startBotAndConnect(...).await() +``` + +## Configuration + +### IceConfig + +Pass custom ICE servers for TURN/STUN support: + +```kotlin +val iceConfig = IceConfig( + iceServers = listOf( + IceServer( + urls = listOf("turn:your-turn-server.com:3478"), + username = "user", + credential = "pass" + ) + ) +) + +val transport = SmallWebRTCTransport(context, iceConfig = iceConfig) +``` + +### Audio devices + +The transport exposes static constants for audio routing: + +```kotlin +// Route audio to speakerphone (default) or earpiece +client.updateMic(SmallWebRTCTransport.AudioDevices.Speakerphone.id) +client.updateMic(SmallWebRTCTransport.AudioDevices.Earpiece.id) +``` + +### Camera selection + +```kotlin +// Switch between front and rear cameras +client.updateCam(SmallWebRTCTransport.Cameras.Front.id) +client.updateCam(SmallWebRTCTransport.Cameras.Rear.id) ``` ## Resources diff --git a/client/concepts/events-and-callbacks.mdx b/client/concepts/events-and-callbacks.mdx index e5190db7..804cc2b4 100644 --- a/client/concepts/events-and-callbacks.mdx +++ b/client/concepts/events-and-callbacks.mdx @@ -91,7 +91,7 @@ iOS uses the delegate pattern rather than constructor callbacks — see [Event l -Android uses `RTVIEventCallbacks` rather than a callbacks map — see [Event listeners](#event-listeners) below. +Android uses `PipecatEventCallbacks` rather than a callbacks map — see [Event listeners](#event-listeners) below. @@ -188,10 +188,14 @@ Delegate callbacks may arrive off the main actor — always use `Task { @MainAct -Extend `RTVIEventCallbacks` and pass it to the `RTVIClient` constructor. Override only the methods you need: +Extend `PipecatEventCallbacks` and pass it via `PipecatClientOptions`. Override only the methods you need: ```kotlin -val callbacks = object : RTVIEventCallbacks() { +val callbacks = object : PipecatEventCallbacks() { + override fun onBackendError(message: String) { + Log.e("Bot", "Backend error: $message") + } + override fun onBotReady(data: BotReadyData) { Log.d("Bot", "Bot is ready") } @@ -201,8 +205,8 @@ val callbacks = object : RTVIEventCallbacks() { } } -val options = RTVIClientOptions(params = RTVIClientParams(baseUrl = null), enableMic = true) -val client = RTVIClient(SmallWebRTCTransport.Factory(context, botUrl), callbacks, options) +val options = PipecatClientOptions(callbacks = callbacks, enableMic = true) +val client = PipecatClientSmallWebRTC(SmallWebRTCTransport(context), options) ``` Callbacks run on the main thread and can update Compose `mutableStateOf` directly. @@ -406,11 +410,11 @@ func onBotOutput(data: BotOutputData) { -`onBotTranscript` receives the bot's text output as it accumulates: +`onBotOutput` receives the bot's text output: ```kotlin -override fun onBotTranscript(text: String) { - appendSentence(text) +override fun onBotOutput(data: BotOutputData) { + appendSentence(data.text) } ``` @@ -580,14 +584,15 @@ client.registerFunctionCallHandler(functionName: "get_weather") { data, onResult -Override `onLLMFunctionCall` in your `RTVIEventCallbacks` to handle function calls. Invoke `onResult` with the function's return value: +Override `onLLMFunctionCall` in your `PipecatEventCallbacks` to handle function calls. Invoke `onResult` with the function's return value: ```kotlin -val callbacks = object : RTVIEventCallbacks() { +val callbacks = object : PipecatEventCallbacks() { override fun onLLMFunctionCall(data: LLMFunctionCallData, onResult: (Value) -> Unit) { if (data.functionName == "get_weather") { - val city = (data.args as? Value.Object)?.value?.get("city") - ?.let { (it as? Value.Str)?.value } ?: "" + val city = data.args.jsonObject["city"]?.let { + (it as? JsonPrimitive)?.content + } ?: "" onResult(Value.Str(fetchWeather(city))) } } @@ -598,8 +603,9 @@ For multiple functions, use `registerFunctionCallHandler` to register per-functi ```kotlin client.registerFunctionCallHandler("get_weather") { data, onResult -> - val city = (data.args as? Value.Object)?.value?.get("city") - ?.let { (it as? Value.Str)?.value } ?: "" + val city = data.args.jsonObject["city"]?.let { + (it as? JsonPrimitive)?.content + } ?: "" onResult(Value.Str(fetchWeather(city))) } ``` @@ -666,7 +672,7 @@ For custom server\<-\>client messaging, see [Custom Messaging](/client/guides/cu - Full `RTVIEventCallbacks` class and API reference + Full `PipecatEventCallbacks` class and API reference diff --git a/client/concepts/media-management.mdx b/client/concepts/media-management.mdx index bc8f2de9..896927af 100644 --- a/client/concepts/media-management.mdx +++ b/client/concepts/media-management.mdx @@ -160,14 +160,11 @@ let client = PipecatClient(options: options) -Pass `enableMic = true` to `RTVIClientOptions` to start the session with the mic active. This is the default: +Pass `enableMic = true` to `PipecatClientOptions` to start the session with the mic active. This is the default: ```kotlin -val options = RTVIClientOptions( - params = RTVIClientParams(baseUrl = null), - enableMic = true -) -val client = RTVIClient(SmallWebRTCTransport.Factory(context, botUrl), callbacks, options) +val options = PipecatClientOptions(callbacks = callbacks, enableMic = true) +val client = PipecatClientSmallWebRTC(SmallWebRTCTransport(context), options) ``` @@ -530,13 +527,10 @@ func onTrackStarted(track: MediaStreamTrack, participant: Participant?) { -Enable the camera via `RTVIClientOptions` and toggle it with `enableCam()`: +Enable the camera via `PipecatClientOptions` and toggle it with `enableCam()`: ```kotlin -val options = RTVIClientOptions( - params = RTVIClientParams(baseUrl = null), - enableCam = true -) +val options = PipecatClientOptions(callbacks = callbacks, enableCam = true) client.enableCam(true) val isOn = client.isCamEnabled @@ -855,7 +849,7 @@ Override the audio level callbacks and drive your own Compose visualization: val localAudioLevel = mutableFloatStateOf(0f) val remoteAudioLevel = mutableFloatStateOf(0f) -// In RTVIEventCallbacks: +// In PipecatEventCallbacks: override fun onUserAudioLevel(level: Float) { localAudioLevel.floatValue = level } @@ -942,7 +936,7 @@ fun AudioViz(manager: MyManager) { - Full API reference including `RTVIEventCallbacks` and device management + Full API reference including `PipecatEventCallbacks` and device management diff --git a/client/concepts/session-lifecycle.mdx b/client/concepts/session-lifecycle.mdx index c755ae59..152699fb 100644 --- a/client/concepts/session-lifecycle.mdx +++ b/client/concepts/session-lifecycle.mdx @@ -183,15 +183,15 @@ await client.connect(transportParams: transportParams) -With SmallWebRTC, pass the endpoint URL directly to the transport factory and call `connect()`: +With SmallWebRTC, pass the endpoint URL in `SmallWebRTCTransportConnectParams`: ```kotlin -val client = RTVIClient( - SmallWebRTCTransport.Factory(context, "/api/offer"), - callbacks, - options -) -client.connect() +val options = PipecatClientOptions(callbacks = callbacks, enableMic = true) +val client = PipecatClientSmallWebRTC(SmallWebRTCTransport(context), options) + +client.connect(SmallWebRTCTransportConnectParams( + webrtcRequestParams = APIRequest(endpoint = "/api/offer", requestData = Value.Object()) +)) ``` @@ -247,7 +247,7 @@ await client.connect(connectionParams) ```kotlin client.startBot( - APIRequest(endpoint = "/api/start") + APIRequest(endpoint = "/api/start", requestData = Value.Object()) ).withCallback { result -> val connectionParams = result.valueOrNull ?: return@withCallback // inspect connectionParams here @@ -338,10 +338,10 @@ extension MyModel: PipecatClientDelegate { -Override `onBotReady` in your `RTVIEventCallbacks`: +Override `onBotReady` in your `PipecatEventCallbacks`: ```kotlin -val callbacks = object : RTVIEventCallbacks() { +val callbacks = object : PipecatEventCallbacks() { override fun onBotReady(data: BotReadyData) { Log.d("Bot", "Bot ready, RTVI version: ${data.version}") } @@ -476,10 +476,10 @@ After a bot disconnect, call `release()` on the client before creating a new one -Override `onBotDisconnected` in your `RTVIEventCallbacks`. Call `release()` in `onDisconnected` to free resources: +Override `onBotDisconnected` in your `PipecatEventCallbacks`. Call `release()` in `onDisconnected` to free resources: ```kotlin -val callbacks = object : RTVIEventCallbacks() { +val callbacks = object : PipecatEventCallbacks() { override fun onBotDisconnected(participant: Participant) { // Update UI to show the session has ended } @@ -581,10 +581,10 @@ func onError(message: RTVIMessageInbound) { -Override `onBackendError` in your `RTVIEventCallbacks`. For startup errors, handle the `Future` result: +Override `onBackendError` in your `PipecatEventCallbacks`. For startup errors, handle the `Future` result: ```kotlin -val callbacks = object : RTVIEventCallbacks() { +val callbacks = object : PipecatEventCallbacks() { override fun onBackendError(message: String) { Log.e("Bot", "Backend error: $message") // Update UI to show the error @@ -737,7 +737,7 @@ import ai.pipecat.client.types.TransportState class VoiceClientManager { val transportState = mutableStateOf(null) - private val callbacks = object : RTVIEventCallbacks() { + private val callbacks = object : PipecatEventCallbacks() { override fun onTransportStateChanged(state: TransportState) { this@VoiceClientManager.transportState.value = state } diff --git a/client/guides/building-a-voice-ui.mdx b/client/guides/building-a-voice-ui.mdx index e54fb27f..34928473 100644 --- a/client/guides/building-a-voice-ui.mdx +++ b/client/guides/building-a-voice-ui.mdx @@ -183,7 +183,7 @@ In Android Studio, create a new project using the **Empty Activity** template (K ```kotlin dependencies { - implementation("ai.pipecat:small-webrtc-transport:0.3.7") + implementation("ai.pipecat:small-webrtc-transport:1.2.0") } ``` @@ -315,13 +315,15 @@ Audio output is handled automatically — no additional setup required. Create a `VoiceClientManager.kt` class that holds state Compose can observe. This is analogous to the SwiftUI `ObservableObject`: ```kotlin -import ai.pipecat.client.RTVIClient -import ai.pipecat.client.RTVIClientOptions -import ai.pipecat.client.RTVIClientParams -import ai.pipecat.client.RTVIEventCallbacks +import ai.pipecat.client.PipecatClientOptions +import ai.pipecat.client.PipecatEventCallbacks +import ai.pipecat.client.small_webrtc_transport.PipecatClientSmallWebRTC import ai.pipecat.client.small_webrtc_transport.SmallWebRTCTransport +import ai.pipecat.client.types.APIRequest +import ai.pipecat.client.types.BotOutputData import ai.pipecat.client.types.Transcript import ai.pipecat.client.types.TransportState +import ai.pipecat.client.types.Value import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf @@ -331,7 +333,7 @@ data class Message(val role: String, val text: String) @Stable class VoiceClientManager(private val context: Context) { - private var client: RTVIClient? = null + private var client: PipecatClientSmallWebRTC? = null val state = mutableStateOf(null) val messages = mutableStateListOf() } @@ -697,15 +699,15 @@ Add these methods to `VoiceClientManager`: private val BOT_URL = "http://10.0.2.2:7860/api/offer" fun connect() { - val callbacks = object : RTVIEventCallbacks() { + val callbacks = object : PipecatEventCallbacks() { override fun onTransportStateChanged(state: TransportState) { this@VoiceClientManager.state.value = state } override fun onUserTranscript(data: Transcript) { if (data.final) messages.add(Message("user", data.text)) } - override fun onBotTranscript(text: String) { - messages.add(Message("bot", text)) + override fun onBotOutput(data: BotOutputData) { + messages.add(Message("bot", data.text)) } override fun onDisconnected() { client?.release() @@ -717,13 +719,10 @@ fun connect() { } } - val options = RTVIClientOptions( - params = RTVIClientParams(baseUrl = null), - enableMic = true - ) + val options = PipecatClientOptions(callbacks = callbacks, enableMic = true) - client = RTVIClient(SmallWebRTCTransport.Factory(context, BOT_URL), callbacks, options) - client?.connect() + client = PipecatClientSmallWebRTC(SmallWebRTCTransport(context), options) + client?.startBotAndConnect(APIRequest(endpoint = BOT_URL, requestData = Value.Object())) } fun disconnect() { @@ -935,12 +934,12 @@ Tap **Connect**, grant microphone access when prompted, and start talking. | Piece | Role | |---|---| -| `RTVIClient` | Manages the connection and session lifecycle | -| `SmallWebRTCTransport.Factory` | Creates the WebRTC peer connection to your bot | -| `RTVIEventCallbacks` | Abstract class — override the methods you care about | +| `PipecatClientSmallWebRTC` | Manages the connection and session lifecycle | +| `SmallWebRTCTransport` | Handles the WebRTC peer connection to your bot | +| `PipecatEventCallbacks` | Abstract class — override the methods you care about | | `mutableStateOf` / `mutableStateListOf` | Compose-observable state for UI reactivity | | `onTransportStateChanged` | Drives button state and status display | -| `onBotTranscript` | Receives the bot's text output | +| `onBotOutput` | Receives the bot's text output | | `onUserTranscript` | Receives finalized speech-to-text from the user | | `onDisconnected` | Cleanup hook — call `release()` here | diff --git a/client/guides/custom-messaging.mdx b/client/guides/custom-messaging.mdx index fc0b46f1..396272dd 100644 --- a/client/guides/custom-messaging.mdx +++ b/client/guides/custom-messaging.mdx @@ -470,7 +470,7 @@ func onServerMessage(data: Any) { -Override `onServerMessage` in your `RTVIEventCallbacks` to receive unsolicited server messages: +Override `onServerMessage` in your `PipecatEventCallbacks` to receive unsolicited server messages: ```kotlin override fun onServerMessage(data: Value) {