Skip to content

MathGeniusJodie/earshine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

9 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Earshine โœจ๐ŸŒ™๐Ÿ‘‚โœจ

An on-device Android speech-to-text provider backed by Moonshine. It implements android.speech.RecognitionService, so any app can use it through the standard SpeechRecognizer API, and it appears in system voice-input pickers. All recognition happens locally; nothing leaves the device.

Building

scripts/fetch-models.sh      # ~51 MB into app/src/main/assets (once)
./gradlew :app:assembleDebug   # or :app:assembleRelease (R8-minified)

Release builds are signed with the release keystore when the EARSHINE_KEYSTORE_FILE / EARSHINE_KEYSTORE_PASSWORD / EARSHINE_KEY_ALIAS / EARSHINE_KEY_PASSWORD environment variables are set, and fall back to the debug key otherwise so a local adb install just works. Pushing a v* tag runs .github/workflows/release.yml, which builds a signed APK from repo secrets and attaches it to a GitHub release.

Requirements: JDK 17โ€“21 for Gradle (export JAVA_HOME=...), Android SDK (platform 35). If you skip fetch-models.sh, the APK still works โ€” it downloads the same files from download.moonshine.ai on first launch instead of unpacking them from assets.

Constraints inherited from the upstream AAR: arm64-v8a only and minSdk 35 (Android 15). Use a physical device or an arm64 emulator image.

Using it as a recognition provider

From another app:

val recognizer = SpeechRecognizer.createSpeechRecognizer(
    context,
    ComponentName("dev.earshine", "dev.earshine.MoonshineRecognitionService"),
)
recognizer.setRecognitionListener(listener)
recognizer.startListening(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
    putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
})
  • Default sessions are single-utterance: Moonshine's VAD endpoints the speech, onResults fires, done.
  • Pass RecognizerIntent.EXTRA_SEGMENTED_SESSION for continuous dictation: each completed line arrives via onSegmentResults until you call stopListening().
  • English only (en*); other EXTRA_LANGUAGE values get ERROR_LANGUAGE_NOT_SUPPORTED.

The bundled demo activity does exactly this against the service and also serves as the first-run screen that provisions the model and requests the microphone permission (the audio is captured in this app's process, so Earshine itself must hold RECORD_AUDIO).

Other entry points

  • Voice IME (VoiceInputMethodService): a voice input method that keyboards with a mic key (HeliBoard, AnySoftKeyboard, โ€ฆ) can switch to. Enable it under Settings โ†’ System โ†’ Keyboard โ†’ On-screen keyboards. It runs a continuous segmented session, shows the in-progress line as composing text, and commits each completed line.
  • RECOGNIZE_SPEECH dialog (RecognizeSpeechActivity): handles the classic RecognizerIntent.ACTION_RECOGNIZE_SPEECH startActivityForResult contract, returning the transcript in RecognizerIntent.EXTRA_RESULTS.

Swapping models

tiny-streaming-en is the default. For higher accuracy at ~2ร— the size and compute, base-en (non-streaming) or base-streaming-en also work: run scripts/fetch-models.sh base-en and update Models.DEFAULT in app/src/main/java/dev/earshine/ModelStore.kt (name, arch constant, file list โ€” see the script header). Non-English base models exist too, but the service currently advertises English only.

About

๐ŸŒ™๐Ÿ‘‚fast/light local dictation provider for android using moonshine

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors