Skip to content

@ottervoice/runtime-react-native

The React Native runtime does not hard-depend on Expo packages: you inject recording/playback primitives so the adapter stays testable and Expo Go–friendly.

import { createExpoRuntime } from '@ottervoice/runtime-react-native';
const runtime = createExpoRuntime({
input: {
requestPermission: () => /* requestRecordingPermissionsAsync */,
createPcmStream: (options, onBuffer) => /* useAudioStream bridge */,
},
output: {
createSound: (uri) => /* createAudioPlayer */,
createPcmPlaylist: () => /* AudioPlaylist */,
writePcmChunk: (input) => /* PCM16 → temp WAV URI */,
deleteAudioFile: (uri) => /* cleanup */,
},
});

Full binding: examples/react-native-expo/src/expo-adapters.ts.

Field Notes
input Required — see ExpoAudioInputOptions
output Required — see ExpoAudioOutputOptions

No network adapter: providers use React Native’s global fetch / WebSocket.

Field Notes
createPcmStream Preferred: native PCM mic (wrap Expo SDK 57 useAudioStream)
requestPermission Microphone permission
createRecording / readAudioFile Legacy whole-file recording; prefer PCM for full duplex
now Test clock

Prefer 16 kHz · mono · PCM16. Chunks may set delivery: 'stream' | 'turn': streaming ASR consumes small buffers; batch / Audio LLM consumes full-turn WAV.

Field Notes
createSound URI → playable sound
createPcmPlaylist Gapless PCM playlist for streamed replies
writePcmChunk One PCM16 slice → temp WAV URI
writeAudioFile / deleteAudioFile Full-buffer write and cleanup

The createSound status bridge must forward the native playing field. Playback exposes two distinct lifecycle signals:

runtime.audioOutput.onPlaybackRequested(() => {
// Audio is ready and native play/playAsync is about to be called; it may still fail or cancel.
});
runtime.audioOutput.onStart(() => {
// The native sound/playlist first reported playing: true; resume does not fire this again.
});

Whole-file and streamed PCM playback share the same order: onPlaybackRequestedonStartonEnd. A failure or cancellation before audible playback emits only the first signal.

start() and stop() are safe to overlap across React Native lifecycle changes. If native microphone, sound, or playlist setup completes after stop(), the runtime immediately stops or destroys that stale object instead of resuming work in the background. Temporary files created by writeAudioFile and writePcmChunk are best-effort removed through deleteAudioFile on normal, cancelled, and failed paths.

During assistant playback, suspend encoded capture while keeping volume callbacks for real barge-in.

Example: examples/react-native-expo. Types: API reference.