Skip to content

Expo / React Native

Example: examples/react-native-expo

Full binding: expo-adapters.ts.

Terminal window
cd examples/react-native-expo
cp .env.example .env
bun run start

Preferred input: 16 kHz · mono · PCM16. The runtime does not depend on Expo packages; you inject small adapters:

import { createExpoRuntime } from '@ottervoice/runtime-react-native';
import { createVoiceSession } from '@ottervoice/core';
const runtime = createExpoRuntime({
input: { /* Expo AudioStream / recorder bindings */ },
output: { /* AudioPlaylist / playback bindings */ },
});
const session = createVoiceSession({
mode: 'full_duplex',
audioLlmStartTiming: 'after_audio', // Run final ASR and reply generation in parallel.
asrPartial: false, // One final ASR call; clients cannot amplify rolling requests.
runtime,
providers: { asr, audioLlm },
});

The example’s createMobileProviders() always returns the unified providers.audioLlm contract:

EXPO_PUBLIC_OTTERVOICE_BACKEND Server implementation Client providers
composite (default) One /asr-llm-tts request runs ASR → LLM → TTS audioLlm
native /audio-llm native speech model; /asr is optional captions only audioLlm + optional asr

Switching backends does not change Session, Runtime, events, or UI. Mobile has no classic ASR / LLM / TTS three-provider path.

Dual delivery:

  • delivery: 'stream' — small PCM buffers → streaming ASR
  • delivery: 'turn' — complete WAV → batch ASR / Audio LLM

The example explicitly uses audioLlmStartTiming: 'after_audio'. Once the full user audio is ready, final ASR and the Audio LLM reply request start in parallel, reducing user_audio_end to assistant_audio_start latency. The assistant reply may arrive before asr_final, so correlate UI and persistence events by turnId. This mode may spend on a natural pause that later speech supersedes; enforce per-user, conversation, and profile budgets, rate limits, and idempotency at the gateway.

Suspend encoded capture during assistant playback while keeping volume callbacks for barge-in. See runtime-react-native.

When the app leaves active, the example calls finish('app_background') and then dispose() to stop capture, requests, and playback. Returning to the foreground requires an explicit new session. The runtime cancels asynchronous microphone/player setup that completes after stop() and removes temporary audio files, preventing background resource leaks.