@siteed/moonshine.rn

Give it a GitHub star, if you found this repo useful.
React Native bindings for Moonshine on-device speech recognition, offline
transcription from decoded PCM, streaming transcription, and intent
recognition across iOS, Android, and web.
Highlights
- Offline transcription from decoded PCM
- Streaming transcription with incremental transcript events
- Incremental transcript events during streaming and offline processing
- Word timestamps when the required model assets are present
- Intent recognizer support
- Web backend built on
onnxruntime-web
- Typed transcriber API with explicit cancellation support
Install
yarn add @siteed/moonshine.rn
For Expo / React Native apps using Yarn Berry, node-modules is still the most
predictable setup:
nodeLinker: node-modules
Platform requirements
- Android:
minSdkVersion 35
- iOS: native rebuild required after installing or upgrading the package
- Web:
window.ort must be available before creating a transcriber
Consumer setup at a glance
| iOS | Install the package, then run pod install / rebuild the native app. | Downloads the checksum-pinned Moonshine.xcframework.zip for the installed package version and caches it outside node_modules. | SITEED_MOONSHINE_IOS_XCFRAMEWORK_URL, SITEED_MOONSHINE_IOS_XCFRAMEWORK_SHA256, SITEED_MOONSHINE_IOS_CACHE_DIR. |
| Android | Install the package and rebuild the app. | Resolves ai.moonshine:moonshine-voice from Maven by default; the heavy AAR is not shipped in npm. | SITEED_MOONSHINE_ANDROID_MAVEN_COORD, SITEED_MOONSHINE_ANDROID_MAVEN_REPO, SITEED_MOONSHINE_ANDROID_AAR, SITEED_MOONSHINE_ANDROID_USE_MAVEN. |
| Web | Load onnxruntime-web, configure asset URLs if needed, then create a transcriber. | Uses the package web backend and fetches model assets from the configured CDN/base path. | configureMoonshineWeb(), webEncoderUrl, webDecoderUrl, webProgressModelBasePath. |
Most apps do not need native customization. Use the override hooks only for
mirrors, offline CI cache seeding, source-built native artifacts, or self-hosted
web model assets.
Quick start
Create a transcriber from files
import Moonshine from '@siteed/moonshine.rn';
const transcriber = await Moonshine.createTranscriberFromFiles({
modelArch: 'small-streaming',
modelPath: '/absolute/path/to/model-dir',
options: {
wordTimestamps: true,
},
});
const result = await transcriber.transcribe({
input: samples,
sampleRate: 16000,
});
console.log(result.text);
Cancel an in-flight offline transcription
const controller = new AbortController();
const transcriptionPromise = transcriber.transcribe({
input: samples,
sampleRate: 16000,
signal: controller.signal,
});
controller.abort();
try {
await transcriptionPromise;
} catch (error) {
if (
error &&
typeof error === 'object' &&
'code' in error &&
error.code === 'MOONSHINE_TRANSCRIPTION_CANCELLED'
) {
console.log('Transcription cancelled');
}
}
Listen for incremental transcript events
const removeListener = transcriber.addListener((event) => {
switch (event.type) {
case 'lineStarted':
case 'lineUpdated':
case 'lineTextChanged':
case 'lineCompleted':
console.log(event.line);
break;
case 'transcriptionCancelled':
console.warn('Transcription cancelled');
break;
case 'error':
console.error(event.error);
break;
}
});
Main API surface
Transcriber lifecycle
createTranscriberFromFiles()
createTranscriberFromAssets()
createTranscriberFromMemory()
releaseTranscriber()
Singleton compatibility helpers
loadFromFiles()
loadFromAssets()
loadFromMemory()
initialize()
release()
Transcription
start() / stop()
cancel()
createStream() / removeStream()
startStream() / stopStream()
addAudio() / addAudioToStream()
transcribe({ input, sampleRate, signal? })
transcribe(...) accepts decoded mono PCM samples (number[] or
Float32Array) and a required sampleRate.
This package intentionally stays at the transcription-wrapper layer. Decode
files, URIs, and other container formats upstream before calling transcribe.
In this monorepo, that responsibility belongs in audio-processing utilities
such as audio-studio, not inside moonshine.rn.
Offline transcription cancellation rejects the in-flight promise with
MOONSHINE_TRANSCRIPTION_CANCELLED, surfaces as an AbortError-style
rejection, and emits a terminal transcriptionCancelled event instead of
returning a partial success result.
Intent recognition
createIntentRecognizer()
releaseIntentRecognizer()
registerIntent() / unregisterIntent()
processUtterance()
clearIntents()
setIntentThreshold() / getIntentThreshold()
getIntentCount()
Model options
Typed transcriber options include:
identifySpeakers
speakerIdClusterThreshold
vadThreshold
vadHopSize
vadWindowDurationMs
vadMaxSegmentDurationMs
vadLookBehindSampleCount
maxTokensPerSecond
logApiCalls
logOrtRuns
logOutputText
saveInputWavPath
wordTimestamps
transcriberOptions is also available as a low-level escape hatch for
upstream native options that are not yet modeled in the typed surface.
Customization reference
Common transcriber options
Pass typed options when creating a transcriber:
const transcriber = await Moonshine.createTranscriberFromFiles({
modelArch: 'small-streaming',
modelPath: '/absolute/path/to/model-dir',
options: {
wordTimestamps: true,
vadThreshold: 0.5,
identifySpeakers: false,
speakerIdClusterThreshold: 0.7,
},
});
| Enable word timings | options.wordTimestamps | Requires compatible model assets; see Word timestamps. |
| Tune voice activity detection | vadThreshold, vadHopSize, vadWindowDurationMs, vadMaxSegmentDurationMs, vadLookBehindSampleCount | Useful when input has long silence, noise, or unusual speech pacing. |
| Debug model calls | logApiCalls, logOrtRuns, logOutputText, saveInputWavPath | Development-only; avoid verbose logging in production builds. |
| Experimental speaker hints | identifySpeakers, speakerIdClusterThreshold | Not trusted diarization; see Speaker metadata. |
| Upstream option not typed yet | transcriberOptions | Low-level escape hatch; prefer typed options when available. |
Native install overrides
| iOS | SITEED_MOONSHINE_IOS_XCFRAMEWORK_URL | Mirror or locally host Moonshine.xcframework.zip. |
| iOS | SITEED_MOONSHINE_IOS_XCFRAMEWORK_SHA256 | Override the package-pinned checksum for a trusted mirror or local artifact. |
| iOS | SITEED_MOONSHINE_IOS_CACHE_DIR | Seed or persist the artifact cache in CI. |
| Android | SITEED_MOONSHINE_ANDROID_MAVEN_COORD / siteedMoonshineAndroidMavenCoord | Use a different Maven coordinate. |
| Android | SITEED_MOONSHINE_ANDROID_MAVEN_REPO / siteedMoonshineAndroidMavenRepo | Use an internal Maven repository. |
| Android | SITEED_MOONSHINE_ANDROID_AAR / siteedMoonshineAndroidAar | Use a custom source-built AAR. |
| Android | SITEED_MOONSHINE_ANDROID_USE_MAVEN / siteedMoonshineAndroidUseMaven | Force Maven even when a local source AAR exists. |
Platform notes
Android
The public npm package does not ship the Android AAR so installs stay small.
Published consumers resolve Moonshine dynamically from Maven by default:
ai.moonshine:moonshine-voice.
For repo-local source builds, Gradle automatically uses
prebuilt/android/moonshine-voice-source-release.aar when that file exists.
You can also set SITEED_MOONSHINE_ANDROID_AAR /
siteedMoonshineAndroidAar to point at a custom AAR.
To force Maven even when a local source AAR exists, set
SITEED_MOONSHINE_ANDROID_USE_MAVEN=1 or
siteedMoonshineAndroidUseMaven=true.
Using Moonshine with Sherpa or another ONNX Runtime package
Android apps can package only one libonnxruntime.so per ABI. If the app also
installs @siteed/sherpa-onnx.rn or another ONNX Runtime provider, do not rely
on file presence or pickFirst alone. Moonshine is safest when
libmoonshine.so imports unversioned UND OrtGetApiBase; a versioned import
such as OrtGetApiBase@VERS_... must match the selected libonnxruntime.so.
The current default Maven artifact (ai.moonshine:moonshine-voice:0.0.59) has
been observed with OrtGetApiBase@VERS_1.23.0, which is risky beside
@siteed/sherpa-onnx.rn@1.2.0's VERS_1.24.3 runtime. Use a compatible custom
or source Moonshine AAR, align the other runtime provider, or patch after
Android native libraries are merged and stripped. See
Android ONNX Runtime coexistence.
iOS
The public npm package does not ship the Moonshine xcframework. During
pod install, the podspec runs scripts/ensure-ios-artifacts.sh to prepare the
xcframework dynamically.
By default, the script downloads
Moonshine.xcframework.zip from the package release artifact URL for the
current npm version, using the @siteed/moonshine.rn@<version> GitHub release
tag. For the current native artifact line, the released archive is 91,320,003 bytes
(about 87 MiB) and expands to about 198 MiB for the xcframework. The
prepared prebuilt/ios directory is about 333 MiB after the selected slice is
copied into prebuilt/ios/current, so CI and developer machines need network
access the first time each version is prepared.
The published package pins the expected SHA-256 checksum in package.json and
the install script verifies it by default. To use an internal mirror or a
locally staged artifact URL, set SITEED_MOONSHINE_IOS_XCFRAMEWORK_URL. Mirrors
used for publishing must respond to HEAD or to a ranged GET request
(Range: bytes=0-0) so validate:ios-release-artifact can confirm the asset
before npm publish. To override the expected checksum, set
SITEED_MOONSHINE_IOS_XCFRAMEWORK_SHA256; this intentionally replaces the
package-pinned checksum and should only be used for trusted mirrors or local
development artifacts.
Downloaded archives are cached outside node_modules so reinstalling packages
does not force another network fetch. On macOS the default cache is
~/Library/Caches/@siteed/moonshine.rn/ios/<version>-<sha>/; override it with
SITEED_MOONSHINE_IOS_CACHE_DIR. When no checksum is pinned, the cache key uses
the artifact URL hash instead, so changing a mirror URL creates a separate cache
entry. Offline or sandboxed CI must either seed that cache or point
SITEED_MOONSHINE_IOS_XCFRAMEWORK_URL at an accessible mirror. On non-macOS
machines the fallback cache root is $XDG_CACHE_HOME or $HOME/.cache; if
neither is available, set SITEED_MOONSHINE_IOS_CACHE_DIR.
Normal CocoaPods output may only show Installing Moonshine <version> while
prepare_command runs; use verbose CocoaPods output or run
bash node_modules/@siteed/moonshine.rn/scripts/ensure-ios-artifacts.sh
directly if you need to see curl progress.
Before publishing a new npm version, upload the matching GitHub release asset
first. npm publish is gated by validate:ios-release-artifact so a missing
asset fails fast instead of shipping a package whose first pod install would
404. This publish-time check intentionally requires network access.
After upgrading the package, rebuild the native iOS app so the JS layer and
native bridge stay in sync.
Web
The web backend is package-owned and does not depend on the published
@moonshine-ai/moonshine-js runtime bundle. It uses onnxruntime-web, so
window.ort must be available before creating a transcriber.
A minimal browser setup can load ORT from a CDN before your app bundle:
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.21.1/dist/ort.min.js"></script>
Then configure Moonshine if you want to self-host model or wasm assets:
import Moonshine, { configureMoonshineWeb } from '@siteed/moonshine.rn';
configureMoonshineWeb({
modelAssetBasePath: 'https://cdn.example.com/moonshine/',
onnxRuntimeWasmBasePath: 'https://cdn.example.com/onnxruntime-web/',
});
const transcriber = await Moonshine.createTranscriberFromFiles({
modelArch: 'base',
modelPath: 'model/base',
options: {
wordTimestamps: true,
},
});
For per-transcriber web assets, pass typed load-config overrides instead of
changing global config:
const transcriber = await Moonshine.createTranscriberFromFiles({
modelArch: 'base',
modelPath: 'model/base',
webEncoderUrl: 'https://cdn.example.com/moonshine/model/base/quantized/encoder_model_quantized.onnx',
webDecoderUrl: 'https://cdn.example.com/moonshine/model/base/quantized/decoder_model_merged_quantized.onnx',
webProgressModelBasePath: 'https://cdn.example.com/moonshine/model/tiny',
});
The public npm package does not include web model binaries. By default, web
models are fetched from https://download.moonshine.ai/model/; production apps
can self-host or mirror those assets and point configureMoonshineWeb() at the
mirror. Web currently supports the tiny and base model tiers; streaming
model names are mapped to those tiers for web execution.
Word timestamps
Word timestamps depend on model assets, not just API flags.
- Native streaming/offline word timestamps require the attention-capable decoder
assets to be present alongside the model bundle.
- On web, word timestamps require an attention-capable decoder path.
Speaker metadata
Speaker-turn / speaker-clustering hints are available, but they should be
considered experimental. They are useful for tentative turn segmentation,
not trusted diarization or speaker identity.
Advanced / repo-local workflows
These are mainly for development inside this monorepo:
bash packages/moonshine.rn/setup.sh
bash packages/moonshine.rn/build-moonshine-android.sh
bash packages/moonshine.rn/build-moonshine-ios.sh
bash packages/moonshine.rn/build-moonshine-web.sh
Useful package-local checks:
yarn release:beta:preflight
yarn validate:ios-release-artifact
yarn validate:offline:contract <model-id> [device-filter]
release:beta:preflight validates local type/tests/tarball/native contracts.
validate:ios-release-artifact is intentionally separate because it is a
networked publish gate: it checks reachability and downloads the full iOS
release archive, about 87 MiB for the current native artifact line, to verify the pinned
SHA-256 before npm publish.
Known limitations
MicTranscriber is not wrapped directly; apps are expected to own microphone
capture and audio routing.
- Android apps that combine Moonshine with Sherpa or another ONNX Runtime
provider must verify ONNX Runtime symbol compatibility; see
Android ONNX Runtime coexistence.
- External Android consumers still need an app configuration compatible with
minSdkVersion 35.