capacitor-microphone 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,8 +17,6 @@ import android.speech.RecognitionListener;
17
17
  import android.speech.RecognizerIntent;
18
18
  import android.speech.SpeechRecognizer;
19
19
 
20
-
21
-
22
20
  import java.util.ArrayList;
23
21
 
24
22
  @CapacitorPlugin(
@@ -32,24 +30,32 @@ import java.util.ArrayList;
32
30
  )
33
31
  public class CapacitorMicrophonePlugin extends Plugin {
34
32
 
35
- private SpeechRecognizer speechRecognizer;
36
- private PluginCall currentCall;
37
- private Intent speechIntent;
38
- private boolean isListening = false;
39
- private boolean isDestroyed = false;
40
- private long lastStopTime = 0;
41
- private static final long RESTART_COOLDOWN_MS = 400;
42
- private Runnable delayedStartRunnable;
43
- private int ignoreResultCount = 0;
44
- private Runnable silenceRunnable;
45
- private static final long SILENCE_TIMEOUT_MS = 4000;
46
-
47
-
48
-
33
+ private SpeechRecognizer speechRecognizer;
34
+ private PluginCall currentCall;
35
+ private Intent speechIntent;
36
+ private boolean isListening = false;
37
+ private boolean isDestroyed = false;
38
+ private long lastStopTime = 0;
39
+ private static final long RESTART_COOLDOWN_MS = 400;
40
+ private Runnable delayedStartRunnable;
41
+ private Runnable silenceRunnable;
42
+ private static final long SILENCE_TIMEOUT_MS = 3000;
43
+ private boolean shouldClearOnNextResult = false;
44
+ private StringBuilder accumulatedText = new StringBuilder();
45
+ private String lastSentPartial = "";
46
+ private boolean isInSpeech = false;
47
+ private long lastSpeechTime = 0;
48
+ private static final long SPEECH_PAUSE_THRESHOLD = 1500;
49
+ private float lastRmsValue = 0;
50
+ private static final float RMS_THRESHOLD = 2.0f;
51
+ private boolean hasRealSpeech = false;
52
+ private String pendingPartialText = "";
53
+ private long lastPartialTime = 0;
54
+
55
+ // Métodos de permisos (sin cambios)
49
56
  @PluginMethod
50
57
  public void checkPermission(PluginCall call) {
51
58
  PermissionState state = getPermissionState("microphone");
52
-
53
59
  JSObject result = new JSObject();
54
60
  result.put("granted", state == PermissionState.GRANTED);
55
61
  result.put("status",
@@ -58,7 +64,6 @@ private static final long SILENCE_TIMEOUT_MS = 4000;
58
64
  );
59
65
  result.put("details", "Android checkPermission");
60
66
  result.put("errorMessage", "");
61
-
62
67
  call.resolve(result);
63
68
  }
64
69
 
@@ -73,14 +78,12 @@ private static final long SILENCE_TIMEOUT_MS = 4000;
73
78
  call.resolve(result);
74
79
  return;
75
80
  }
76
-
77
81
  requestPermissionForAlias("microphone", call, "permissionCallback");
78
82
  }
79
83
 
80
84
  @PluginMethod
81
85
  public void checkRequestPermission(PluginCall call) {
82
86
  PermissionState state = getPermissionState("microphone");
83
-
84
87
  if (state == PermissionState.GRANTED) {
85
88
  JSObject result = new JSObject();
86
89
  result.put("granted", true);
@@ -90,223 +93,558 @@ private static final long SILENCE_TIMEOUT_MS = 4000;
90
93
  call.resolve(result);
91
94
  return;
92
95
  }
93
-
94
- if (state == PermissionState.DENIED) {
95
- requestPermissionForAlias("microphone", call, "permissionCallback");
96
- return;
97
- }
98
-
99
96
  requestPermissionForAlias("microphone", call, "permissionCallback");
100
97
  }
101
98
 
102
99
  @PermissionCallback
103
100
  private void permissionCallback(PluginCall call) {
104
101
  boolean granted = getPermissionState("microphone") == PermissionState.GRANTED;
105
-
106
102
  JSObject result = new JSObject();
107
103
  result.put("granted", granted);
108
104
  result.put("status", granted ? "granted" : "denied");
109
105
  result.put("details", "Android permission callback");
110
106
  result.put("errorMessage", granted ? "" : "User denied microphone permission");
111
-
112
107
  call.resolve(result);
113
108
  }
114
109
 
115
- @PluginMethod
116
- public void startListening(PluginCall call) {
117
- String lang = call.getString("lang", "es-MX");
118
-
119
- if (delayedStartRunnable != null) {
120
- getActivity().runOnUiThread(() -> {
121
- getActivity()
122
- .getWindow()
123
- .getDecorView()
124
- .removeCallbacks(delayedStartRunnable);
125
- });
126
- delayedStartRunnable = null;
127
- }
128
-
129
- long now = System.currentTimeMillis();
130
- long diff = now - lastStopTime;
131
-
132
- if (diff < RESTART_COOLDOWN_MS) {
133
- long delay = RESTART_COOLDOWN_MS - diff;
110
+ @PluginMethod
111
+ public void startListening(PluginCall call) {
112
+ String lang = call.getString("lang", "es-MX");
134
113
 
135
- delayedStartRunnable = () -> {
136
- if (!isDestroyed) {
137
- startListeningInternal(call, lang);
138
- } else {
139
- call.reject("Listening was cancelled");
140
- }
114
+ if (delayedStartRunnable != null) {
115
+ getActivity().runOnUiThread(() -> {
116
+ getActivity()
117
+ .getWindow()
118
+ .getDecorView()
119
+ .removeCallbacks(delayedStartRunnable);
120
+ });
141
121
  delayedStartRunnable = null;
142
- };
143
-
144
- getActivity().runOnUiThread(() -> {
145
- getActivity()
146
- .getWindow()
147
- .getDecorView()
148
- .postDelayed(delayedStartRunnable, delay);
149
- });
150
- return;
151
- }
152
-
153
- startListeningInternal(call, lang);
154
- }
155
-
122
+ }
156
123
 
157
- private void startListeningInternal(PluginCall call, String lang) {
158
- ignoreResultCount = 0;
159
- isDestroyed = false;
160
- isListening = true;
124
+ long now = System.currentTimeMillis();
125
+ long diff = now - lastStopTime;
161
126
 
162
- if (getPermissionState("microphone") != PermissionState.GRANTED) {
163
- call.reject("Microphone permission not granted");
164
- return;
165
- }
127
+ if (diff < RESTART_COOLDOWN_MS) {
128
+ long delay = RESTART_COOLDOWN_MS - diff;
129
+ delayedStartRunnable = () -> {
130
+ if (!isDestroyed) {
131
+ startListeningInternal(call, lang);
132
+ } else {
133
+ call.reject("Listening was cancelled");
134
+ }
135
+ delayedStartRunnable = null;
136
+ };
166
137
 
167
- if (!SpeechRecognizer.isRecognitionAvailable(getContext())) {
168
- call.reject("Speech recognition not available");
169
- return;
170
- }
138
+ getActivity().runOnUiThread(() -> {
139
+ getActivity()
140
+ .getWindow()
141
+ .getDecorView()
142
+ .postDelayed(delayedStartRunnable, delay);
143
+ });
144
+ return;
145
+ }
171
146
 
172
- if (speechRecognizer != null) {
173
- stopSpeech();
147
+ startListeningInternal(call, lang);
174
148
  }
175
149
 
176
- notifyListeners(
177
- "partialResult",
178
- new JSObject()
179
- .put("text", "")
180
- .put("isFinal", false)
181
- );
182
-
183
- this.currentCall = call;
184
-
185
- getActivity().runOnUiThread(() -> {
186
- try {
187
- speechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
188
- speechRecognizer.setRecognitionListener(new RecognitionListener() {
189
-
190
- @Override
191
- public void onReadyForSpeech(Bundle params) {
192
- scheduleSilenceRestart();
193
- }
194
-
195
- @Override public void onBeginningOfSpeech() {}
196
- @Override public void onRmsChanged(float rmsdB) {}
197
- @Override public void onBufferReceived(byte[] buffer) {}
198
- @Override public void onEndOfSpeech() {}
199
-
200
-
201
- @Override
202
- public void onError(int error) {
203
- if (isDestroyed || !isListening) return;
204
-
205
- if (!isDestroyed && isListening && speechRecognizer != null && speechIntent != null) {
206
- getActivity().runOnUiThread(() -> {
207
- try {
208
- speechRecognizer.startListening(speechIntent);
209
- } catch (Exception ignored) {}
210
- });
211
- }
212
- }
150
+ private void startListeningInternal(PluginCall call, String lang) {
151
+ // Solo limpiar si es un inicio nuevo, no si es continuación
152
+ if (!isListening) {
153
+ accumulatedText.setLength(0);
154
+ lastSentPartial = "";
155
+ hasRealSpeech = false;
156
+ pendingPartialText = "";
157
+ }
213
158
 
159
+ shouldClearOnNextResult = false;
160
+ isDestroyed = false;
161
+ isListening = true;
162
+ isInSpeech = false;
163
+ lastRmsValue = 0;
214
164
 
215
- @Override
216
- public void onResults(Bundle results) {
165
+ if (getPermissionState("microphone") != PermissionState.GRANTED) {
166
+ call.reject("Microphone permission not granted");
167
+ return;
168
+ }
217
169
 
218
- if (ignoreResultCount > 0) {
219
- ignoreResultCount--;
220
- return;
221
- }
170
+ if (!SpeechRecognizer.isRecognitionAvailable(getContext())) {
171
+ call.reject("Speech recognition not available");
172
+ return;
173
+ }
222
174
 
223
- if (!isListening) return;
175
+ if (speechRecognizer != null) {
176
+ stopSpeech();
177
+ }
224
178
 
225
- ArrayList<String> matches =
226
- results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
179
+ // Notificar inicio con texto acumulado (si hay)
180
+ if (accumulatedText.length() > 0) {
181
+ notifyListeners(
182
+ "partialResult",
183
+ new JSObject()
184
+ .put("text", accumulatedText.toString())
185
+ .put("isFinal", false)
186
+ .put("hasSpeech", true)
187
+ );
188
+ } else {
189
+ // Enviar evento vacío para indicar que está listo pero no hay habla
190
+ notifyListeners(
191
+ "partialResult",
192
+ new JSObject()
193
+ .put("text", "")
194
+ .put("isFinal", false)
195
+ .put("hasSpeech", false)
196
+ );
197
+ }
227
198
 
228
- if (matches != null && !matches.isEmpty()) {
229
- notifyListeners(
230
- "partialResult",
231
- new JSObject()
232
- .put("text", matches.get(0))
233
- .put("isFinal", true)
234
- );
235
- }
199
+ this.currentCall = call;
236
200
 
237
-
201
+ getActivity().runOnUiThread(() -> {
202
+ try {
203
+ speechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
204
+ speechRecognizer.setRecognitionListener(new RecognitionListener() {
205
+
206
+ @Override
207
+ public void onReadyForSpeech(Bundle params) {
208
+ cancelSilenceTimeout();
209
+ scheduleSilenceTimeout();
210
+ // Indicar que está listo pero no hay habla aún
211
+ if (!hasRealSpeech && accumulatedText.length() == 0) {
212
+ notifyListeners(
213
+ "partialResult",
214
+ new JSObject()
215
+ .put("text", "")
216
+ .put("isFinal", false)
217
+ .put("hasSpeech", false)
218
+ );
219
+ }
220
+ }
221
+
222
+ @Override
223
+ public void onBeginningOfSpeech() {
224
+ isInSpeech = true;
225
+ hasRealSpeech = true;
226
+ lastSpeechTime = System.currentTimeMillis();
227
+ cancelSilenceTimeout();
228
+
229
+ // Notificar que comenzó el habla
230
+ notifyListeners(
231
+ "partialResult",
232
+ new JSObject()
233
+ .put("text", accumulatedText.length() > 0 ? accumulatedText.toString() : "")
234
+ .put("isFinal", false)
235
+ .put("hasSpeech", true)
236
+ );
237
+ }
238
+
239
+ @Override
240
+ public void onRmsChanged(float rmsdB) {
241
+ lastRmsValue = rmsdB;
242
+
243
+ // Detectar si hay actividad de voz real
244
+ if (rmsdB > RMS_THRESHOLD) {
245
+ isInSpeech = true;
246
+ hasRealSpeech = true;
247
+ lastSpeechTime = System.currentTimeMillis();
248
+ cancelSilenceTimeout();
249
+ scheduleSilenceTimeout();
250
+ } else if (rmsdB < 0.5f && !isInSpeech) {
251
+ // Muy bajo RMS, probablemente silencio
252
+ // No enviar eventos de habla si no hay texto
253
+ if (!hasRealSpeech && pendingPartialText.isEmpty()) {
254
+ notifyListeners(
255
+ "partialResult",
256
+ new JSObject()
257
+ .put("text", "")
258
+ .put("isFinal", false)
259
+ .put("hasSpeech", false)
260
+ );
261
+ }
262
+ }
263
+ }
264
+
265
+ @Override
266
+ public void onBufferReceived(byte[] buffer) {}
267
+
268
+ @Override
269
+ public void onEndOfSpeech() {
270
+ isInSpeech = false;
271
+ // Programar timeout más corto para pausas entre palabras
272
+ cancelSilenceTimeout();
273
+ getActivity()
274
+ .getWindow()
275
+ .getDecorView()
276
+ .postDelayed(() -> {
277
+ if (!isInSpeech && isListening && !isDestroyed) {
278
+ scheduleSilenceTimeout();
279
+ }
280
+ }, 500);
281
+ }
282
+
283
+ @Override
284
+ public void onError(int error) {
285
+ if (isDestroyed || !isListening) return;
286
+
287
+ // Manejo diferente para cada tipo de error
288
+ switch (error) {
289
+ case SpeechRecognizer.ERROR_NO_MATCH:
290
+ // No se reconoció voz - no es un error real
291
+ if (hasRealSpeech) {
292
+ // Había habla pero no se reconoció, mantener estado
293
+ restartListeningQuietly();
294
+ } else {
295
+ // Nunca hubo habla, mantener estado silencioso
296
+ notifyListeners(
297
+ "partialResult",
298
+ new JSObject()
299
+ .put("text", "")
300
+ .put("isFinal", false)
301
+ .put("hasSpeech", false)
302
+ );
303
+ restartListeningQuietly();
304
+ }
305
+ break;
306
+ case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
307
+ // Timeout de habla - reiniciar silenciosamente
308
+ restartListeningQuietly();
309
+ break;
310
+ case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
311
+ // Esperar y reintentar
312
+ getActivity()
313
+ .getWindow()
314
+ .getDecorView()
315
+ .postDelayed(() -> restartListeningAfterError(), 300);
316
+ break;
317
+ default:
318
+ // Otros errores, intentar reiniciar
319
+ restartListeningAfterError();
320
+ break;
321
+ }
322
+ }
323
+
324
+ @Override
325
+ public void onResults(Bundle results) {
326
+ if (shouldClearOnNextResult) {
327
+ shouldClearOnNextResult = false;
328
+ accumulatedText.setLength(0);
329
+ lastSentPartial = "";
330
+ hasRealSpeech = false;
331
+ pendingPartialText = "";
332
+ restartListeningQuietly();
333
+ return;
334
+ }
335
+
336
+ if (!isListening || isDestroyed) return;
337
+
338
+ ArrayList<String> matches =
339
+ results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
340
+
341
+ if (matches != null && !matches.isEmpty()) {
342
+ String finalText = matches.get(0);
343
+
344
+ // Solo procesar si hay texto real
345
+ if (finalText != null && !finalText.trim().isEmpty()) {
346
+ // Actualizar texto acumulado
347
+ if (!accumulatedText.toString().endsWith(finalText)) {
348
+ // Agregar espacio si hay texto previo
349
+ if (accumulatedText.length() > 0 && !accumulatedText.toString().endsWith(" ")) {
350
+ accumulatedText.append(" ");
351
+ }
352
+ accumulatedText.append(finalText);
353
+ }
354
+
355
+ // Enviar resultado final
356
+ notifyListeners(
357
+ "partialResult",
358
+ new JSObject()
359
+ .put("text", accumulatedText.toString())
360
+ .put("isFinal", true)
361
+ .put("hasSpeech", true)
362
+ );
363
+
364
+ lastSentPartial = accumulatedText.toString();
365
+ pendingPartialText = "";
366
+ }
367
+ } else {
368
+ // No hay resultados - mantener estado actual
369
+ if (accumulatedText.length() > 0) {
370
+ notifyListeners(
371
+ "partialResult",
372
+ new JSObject()
373
+ .put("text", accumulatedText.toString())
374
+ .put("isFinal", false)
375
+ .put("hasSpeech", true)
376
+ );
377
+ }
378
+ }
379
+
380
+ // Reiniciar para continuar escuchando
381
+ restartListeningQuietly();
382
+ }
383
+
384
+ @Override
385
+ public void onPartialResults(Bundle partialResults) {
386
+ if (!isListening || isDestroyed) return;
387
+
388
+ ArrayList<String> matches =
389
+ partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
390
+
391
+ if (matches != null && !matches.isEmpty()) {
392
+ String partialText = matches.get(0);
393
+ lastPartialTime = System.currentTimeMillis();
394
+
395
+ // Filtrar resultados vacíos o muy cortos
396
+ if (partialText != null && !partialText.trim().isEmpty() && partialText.length() > 1) {
397
+ pendingPartialText = partialText;
398
+ hasRealSpeech = true;
399
+
400
+ // Construir texto completo: acumulado + nuevo parcial
401
+ String currentText = accumulatedText.toString();
402
+ String displayText;
403
+
404
+ if (currentText.isEmpty()) {
405
+ displayText = partialText;
406
+ } else {
407
+ // Si el texto parcial ya está contenido en el acumulado, usar solo acumulado
408
+ if (currentText.contains(partialText)) {
409
+ displayText = currentText;
410
+ } else {
411
+ // Sino, mostrar acumulado + nuevo
412
+ displayText = currentText + " " + partialText;
413
+ }
414
+ }
415
+
416
+ // Solo enviar si es diferente del último enviado
417
+ if (!displayText.equals(lastSentPartial)) {
418
+ notifyListeners(
419
+ "partialResult",
420
+ new JSObject()
421
+ .put("text", displayText)
422
+ .put("isFinal", false)
423
+ .put("hasSpeech", true)
424
+ );
425
+ lastSentPartial = displayText;
426
+ }
427
+ } else if (hasRealSpeech) {
428
+ // Hay habla previa pero el parcial está vacío
429
+ // Mantener el último texto enviado
430
+ notifyListeners(
431
+ "partialResult",
432
+ new JSObject()
433
+ .put("text", accumulatedText.toString())
434
+ .put("isFinal", false)
435
+ .put("hasSpeech", true)
436
+ );
437
+ } else {
438
+ // Nunca ha habido habla, enviar estado silencioso
439
+ notifyListeners(
440
+ "partialResult",
441
+ new JSObject()
442
+ .put("text", "")
443
+ .put("isFinal", false)
444
+ .put("hasSpeech", false)
445
+ );
446
+ }
447
+
448
+ // Reiniciar timeout cuando recibimos resultados
449
+ cancelSilenceTimeout();
450
+ scheduleSilenceTimeout();
451
+ }
452
+ }
453
+
454
+ @Override
455
+ public void onEvent(int eventType, Bundle params) {}
456
+ });
238
457
 
239
- scheduleSilenceRestart();
458
+ speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
459
+ speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
460
+ speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, lang);
461
+ speechIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
462
+ speechIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
463
+ // Configuración para evitar timeout muy rápidos
464
+ speechIntent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 10000);
465
+ speechIntent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 4000);
466
+ speechIntent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 3000);
240
467
 
241
- }
468
+ speechRecognizer.startListening(speechIntent);
469
+ } catch (Exception e) {
470
+ if (currentCall != null) {
471
+ currentCall.reject(e.getMessage());
472
+ currentCall = null;
473
+ }
474
+ }
475
+ });
476
+ }
242
477
 
478
+ private void restartListeningQuietly() {
479
+ if (isDestroyed || !isListening || speechRecognizer == null || speechIntent == null) {
480
+ return;
481
+ }
243
482
 
483
+ getActivity().runOnUiThread(() -> {
484
+ try {
485
+ // Pequeña pausa antes de reiniciar
486
+ getActivity()
487
+ .getWindow()
488
+ .getDecorView()
489
+ .postDelayed(() -> {
490
+ if (!isDestroyed && isListening && speechRecognizer != null) {
491
+ try {
492
+ speechRecognizer.startListening(speechIntent);
493
+ } catch (Exception ignored) {}
494
+ }
495
+ }, 100);
496
+ } catch (Exception ignored) {}
497
+ });
498
+ }
244
499
 
245
- @Override
246
- public void onPartialResults(Bundle partialResults) {
500
+ private void restartListeningAfterError() {
501
+ if (isDestroyed || !isListening) return;
247
502
 
248
- if (ignoreResultCount > 0) {
249
- ignoreResultCount--;
250
- return;
251
- }
503
+ getActivity().runOnUiThread(() -> {
504
+ if (!isDestroyed && isListening && speechRecognizer != null && speechIntent != null) {
505
+ try {
506
+ // Pausa más larga después de un error
507
+ getActivity()
508
+ .getWindow()
509
+ .getDecorView()
510
+ .postDelayed(() -> {
511
+ if (!isDestroyed && isListening && speechRecognizer != null) {
512
+ try {
513
+ speechRecognizer.startListening(speechIntent);
514
+ } catch (Exception ignored) {}
515
+ }
516
+ }, 800);
517
+ } catch (Exception ignored) {}
518
+ }
519
+ });
520
+ }
252
521
 
522
+ private void scheduleSilenceTimeout() {
523
+ cancelSilenceTimeout();
524
+
525
+ silenceRunnable = () -> {
526
+ if (!isDestroyed && isListening) {
527
+ // Silencio prolongado detectado
528
+ // Si nunca hubo habla, mantener estado silencioso
529
+ if (!hasRealSpeech) {
530
+ notifyListeners(
531
+ "partialResult",
532
+ new JSObject()
533
+ .put("text", "")
534
+ .put("isFinal", false)
535
+ .put("hasSpeech", false)
536
+ );
537
+ }
538
+ // Reiniciar el reconocimiento
539
+ restartListeningQuietly();
540
+ }
541
+ };
253
542
 
254
- if (!isListening || isDestroyed) return;
543
+ getActivity()
544
+ .getWindow()
545
+ .getDecorView()
546
+ .postDelayed(silenceRunnable, SILENCE_TIMEOUT_MS);
547
+ }
255
548
 
256
- ArrayList<String> matches =
257
- partialResults.getStringArrayList(
258
- SpeechRecognizer.RESULTS_RECOGNITION);
549
+ private void cancelSilenceTimeout() {
550
+ if (silenceRunnable != null) {
551
+ getActivity().getWindow().getDecorView().removeCallbacks(silenceRunnable);
552
+ silenceRunnable = null;
553
+ }
554
+ }
259
555
 
260
- if (matches != null && !matches.isEmpty()) {
261
- notifyListeners(
262
- "partialResult",
263
- new JSObject()
264
- .put("text", matches.get(0))
265
- .put("isFinal", false)
266
- );
267
- }
556
+ @PluginMethod
557
+ public void restartListening(PluginCall call) {
558
+ String lang = call.getString("lang", "es-MX");
559
+
560
+ // Marcar para limpiar en el próximo resultado
561
+ shouldClearOnNextResult = true;
562
+
563
+ // Limpiar inmediatamente el texto acumulado
564
+ accumulatedText.setLength(0);
565
+ lastSentPartial = "";
566
+ hasRealSpeech = false;
567
+ pendingPartialText = "";
568
+
569
+ // Notificar limpieza (sin habla)
570
+ notifyListeners(
571
+ "partialResult",
572
+ new JSObject()
573
+ .put("text", "")
574
+ .put("isFinal", false)
575
+ .put("hasSpeech", false)
576
+ );
268
577
 
269
- scheduleSilenceRestart();
578
+ // Limpiar el estado actual
579
+ isListening = false;
270
580
 
271
- }
581
+ if (delayedStartRunnable != null) {
582
+ getActivity().runOnUiThread(() -> {
583
+ getActivity()
584
+ .getWindow()
585
+ .getDecorView()
586
+ .removeCallbacks(delayedStartRunnable);
587
+ });
588
+ delayedStartRunnable = null;
589
+ }
272
590
 
273
- @Override public void onEvent(int eventType, Bundle params) {}
274
- });
591
+ // Parar y reiniciar
592
+ stopSpeech();
275
593
 
594
+ getActivity().runOnUiThread(() -> {
595
+ getActivity()
596
+ .getWindow()
597
+ .getDecorView()
598
+ .postDelayed(() -> {
599
+ isDestroyed = false;
600
+ isListening = true;
601
+ speechIntent = null;
602
+
603
+ // Comenzar de nuevo
604
+ startListeningInternal(call, lang);
605
+ }, 300);
606
+ });
276
607
 
277
- speechIntent = new Intent(
278
- RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
279
- speechIntent.putExtra(
280
- RecognizerIntent.EXTRA_LANGUAGE_MODEL,
281
- RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
282
- speechIntent.putExtra(
283
- RecognizerIntent.EXTRA_LANGUAGE, lang);
284
- speechIntent.putExtra(
285
- RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
608
+ call.resolve();
609
+ }
286
610
 
611
+ @PluginMethod
612
+ public void stopListening(PluginCall call) {
613
+ isListening = false;
614
+ isDestroyed = true;
287
615
 
288
- speechRecognizer.startListening(speechIntent);
289
- } catch (Exception e) {
290
- if (currentCall != null) {
291
- currentCall.reject(e.getMessage());
292
- currentCall = null;
293
- }
616
+ if (delayedStartRunnable != null) {
617
+ getActivity().runOnUiThread(() -> {
618
+ getActivity()
619
+ .getWindow()
620
+ .getDecorView()
621
+ .removeCallbacks(delayedStartRunnable);
622
+ });
623
+ delayedStartRunnable = null;
294
624
  }
295
- });
296
- }
297
-
298
625
 
626
+ cancelSilenceTimeout();
627
+ currentCall = null;
628
+ stopSpeech();
299
629
 
630
+ // Enviar resultado final con todo lo acumulado
631
+ if (accumulatedText.length() > 0) {
632
+ notifyListeners(
633
+ "partialResult",
634
+ new JSObject()
635
+ .put("text", accumulatedText.toString())
636
+ .put("isFinal", true)
637
+ .put("hasSpeech", true)
638
+ );
639
+ }
300
640
 
641
+ call.resolve();
642
+ }
301
643
 
302
644
  private void stopSpeech() {
303
645
  try {
304
646
  lastStopTime = System.currentTimeMillis();
305
-
306
- if (silenceRunnable != null) {
307
- getActivity().getWindow().getDecorView().removeCallbacks(silenceRunnable);
308
- silenceRunnable = null;
309
- }
647
+ cancelSilenceTimeout();
310
648
 
311
649
  if (speechRecognizer != null) {
312
650
  speechRecognizer.cancel();
@@ -329,79 +667,11 @@ private void startListeningInternal(PluginCall call, String lang) {
329
667
  } catch (Exception ignored) {}
330
668
  }
331
669
 
332
-
333
-
334
- @PluginMethod
335
- public void restartListening(PluginCall call) {
336
- String lang = call.getString("lang", "es-MX");
337
-
338
- isListening = false;
339
- isDestroyed = true;
340
-
341
- if (delayedStartRunnable != null) {
342
- getActivity().runOnUiThread(() -> {
343
- getActivity()
344
- .getWindow()
345
- .getDecorView()
346
- .removeCallbacks(delayedStartRunnable);
347
- });
348
- delayedStartRunnable = null;
349
- }
350
-
351
- stopSpeech();
352
- ignoreResultCount = 3;
353
- getActivity().runOnUiThread(() -> {
354
- getActivity()
355
- .getWindow()
356
- .getDecorView()
357
- .postDelayed(() -> {
358
- isDestroyed = false;
359
- isListening = true;
360
- speechIntent = null;
361
-
362
- notifyListeners(
363
- "partialResult",
364
- new JSObject()
365
- .put("text", "")
366
- .put("isFinal", false)
367
- );
368
-
369
- startListeningInternal(call, lang);
370
- }, 250);
371
- });
372
-
373
- call.resolve();
374
- }
375
-
376
-
377
-
378
- @PluginMethod
379
- public void stopListening(PluginCall call) {
380
- isListening = false;
381
- isDestroyed = true;
382
-
383
-
384
- if (delayedStartRunnable != null) {
385
- getActivity().runOnUiThread(() -> {
386
- getActivity()
387
- .getWindow()
388
- .getDecorView()
389
- .removeCallbacks(delayedStartRunnable);
390
- });
391
- delayedStartRunnable = null;
392
- }
393
-
394
- currentCall = null;
395
- stopSpeech();
396
- call.resolve();
397
- }
398
-
399
670
  @Override
400
671
  protected void handleOnDestroy() {
401
672
  isDestroyed = true;
402
673
  isListening = false;
403
674
 
404
-
405
675
  if (delayedStartRunnable != null) {
406
676
  getActivity().runOnUiThread(() -> {
407
677
  getActivity()
@@ -412,40 +682,17 @@ public void restartListening(PluginCall call) {
412
682
  delayedStartRunnable = null;
413
683
  }
414
684
 
685
+ cancelSilenceTimeout();
415
686
  stopSpeech();
416
687
  }
417
688
 
418
-
419
-
420
- private String mapError(int error) {
421
- switch (error) {
422
- case SpeechRecognizer.ERROR_AUDIO: return "Audio error";
423
- case SpeechRecognizer.ERROR_NETWORK: return "Network error";
424
- case SpeechRecognizer.ERROR_NO_MATCH: return "No speech recognized";
425
- case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: return "Speech timeout";
426
- default: return "Unknown error";
427
- }
428
- }
429
-
430
- private void scheduleSilenceRestart() {
431
- if (silenceRunnable != null) {
432
- getActivity().getWindow().getDecorView().removeCallbacks(silenceRunnable);
433
- }
434
-
435
- silenceRunnable = () -> {
436
- if (!isDestroyed && isListening && speechRecognizer != null && speechIntent != null) {
437
- try {
438
- speechRecognizer.startListening(speechIntent);
439
- } catch (Exception ignored) {}
689
+ private String mapError(int error) {
690
+ switch (error) {
691
+ case SpeechRecognizer.ERROR_AUDIO: return "Audio error";
692
+ case SpeechRecognizer.ERROR_NETWORK: return "Network error";
693
+ case SpeechRecognizer.ERROR_NO_MATCH: return "No speech recognized";
694
+ case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: return "Speech timeout";
695
+ default: return "Unknown error";
440
696
  }
441
- };
442
-
443
- getActivity()
444
- .getWindow()
445
- .getDecorView()
446
- .postDelayed(silenceRunnable, SILENCE_TIMEOUT_MS);
447
- }
448
-
449
-
450
-
697
+ }
451
698
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-microphone",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "plugin to use the microphone",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",