# @elizaos/capacitor-talkmode Capacitor plugin for full voice conversation sessions: speech-to-text → chat orchestration → text-to-speech. Runs inside Eliza agent apps on **iOS**, **Android**, **Electrobun (desktop)**, and **browser**. On native platforms, it uses platform STT (AVFoundation / SFSpeechRecognizer on iOS, Android SpeechRecognizer) and ElevenLabs streaming TTS with PCM/MP3 audio playback. On web, it falls back to the Web Speech API for both STT and TTS. ## Capabilities - **Voice session management** — start/stop a hands-free, push-to-talk, compose, or passive session. - **Live transcription** — streaming interim and final transcripts from the microphone. - **TTS playback** — speak text via ElevenLabs (native only) or system TTS; supports per-utterance directives (voice, speed, stability, language, seed, latency tier). - **Interrupt on speech** — automatically cut TTS playback when the user starts speaking. - **Permission handling** — check and request microphone + speech recognition permissions. - **Event-driven state machine** — `idle` → `listening` → `processing` → `speaking` → `error` with typed events. ## Installation ```bash bun add @elizaos/capacitor-talkmode npx cap sync ``` Peer dependency: `@capacitor/core ^8.3.1`. On iOS, add the following to your `Info.plist`: ```xml NSMicrophoneUsageDescription Used for voice conversations NSSpeechRecognitionUsageDescription Used for speech-to-text transcription ``` ## Usage ```ts import { TalkMode } from "@elizaos/capacitor-talkmode"; // Start a voice session with ElevenLabs TTS const { started } = await TalkMode.start({ config: { mode: "hands-free", tts: { apiKey: process.env.ELEVENLABS_API_KEY, voiceId: "your-voice-id", modelId: "eleven_flash_v2_5", interruptOnSpeech: true, }, stt: { language: "en", sampleRate: 16000, }, silenceWindowMs: 700, }, }); // Listen for transcripts await TalkMode.addListener("transcript", (event) => { console.log(event.transcript, event.isFinal); }); // Listen for state changes await TalkMode.addListener("stateChange", (event) => { console.log(event.state, event.statusText); }); // Speak a string directly const result = await TalkMode.speak({ text: "Hello from your Eliza agent.", directive: { speed: 1.1, language: "en-US" }, }); // Stop the session await TalkMode.stop(); ``` ## Actions / Methods | Method | Description | |---|---| | `start(options?)` | Begin a voice session | | `stop()` | End the session and release audio resources | | `isEnabled()` | Whether a session is currently active | | `getState()` | Current state (`TalkModeState`) and status text | | `updateConfig(options)` | Patch configuration mid-session | | `speak(options)` | Speak text via TTS; returns completion result | | `stopSpeaking()` | Interrupt TTS playback | | `isSpeaking()` | Whether TTS is currently playing | | `checkPermissions()` | Read microphone + speech recognition permission status | | `requestPermissions()` | Prompt for required permissions | ## Events | Event | Payload type | When fired | |---|---|---| | `stateChange` | `TalkModeStateEvent` | State machine transition | | `transcript` | `TalkModeTranscriptEvent` | Interim or final STT result | | `speaking` | `TTSSpeakingEvent` | TTS utterance starts | | `speakComplete` | `TTSCompleteEvent` | TTS utterance finishes or is interrupted | | `playbackStart` | `TalkModePlaybackStartEvent` | Native PCM/MP3 playback begins | | `error` | `TalkModeErrorEvent` | Recoverable or fatal error | ## Configuration reference All config is passed to `start()` or `updateConfig()` — no process env vars are read by this package. | Field | Required | Description | |---|---|---| | `tts.apiKey` | Native TTS only | ElevenLabs API key | | `tts.voiceId` | No | ElevenLabs voice ID | | `tts.modelId` | No | ElevenLabs model (default: `eleven_flash_v2_5` on iOS) | | `tts.outputFormat` | No | e.g. `"pcm_24000"`, `"mp3_44100"` | | `tts.interruptOnSpeech` | No | Cut TTS when mic detects speech | | `tts.voiceAliases` | No | Name → voiceId mapping | | `stt.engine` | No | `"native"` or `"web"` | | `stt.modelSize` | No | Legacy compatibility field; ignored by current recognizers | | `stt.language` | No | BCP-47 language code | | `stt.sampleRate` | No | Hz, default 16000 | | `silenceWindowMs` | No | Silence gap before finalising transcript | | `mode` | No | Session mode (`compose`, `push-to-talk`, `hands-free`, `passive`) | | `sessionKey` | No | Chat session key for orchestration | ## Platform notes | Platform | STT | TTS | |---|---|---| | iOS / Android | Native platform API | ElevenLabs streaming + system TTS fallback | | Electrobun (desktop) | Native STT | ElevenLabs streaming + system TTS fallback | | Browser | Web Speech API | `SpeechSynthesis` only (ElevenLabs blocked by CORS) | ## Volume and mute policy TalkMode treats voice as an active communication session. The policy is: - **iOS:** configure `AVAudioSession` as `.playAndRecord` with `.voiceChat` and `.defaultToSpeaker` while allowing Bluetooth output. TTS is expected to stay audible during a voice session even when the hardware silent switch is on; volume buttons control the active output route volume. Microphone capture is unaffected by output volume or silent switch state. - **Android:** route TTS/PCM playback through `USAGE_VOICE_COMMUNICATION` while the app is in `MODE_IN_COMMUNICATION`, defaulting to speaker unless a headset is connected. The plugin may mute recognizer earcon streams during a session, but it must not mute the voice-communication output path. Hardware volume keys control the communication stream; OS mute can silence output, but capture and the agent turn must remain interactive and recoverable. - **Electrobun desktop:** honor the operating system output volume/mute state. Muting output may make TTS inaudible, but it must not stop microphone capture, chat orchestration, or completion events. - **Browser:** `SpeechSynthesis` follows browser/OS output controls. Microphone capture is governed by browser permission and should continue independently of output volume. ## Building ```bash bun run --cwd plugins/plugin-native-talkmode build ``` Outputs ESM to `dist/esm/`, CJS to `dist/plugin.cjs.js`, and IIFE to `dist/plugin.js`.