Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,30 @@ private void listening(boolean value) {
this.listening = value;
}

private void resetPartialResultsCache() {
previousPartialResults = new JSONArray();
}

private void rebuildRecognizerLocked(PluginCall call, boolean partialResults) {
// Reuse the existing recognizer if available - destroying/recreating causes ERROR_SERVER_DISCONNECTED (11)
// Only create new if null (first time or after an error destroyed it)
if (speechRecognizer == null) {
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(bridge.getActivity());
Logger.info(getLogTag(), "Created new SpeechRecognizer instance");
} else {
// Cancel any pending recognition before starting a new one
try {
speechRecognizer.cancel();
} catch (Exception ignored) {}
Logger.info(getLogTag(), "Reusing existing SpeechRecognizer instance");
}

SpeechRecognitionListener listener = new SpeechRecognitionListener();
listener.setCall(call);
listener.setPartialResults(partialResults);
speechRecognizer.setRecognitionListener(listener);
}

private void beginListening(
String language,
int maxResults,
Expand All @@ -173,38 +197,42 @@ private void beginListening(
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
}

resetPartialResultsCache();

if (showPopup) {
startActivityForResult(call, intent, "listeningResult");
} else {
bridge
.getWebView()
.post(() -> {
.getActivity()
.runOnUiThread(() -> {
try {
SpeechRecognition.this.lock.lock();

if (speechRecognizer != null) {
speechRecognizer.cancel();
speechRecognizer.destroy();
speechRecognizer = null;
}

speechRecognizer = SpeechRecognizer.createSpeechRecognizer(bridge.getActivity());
SpeechRecognitionListener listener = new SpeechRecognitionListener();
listener.setCall(call);
listener.setPartialResults(partialResults);
speechRecognizer.setRecognitionListener(listener);
speechRecognizer.startListening(intent);
SpeechRecognition.this.listening(true);
if (partialResults) {
call.resolve();
}
SpeechRecognition.this.startActivityForResult(call, intent, "listeningResult");
} catch (Exception ex) {
SpeechRecognition.this.listening(false);
call.reject(ex.getMessage());
} finally {
SpeechRecognition.this.lock.unlock();
}
});
return;
}

bridge
.getWebView()
.post(() -> {
try {
SpeechRecognition.this.lock.lock();
Logger.info(getLogTag(), "Rebuilding and starting recognizer");
rebuildRecognizerLocked(call, partialResults);
speechRecognizer.startListening(intent);
SpeechRecognition.this.listening(true);
if (partialResults) {
call.resolve();
}
} catch (Exception ex) {
Logger.error(getLogTag(), "Error starting listening: " + ex.getMessage(), ex);
call.reject(ex.getMessage());
} finally {
SpeechRecognition.this.lock.unlock();
}
});
}

private void stopListening() {
Expand All @@ -213,18 +241,44 @@ private void stopListening() {
.post(() -> {
try {
SpeechRecognition.this.lock.lock();
if (SpeechRecognition.this.listening) {
speechRecognizer.stopListening();
SpeechRecognition.this.listening(false);
Logger.info(getLogTag(), "Stopping listening");
if (speechRecognizer != null) {
try {
speechRecognizer.stopListening();
} catch (Exception ignored) {}
try {
speechRecognizer.cancel();
} catch (Exception ignored) {}
// Don't destroy here - let rebuildRecognizerLocked handle cleanup
}
} catch (Exception ex) {
throw ex;
resetPartialResultsCache();
SpeechRecognition.this.listening(false);
} finally {
SpeechRecognition.this.lock.unlock();
}
});
}

private void destroyRecognizer() {
bridge.getWebView().post(() -> {
try {
SpeechRecognition.this.lock.lock();
if (speechRecognizer != null) {
speechRecognizer.destroy();
speechRecognizer = null;
}
} finally {
SpeechRecognition.this.lock.unlock();
}
});
}

@Override
protected void handleOnDestroy() {
super.handleOnDestroy();
destroyRecognizer();
}

private class SpeechRecognitionListener implements RecognitionListener {

private PluginCall call;
Expand Down Expand Up @@ -280,8 +334,22 @@ public void onEndOfSpeech() {

@Override
public void onError(int error) {
SpeechRecognition.this.stopListening();
String errorMssg = getErrorText(error);

// Reset state synchronously on the same thread
resetPartialResultsCache();
SpeechRecognition.this.listening(false);

// Destroy the recognizer synchronously to ensure clean state for next attempt
if (speechRecognizer != null) {
try {
speechRecognizer.cancel();
} catch (Exception ignored) {}
try {
speechRecognizer.destroy();
} catch (Exception ignored) {}
speechRecognizer = null;
}

if (this.call != null) {
call.reject(errorMssg);
Expand All @@ -306,6 +374,8 @@ public void onResults(Bundle results) {
}
} catch (Exception ex) {
this.call.resolve(new JSObject().put("status", "error").put("message", ex.getMessage()));
} finally {
resetPartialResultsCache();
}
}

Expand Down Expand Up @@ -358,8 +428,11 @@ private String getErrorText(int errorCode) {
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
message = "No speech input";
break;
case SpeechRecognizer.ERROR_SERVER_DISCONNECTED:
message = "Server disconnected";
break;
default:
message = "Didn't understand, please try again.";
message = "Didn't understand, please try again. Error code: " + errorCode;
break;
}
return message;
Expand Down