Skip to content

bug: Android SpeechRecognizer ERROR_SERVER_DISCONNECTED (11) — recorder fails to start on every other attempt (API 35) #124

@itsmorty

Description

@itsmorty

Plugin version:
^7.0.1

Platform(s):
Android

Current behavior:
When starting speech recognition the first time, the recorder and recognition start correctly. On the second attempt (start → stop → start), the recorder UI does not start and no audio is captured. The plugin reports an error mapped to code 11 (native SpeechRecognizer.ERROR_SERVER_DISCONNECTED) but this error code is not handled in getErrorText. The failure alternates (works on 1st attempt, fails on 2nd, works on 3rd, etc.).

Expected behavior:
Speech recognition should start reliably on each start invocation (start → stop → start) without alternating failures. The plugin should either reuse the SpeechRecognizer connection or recover cleanly so the remote service does not disconnect between quick successive starts.

Steps to reproduce:

  1. Install the app with @capacitor-community/speech-recognition on an Android device or emulator running API 35.
  2. Grant microphone permission if prompted.
  3. Start speech recognition (e.g., SpeechRecognition.start({...}) with partialResults: true, popup: false).
  4. Let it run, then stop it (user stop or call SpeechRecognition.stop()).
  5. Immediately start speech recognition again.
  6. Observe that the second start often fails and logs an ERROR_SERVER_DISCONNECTED (11). Repeat — behavior alternates.

Related code:

await SpeechRecognition.removeAllListeners();
await SpeechRecognition.addListener('partialResults', data => { /* ... */ });
await SpeechRecognition.addListener('listeningState', data => { /* ... */ });
await SpeechRecognition.start({
  language: 'en-US',
  maxResults: 1,
  partialResults: true,
  popup: false
});

// later...
await SpeechRecognition.stop();

Native plugin: the plugin's SpeechRecognition.java lifecycle previously destroyed and recreated the SpeechRecognizer instance on each start/stop cycle. This can cause ERROR_SERVER_DISCONNECTED when the recognition service is unbound/rebound too quickly.

Other information:

  • Native error code: 11 = SpeechRecognizer.ERROR_SERVER_DISCONNECTED.
  • Observed on Android API 35 (physical device and emulator).
  • Behavior: works on odd attempts (1st, 3rd, ...), fails on even attempts (2nd, 4th, ...).
  • Likely root cause: destroying/unbinding the SpeechRecognizer between sessions causes a race with the remote recognition service (Google speech service). Rebinding immediately can result in ERROR_SERVER_DISCONNECTED.
  • Local mitigation that reduces the issue frequency:
    • Reuse the SpeechRecognizer instance instead of calling destroy() on every stop. Call stopListening()/cancel() on stop and only destroy() on plugin/activity destroy or when receiving ERROR_SERVER_DISCONNECTED.
    • On onError() if ERROR_SERVER_DISCONNECTED is received, explicitly destroy the recognizer and set it to null to force a fresh create on next start (or attempt a short delay before re-creating).
  • Example recommended pattern (pseudo-Java):
// create once (e.g., in load())
if (speechRecognizer == null) {
  speechRecognizer = SpeechRecognizer.createSpeechRecognizer(activity);
}
speechRecognizer.setRecognitionListener(listener);
speechRecognizer.startListening(intent);

// on stop
speechRecognizer.stopListening();
speechRecognizer.cancel();
// do not destroy here - reuse the instance

// on error
if (error == SpeechRecognizer.ERROR_SERVER_DISCONNECTED) {
  speechRecognizer.destroy();
  speechRecognizer = null;
}
  • I inspected / modified locally the plugin file:
    SpeechRecognition.java
    (added logging, added explicit handling to map error 11, and experimented with reusing the recognizer vs destroying on every stop).

If you need full logcat traces, JS console output, or the patch I applied locally (to show the exact changes), I can attach them or open a PR with a recommended fix (reuse recognizer + error-handling + minimal retry/delay).

Capacitor doctor:

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 7.4.4
  @capacitor/core: 7.4.4
  @capacitor/android: 7.4.4
  @capacitor/ios: 7.4.4

Installed Dependencies:

  @capacitor/cli: 7.4.4
  @capacitor/ios: 7.4.4
  @capacitor/android: 7.4.4
  @capacitor/core: 7.4.4

[error] Xcode is not installed
[success] Android looking great! 👌

If helpful, I can prepare a PR that:

  • Reuses the SpeechRecognizer instance (create once).
  • Avoids calling destroy() on normal stop.
  • Destroys + recreates only on ERROR_SERVER_DISCONNECTED or on plugin/activity destroy.
  • Adds detailed logging and an optional small delay before recreating when necessary.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions