capacitor-community / capacitor-community/speech-recognition

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

Open
#124 0 comments 0 reactions 0 assignees View on GitHub
needs: triage
Dominant language
Java
Stars
130
Forks
69
PR merge metrics
No merged PRs in 30d

Description

**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):

```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.

Contributor guide

Open the contributing guide

Research direction

Start with SpeechRecognition.java and reproduce the start → stop → start sequence on Android API 35 while observing the reported ERROR_SERVER_DISCONNECTED code 11. Trace how the SpeechRecognizer is created, stopped, destroyed, and recreated across sessions. Done means repeated starts reliably begin recognition, normal stops do not cause alternating failures, and error 11 is handled consistently.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, java
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.