@capgo/capacitor-speech-recognition 7.0.0
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.
- package/CapgoCapacitorSpeechRecognition.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +360 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/app/capgo/speechrecognition/Constants.java +17 -0
- package/android/src/main/java/app/capgo/speechrecognition/Receiver.java +49 -0
- package/android/src/main/java/app/capgo/speechrecognition/SpeechRecognitionPlugin.java +406 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +438 -0
- package/dist/esm/definitions.d.ts +130 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +11 -0
- package/dist/esm/web.js +25 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +39 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +42 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/SpeechRecognitionPlugin/SpeechRecognitionPlugin.swift +282 -0
- package/ios/Tests/SpeechRecognitionPluginTests/SpeechRecognitionPluginTests.swift +7 -0
- package/package.json +87 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
package app.capgo.speechrecognition;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.Activity;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.os.Build;
|
|
7
|
+
import android.os.Bundle;
|
|
8
|
+
import android.speech.RecognitionListener;
|
|
9
|
+
import android.speech.RecognizerIntent;
|
|
10
|
+
import android.speech.SpeechRecognizer;
|
|
11
|
+
import androidx.activity.result.ActivityResult;
|
|
12
|
+
import com.getcapacitor.JSArray;
|
|
13
|
+
import com.getcapacitor.JSObject;
|
|
14
|
+
import com.getcapacitor.Logger;
|
|
15
|
+
import com.getcapacitor.PermissionState;
|
|
16
|
+
import com.getcapacitor.Plugin;
|
|
17
|
+
import com.getcapacitor.PluginCall;
|
|
18
|
+
import com.getcapacitor.PluginMethod;
|
|
19
|
+
import com.getcapacitor.annotation.ActivityCallback;
|
|
20
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
21
|
+
import com.getcapacitor.annotation.Permission;
|
|
22
|
+
import com.getcapacitor.annotation.PermissionCallback;
|
|
23
|
+
import java.util.ArrayList;
|
|
24
|
+
import java.util.List;
|
|
25
|
+
import java.util.Locale;
|
|
26
|
+
import java.util.concurrent.locks.ReentrantLock;
|
|
27
|
+
import org.json.JSONArray;
|
|
28
|
+
|
|
29
|
+
@CapacitorPlugin(
|
|
30
|
+
name = "SpeechRecognition",
|
|
31
|
+
permissions = { @Permission(strings = { Manifest.permission.RECORD_AUDIO }, alias = SpeechRecognitionPlugin.SPEECH_RECOGNITION) }
|
|
32
|
+
)
|
|
33
|
+
public class SpeechRecognitionPlugin extends Plugin implements Constants {
|
|
34
|
+
|
|
35
|
+
public static final String SPEECH_RECOGNITION = "speechRecognition";
|
|
36
|
+
private static final String TAG = "SpeechRecognition";
|
|
37
|
+
|
|
38
|
+
private Receiver languageReceiver;
|
|
39
|
+
private SpeechRecognizer speechRecognizer;
|
|
40
|
+
private final ReentrantLock lock = new ReentrantLock();
|
|
41
|
+
private boolean listening = false;
|
|
42
|
+
private JSONArray previousPartialResults = new JSONArray();
|
|
43
|
+
|
|
44
|
+
@Override
|
|
45
|
+
public void load() {
|
|
46
|
+
super.load();
|
|
47
|
+
bridge
|
|
48
|
+
.getWebView()
|
|
49
|
+
.post(() -> {
|
|
50
|
+
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(bridge.getActivity());
|
|
51
|
+
SpeechRecognitionListener listener = new SpeechRecognitionListener();
|
|
52
|
+
speechRecognizer.setRecognitionListener(listener);
|
|
53
|
+
Logger.info(getLogTag(), "Instantiated SpeechRecognizer in load()");
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@PluginMethod
|
|
58
|
+
public void available(PluginCall call) {
|
|
59
|
+
boolean val = SpeechRecognizer.isRecognitionAvailable(bridge.getContext());
|
|
60
|
+
call.resolve(new JSObject().put("available", val));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@PluginMethod
|
|
64
|
+
public void start(PluginCall call) {
|
|
65
|
+
if (!SpeechRecognizer.isRecognitionAvailable(bridge.getContext())) {
|
|
66
|
+
call.unavailable(NOT_AVAILABLE);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (getPermissionState(SPEECH_RECOGNITION) != PermissionState.GRANTED) {
|
|
71
|
+
call.reject(MISSING_PERMISSION);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
String language = call.getString("language", Locale.getDefault().toString());
|
|
76
|
+
int maxResults = call.getInt("maxResults", MAX_RESULTS);
|
|
77
|
+
String prompt = call.getString("prompt", null);
|
|
78
|
+
boolean partialResults = call.getBoolean("partialResults", false);
|
|
79
|
+
boolean popup = call.getBoolean("popup", false);
|
|
80
|
+
int allowForSilence = call.getInt("allowForSilence", 0);
|
|
81
|
+
beginListening(language, maxResults, prompt, partialResults, popup, call, allowForSilence);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@PluginMethod
|
|
85
|
+
public void stop(final PluginCall call) {
|
|
86
|
+
try {
|
|
87
|
+
stopListening();
|
|
88
|
+
} catch (Exception ex) {
|
|
89
|
+
call.reject(ex.getLocalizedMessage());
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
call.resolve();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@PluginMethod
|
|
96
|
+
public void getSupportedLanguages(PluginCall call) {
|
|
97
|
+
if (languageReceiver == null) {
|
|
98
|
+
languageReceiver = new Receiver(call);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
List<String> supportedLanguages = languageReceiver.getSupportedLanguages();
|
|
102
|
+
if (supportedLanguages != null) {
|
|
103
|
+
JSONArray languages = new JSONArray(supportedLanguages);
|
|
104
|
+
call.resolve(new JSObject().put("languages", languages));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Intent detailsIntent = new Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS);
|
|
109
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
110
|
+
detailsIntent.setPackage("com.google.android.googlequicksearchbox");
|
|
111
|
+
}
|
|
112
|
+
bridge.getActivity().sendOrderedBroadcast(detailsIntent, null, languageReceiver, null, Activity.RESULT_OK, null, null);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@PluginMethod
|
|
116
|
+
public void isListening(PluginCall call) {
|
|
117
|
+
call.resolve(new JSObject().put("listening", listening));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@PluginMethod
|
|
121
|
+
@Override
|
|
122
|
+
public void checkPermissions(PluginCall call) {
|
|
123
|
+
String state = permissionStateValue(getPermissionState(SPEECH_RECOGNITION));
|
|
124
|
+
call.resolve(new JSObject().put("speechRecognition", state));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@PluginMethod
|
|
128
|
+
@Override
|
|
129
|
+
public void requestPermissions(PluginCall call) {
|
|
130
|
+
requestPermissionForAlias(SPEECH_RECOGNITION, call, "permissionsCallback");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@PermissionCallback
|
|
134
|
+
private void permissionsCallback(PluginCall call) {
|
|
135
|
+
String state = permissionStateValue(getPermissionState(SPEECH_RECOGNITION));
|
|
136
|
+
call.resolve(new JSObject().put("speechRecognition", state));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@ActivityCallback
|
|
140
|
+
private void listeningResult(PluginCall call, ActivityResult result) {
|
|
141
|
+
if (call == null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
int resultCode = result.getResultCode();
|
|
146
|
+
if (resultCode == Activity.RESULT_OK) {
|
|
147
|
+
try {
|
|
148
|
+
ArrayList<String> matchesList = result.getData().getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
|
|
149
|
+
JSObject resultObj = new JSObject();
|
|
150
|
+
resultObj.put("matches", new JSArray(matchesList));
|
|
151
|
+
call.resolve(resultObj);
|
|
152
|
+
} catch (Exception ex) {
|
|
153
|
+
call.reject(ex.getMessage());
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
call.reject(Integer.toString(resultCode));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
lock.lock();
|
|
160
|
+
listening(false);
|
|
161
|
+
lock.unlock();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private void beginListening(
|
|
165
|
+
String language,
|
|
166
|
+
int maxResults,
|
|
167
|
+
String prompt,
|
|
168
|
+
final boolean partialResults,
|
|
169
|
+
boolean showPopup,
|
|
170
|
+
PluginCall call,
|
|
171
|
+
int allowForSilence
|
|
172
|
+
) {
|
|
173
|
+
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
|
174
|
+
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
|
|
175
|
+
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
|
|
176
|
+
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
|
|
177
|
+
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, bridge.getActivity().getPackageName());
|
|
178
|
+
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, partialResults);
|
|
179
|
+
intent.putExtra("android.speech.extra.DICTATION_MODE", partialResults);
|
|
180
|
+
|
|
181
|
+
if (allowForSilence > 0) {
|
|
182
|
+
intent.putExtra(RecognizerIntent.EXTRA_SEGMENTED_SESSION, true);
|
|
183
|
+
intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, allowForSilence);
|
|
184
|
+
intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, allowForSilence);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (prompt != null) {
|
|
188
|
+
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (showPopup) {
|
|
192
|
+
startActivityForResult(call, intent, "listeningResult");
|
|
193
|
+
} else {
|
|
194
|
+
bridge
|
|
195
|
+
.getWebView()
|
|
196
|
+
.post(() -> {
|
|
197
|
+
try {
|
|
198
|
+
lock.lock();
|
|
199
|
+
|
|
200
|
+
if (speechRecognizer != null) {
|
|
201
|
+
speechRecognizer.cancel();
|
|
202
|
+
speechRecognizer.destroy();
|
|
203
|
+
speechRecognizer = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(bridge.getActivity());
|
|
207
|
+
SpeechRecognitionListener listener = new SpeechRecognitionListener();
|
|
208
|
+
listener.setCall(call);
|
|
209
|
+
listener.setPartialResults(partialResults);
|
|
210
|
+
speechRecognizer.setRecognitionListener(listener);
|
|
211
|
+
speechRecognizer.startListening(intent);
|
|
212
|
+
listening(true);
|
|
213
|
+
if (partialResults) {
|
|
214
|
+
call.resolve();
|
|
215
|
+
}
|
|
216
|
+
} catch (Exception ex) {
|
|
217
|
+
call.reject(ex.getMessage());
|
|
218
|
+
} finally {
|
|
219
|
+
lock.unlock();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private void stopListening() {
|
|
226
|
+
bridge
|
|
227
|
+
.getWebView()
|
|
228
|
+
.post(() -> {
|
|
229
|
+
try {
|
|
230
|
+
lock.lock();
|
|
231
|
+
if (listening) {
|
|
232
|
+
speechRecognizer.stopListening();
|
|
233
|
+
listening(false);
|
|
234
|
+
}
|
|
235
|
+
} catch (Exception ex) {
|
|
236
|
+
throw ex;
|
|
237
|
+
} finally {
|
|
238
|
+
lock.unlock();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private void listening(boolean value) {
|
|
244
|
+
this.listening = value;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private String permissionStateValue(PermissionState state) {
|
|
248
|
+
switch (state) {
|
|
249
|
+
case GRANTED:
|
|
250
|
+
return "granted";
|
|
251
|
+
case DENIED:
|
|
252
|
+
return "denied";
|
|
253
|
+
case PROMPT:
|
|
254
|
+
case PROMPT_WITH_RATIONALE:
|
|
255
|
+
default:
|
|
256
|
+
return "prompt";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private class SpeechRecognitionListener implements RecognitionListener {
|
|
261
|
+
|
|
262
|
+
private PluginCall call;
|
|
263
|
+
private boolean partialResults;
|
|
264
|
+
|
|
265
|
+
public void setCall(PluginCall call) {
|
|
266
|
+
this.call = call;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public void setPartialResults(boolean partialResults) {
|
|
270
|
+
this.partialResults = partialResults;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@Override
|
|
274
|
+
public void onReadyForSpeech(Bundle params) {}
|
|
275
|
+
|
|
276
|
+
@Override
|
|
277
|
+
public void onBeginningOfSpeech() {
|
|
278
|
+
try {
|
|
279
|
+
lock.lock();
|
|
280
|
+
JSObject ret = new JSObject();
|
|
281
|
+
ret.put("status", "started");
|
|
282
|
+
notifyListeners(LISTENING_EVENT, ret);
|
|
283
|
+
} finally {
|
|
284
|
+
lock.unlock();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@Override
|
|
289
|
+
public void onRmsChanged(float rmsdB) {}
|
|
290
|
+
|
|
291
|
+
@Override
|
|
292
|
+
public void onBufferReceived(byte[] buffer) {}
|
|
293
|
+
|
|
294
|
+
@Override
|
|
295
|
+
public void onEndOfSpeech() {
|
|
296
|
+
bridge
|
|
297
|
+
.getWebView()
|
|
298
|
+
.post(() -> {
|
|
299
|
+
try {
|
|
300
|
+
lock.lock();
|
|
301
|
+
listening(false);
|
|
302
|
+
|
|
303
|
+
JSObject ret = new JSObject();
|
|
304
|
+
ret.put("status", "stopped");
|
|
305
|
+
notifyListeners(LISTENING_EVENT, ret);
|
|
306
|
+
} finally {
|
|
307
|
+
lock.unlock();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@Override
|
|
313
|
+
public void onError(int error) {
|
|
314
|
+
stopListening();
|
|
315
|
+
String errorMssg = getErrorText(error);
|
|
316
|
+
|
|
317
|
+
if (call != null) {
|
|
318
|
+
call.reject(errorMssg);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@Override
|
|
323
|
+
public void onResults(Bundle results) {
|
|
324
|
+
ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
JSArray jsArray = new JSArray(matches);
|
|
328
|
+
|
|
329
|
+
if (call != null) {
|
|
330
|
+
if (!partialResults) {
|
|
331
|
+
call.resolve(new JSObject().put("status", "success").put("matches", jsArray));
|
|
332
|
+
} else {
|
|
333
|
+
JSObject ret = new JSObject();
|
|
334
|
+
ret.put("matches", jsArray);
|
|
335
|
+
notifyListeners(PARTIAL_RESULTS_EVENT, ret);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (Exception ex) {
|
|
339
|
+
if (call != null) {
|
|
340
|
+
call.resolve(new JSObject().put("status", "error").put("message", ex.getMessage()));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@Override
|
|
346
|
+
public void onPartialResults(Bundle partialResultsBundle) {
|
|
347
|
+
ArrayList<String> matches = partialResultsBundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
|
348
|
+
JSArray matchesJSON = new JSArray(matches);
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
if (matches != null && matches.size() > 0 && !previousPartialResults.equals(matchesJSON)) {
|
|
352
|
+
previousPartialResults = matchesJSON;
|
|
353
|
+
JSObject ret = new JSObject();
|
|
354
|
+
ret.put("matches", previousPartialResults);
|
|
355
|
+
notifyListeners(PARTIAL_RESULTS_EVENT, ret);
|
|
356
|
+
}
|
|
357
|
+
} catch (Exception ex) {}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@Override
|
|
361
|
+
public void onSegmentResults(Bundle results) {
|
|
362
|
+
ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
|
363
|
+
if (matches == null) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
JSObject ret = new JSObject();
|
|
368
|
+
ret.put("matches", new JSArray(matches));
|
|
369
|
+
notifyListeners(SEGMENT_RESULTS_EVENT, ret);
|
|
370
|
+
} catch (Exception ignored) {}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@Override
|
|
374
|
+
public void onEndOfSegmentedSession() {
|
|
375
|
+
notifyListeners(END_OF_SEGMENT_EVENT, new JSObject());
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@Override
|
|
379
|
+
public void onEvent(int eventType, Bundle params) {}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private String getErrorText(int errorCode) {
|
|
383
|
+
switch (errorCode) {
|
|
384
|
+
case SpeechRecognizer.ERROR_AUDIO:
|
|
385
|
+
return "Audio recording error";
|
|
386
|
+
case SpeechRecognizer.ERROR_CLIENT:
|
|
387
|
+
return "Client side error";
|
|
388
|
+
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
|
|
389
|
+
return "Insufficient permissions";
|
|
390
|
+
case SpeechRecognizer.ERROR_NETWORK:
|
|
391
|
+
return "Network error";
|
|
392
|
+
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
|
|
393
|
+
return "Network timeout";
|
|
394
|
+
case SpeechRecognizer.ERROR_NO_MATCH:
|
|
395
|
+
return "No match";
|
|
396
|
+
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
|
|
397
|
+
return "RecognitionService busy";
|
|
398
|
+
case SpeechRecognizer.ERROR_SERVER:
|
|
399
|
+
return "Error from server";
|
|
400
|
+
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
|
|
401
|
+
return "No speech input";
|
|
402
|
+
default:
|
|
403
|
+
return "Didn't understand, please try again.";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
File without changes
|