react-native-wakeword-sid 1.1.203 → 1.1.205
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/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar +0 -0
- package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.md5 +1 -1
- package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.sha1 +1 -1
- package/android/src/main/java/com/davoice/keywordspotting/KeyWordRNBridge.java +703 -85
- package/package.json +1 -1
- package/wakewords/SpeakerVerificationRNBridge.js +8 -26
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
13fe069bbd5879f49a4ac72d819bb550 keyworddetection-1.0.0.aar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
84d3a2d0f926a3369afa202996e8e36747664f26 keyworddetection-1.0.0.aar
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
package com.davoice.keywordspotting;
|
|
2
2
|
|
|
3
3
|
import com.davoice.keywordsdetection.keywordslibrary.KeyWordsDetection;
|
|
4
|
+
import com.davoice.keywordsdetection.keywordslibrary.SpeakerVerification;
|
|
5
|
+
|
|
6
|
+
import org.json.JSONObject;
|
|
7
|
+
import org.json.JSONException;
|
|
8
|
+
|
|
9
|
+
import java.io.*;
|
|
10
|
+
import java.nio.ByteBuffer;
|
|
11
|
+
import java.nio.ByteOrder;
|
|
12
|
+
import java.util.*;
|
|
13
|
+
|
|
4
14
|
import com.facebook.react.bridge.*;
|
|
5
15
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
6
16
|
import androidx.annotation.Nullable;
|
|
@@ -13,6 +23,21 @@ public class KeyWordRNBridge extends ReactContextBaseJavaModule {
|
|
|
13
23
|
private final String TAG = "KeyWordsDetection";
|
|
14
24
|
private static final String REACT_CLASS = "KeyWordRNBridge";
|
|
15
25
|
private static ReactApplicationContext reactContext;
|
|
26
|
+
// ===============================
|
|
27
|
+
// Speaker Verification holders
|
|
28
|
+
// ===============================
|
|
29
|
+
private static final String SV_TAG = "SV.RNBridge";
|
|
30
|
+
|
|
31
|
+
private static final class SVEngineHolder {
|
|
32
|
+
String engineId;
|
|
33
|
+
SpeakerVerification.SpeakerVerificationConfig cfg;
|
|
34
|
+
SpeakerVerification.SpeakerVerificationEngine engine;
|
|
35
|
+
SpeakerVerification.SpeakerEnrollment enrollment;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private final Map<String, SVEngineHolder> svEngines = new HashMap<>();
|
|
39
|
+
|
|
40
|
+
private final Map<String, SpeakerVerification.SpeakerVerificationMicController> svMicControllers = new HashMap<>();
|
|
16
41
|
|
|
17
42
|
// VAD API:
|
|
18
43
|
private final Map<String, Float> vadThresholdByInstance = new HashMap<>();
|
|
@@ -223,113 +248,706 @@ public class KeyWordRNBridge extends ReactContextBaseJavaModule {
|
|
|
223
248
|
.emit(eventName, params);
|
|
224
249
|
}
|
|
225
250
|
|
|
226
|
-
|
|
251
|
+
// VAD API:
|
|
227
252
|
|
|
228
|
-
// ===== Add: VAD parity methods (anywhere among other @ReactMethod methods) =====
|
|
229
|
-
@ReactMethod
|
|
230
|
-
public void getVoiceProps(String instanceId, Promise promise) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
253
|
+
// ===== Add: VAD parity methods (anywhere among other @ReactMethod methods) =====
|
|
254
|
+
@ReactMethod
|
|
255
|
+
public void getVoiceProps(String instanceId, Promise promise) {
|
|
256
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
257
|
+
if (instance == null) {
|
|
258
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
@SuppressWarnings("unchecked")
|
|
263
|
+
Map<String, Object> props = instance.getVoiceProps();
|
|
264
|
+
WritableMap out = Arguments.createMap();
|
|
240
265
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
266
|
+
Object err = props.get("error");
|
|
267
|
+
Object prob = props.get("voiceProbability");
|
|
268
|
+
Object last = props.get("lastTimeHumanVoiceHeard");
|
|
244
269
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
270
|
+
out.putString("error", err == null ? "" : String.valueOf(err));
|
|
271
|
+
out.putDouble("voiceProbability", prob instanceof Number ? ((Number) prob).doubleValue() : 0.0);
|
|
272
|
+
out.putDouble("lastTimeHumanVoiceHeard", last instanceof Number ? ((Number) last).doubleValue() : 0.0);
|
|
248
273
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
274
|
+
promise.resolve(out);
|
|
275
|
+
} catch (Exception e) {
|
|
276
|
+
promise.reject("GetVoicePropsError", e.getMessage());
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@ReactMethod
|
|
281
|
+
public void startVADDetection(String instanceId, Promise promise) {
|
|
282
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
283
|
+
if (instance == null) {
|
|
284
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
// after: API-21 safe
|
|
289
|
+
Float _thr = vadThresholdByInstance.get(instanceId);
|
|
290
|
+
float thr = (_thr != null) ? _thr : DEFAULT_VAD_THRESHOLD;
|
|
291
|
+
Integer _win = vadMsWindowByInstance.get(instanceId);
|
|
292
|
+
int win = (_win != null) ? _win : DEFAULT_VAD_MSWINDOW;
|
|
293
|
+
instance.setVADParams(thr, win);
|
|
294
|
+
boolean ok = instance.startVADListening();
|
|
295
|
+
promise.resolve(ok);
|
|
296
|
+
} catch (Exception e) {
|
|
297
|
+
promise.reject("StartVADError", e.getMessage());
|
|
298
|
+
}
|
|
252
299
|
}
|
|
253
|
-
}
|
|
254
300
|
|
|
255
|
-
@ReactMethod
|
|
256
|
-
public void
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
301
|
+
@ReactMethod
|
|
302
|
+
public void stopVADDetection(String instanceId, Promise promise) {
|
|
303
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
304
|
+
if (instance == null) {
|
|
305
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
instance.stopVADListening();
|
|
310
|
+
promise.resolve("Stopped VAD for instance: " + instanceId);
|
|
311
|
+
} catch (Exception e) {
|
|
312
|
+
promise.reject("StopVADError", e.getMessage());
|
|
313
|
+
}
|
|
261
314
|
}
|
|
262
|
-
|
|
263
|
-
|
|
315
|
+
|
|
316
|
+
@ReactMethod
|
|
317
|
+
public void setVADParams(String instanceId, double threshold, int msWindow, Promise promise) {
|
|
318
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
319
|
+
if (instance == null) {
|
|
320
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
float thr = (float) threshold;
|
|
325
|
+
vadThresholdByInstance.put(instanceId, thr);
|
|
326
|
+
vadMsWindowByInstance.put(instanceId, msWindow);
|
|
327
|
+
instance.setVADParams(thr, msWindow);
|
|
328
|
+
promise.resolve(null);
|
|
329
|
+
} catch (Exception e) {
|
|
330
|
+
promise.reject("SetVADParamsError", e.getMessage());
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@ReactMethod
|
|
335
|
+
public void getVADParams(String instanceId, Promise promise) {
|
|
336
|
+
if (!instances.containsKey(instanceId)) {
|
|
337
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
WritableMap out = Arguments.createMap();
|
|
264
341
|
Float _thr = vadThresholdByInstance.get(instanceId);
|
|
265
342
|
float thr = (_thr != null) ? _thr : DEFAULT_VAD_THRESHOLD;
|
|
266
343
|
Integer _win = vadMsWindowByInstance.get(instanceId);
|
|
267
344
|
int win = (_win != null) ? _win : DEFAULT_VAD_MSWINDOW;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
promise.resolve(
|
|
271
|
-
}
|
|
272
|
-
|
|
345
|
+
out.putDouble("threshold", (double) thr);
|
|
346
|
+
out.putInt("msWindow", win);
|
|
347
|
+
promise.resolve(out);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@ReactMethod
|
|
351
|
+
public void addListener(String eventName) {
|
|
352
|
+
// Set up any upstream listeners or background tasks as necessary
|
|
273
353
|
}
|
|
274
|
-
}
|
|
275
354
|
|
|
276
|
-
@ReactMethod
|
|
277
|
-
public void
|
|
278
|
-
|
|
279
|
-
if (instance == null) {
|
|
280
|
-
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
281
|
-
return;
|
|
355
|
+
@ReactMethod
|
|
356
|
+
public void removeListeners(Integer count) {
|
|
357
|
+
// Remove upstream listeners, stop unnecessary background tasks
|
|
282
358
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
359
|
+
|
|
360
|
+
// Implement other methods as needed, ensuring to use instanceId
|
|
361
|
+
private String stripFileSchemeAndroid(String s) {
|
|
362
|
+
if (s == null) return null;
|
|
363
|
+
if (s.startsWith("file://")) return s.replace("file://", "");
|
|
364
|
+
return s;
|
|
288
365
|
}
|
|
289
|
-
}
|
|
290
366
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
367
|
+
// ********** SPEAKER IDENTIFICATION APIs **********
|
|
368
|
+
// Resolves:
|
|
369
|
+
// - absolute filesystem path -> returns as-is
|
|
370
|
+
// - "asset:/name.onnx" or "assets:/name.onnx" -> copies from APK assets to cache, returns real path
|
|
371
|
+
// - "name.onnx" (bundle asset name) -> copies from assets to cache
|
|
372
|
+
private String resolveToRealFilePath(String input) throws IOException {
|
|
373
|
+
input = stripFileSchemeAndroid(input);
|
|
374
|
+
if (input == null || input.isEmpty()) return input;
|
|
375
|
+
|
|
376
|
+
// Absolute existing path
|
|
377
|
+
if (input.startsWith("/") && new File(input).exists()) {
|
|
378
|
+
return input;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// RN Android require(...) often yields "asset:/..."
|
|
382
|
+
String assetName = input;
|
|
383
|
+
if (assetName.startsWith("asset:/")) assetName = assetName.substring("asset:/".length());
|
|
384
|
+
if (assetName.startsWith("assets:/")) assetName = assetName.substring("assets:/".length());
|
|
385
|
+
|
|
386
|
+
// If still contains directories like "assets/.../name.onnx", keep last component
|
|
387
|
+
// (your iOS resolver scans bundle; Android assets are flat-ish but can be nested)
|
|
388
|
+
// We'll try direct open first; if fails, try lastPathComponent.
|
|
389
|
+
String try1 = assetName;
|
|
390
|
+
String try2 = new File(assetName).getName();
|
|
391
|
+
|
|
392
|
+
File out1 = copyAssetToCacheIfExists(try1);
|
|
393
|
+
if (out1 != null) return out1.getAbsolutePath();
|
|
394
|
+
|
|
395
|
+
File out2 = copyAssetToCacheIfExists(try2);
|
|
396
|
+
if (out2 != null) return out2.getAbsolutePath();
|
|
397
|
+
|
|
398
|
+
// Not found
|
|
399
|
+
throw new FileNotFoundException("Cannot resolve asset/path: " + input + " (tried " + try1 + " and " + try2 + ")");
|
|
306
400
|
}
|
|
307
|
-
}
|
|
308
401
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
402
|
+
private File copyAssetToCacheIfExists(String assetName) {
|
|
403
|
+
if (assetName == null || assetName.isEmpty()) return null;
|
|
404
|
+
try (InputStream is = reactContext.getAssets().open(assetName)) {
|
|
405
|
+
File out = new File(reactContext.getCacheDir(), assetName);
|
|
406
|
+
// ensure parent
|
|
407
|
+
File parent = out.getParentFile();
|
|
408
|
+
if (parent != null) parent.mkdirs();
|
|
409
|
+
|
|
410
|
+
try (OutputStream os = new FileOutputStream(out)) {
|
|
411
|
+
byte[] buf = new byte[64 * 1024];
|
|
412
|
+
int n;
|
|
413
|
+
while ((n = is.read(buf)) > 0) os.write(buf, 0, n);
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
} catch (Throwable ignore) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private SpeakerVerification.SpeakerVerificationConfig parseSVMicConfigJson(String configJson) throws JSONException, IOException {
|
|
422
|
+
JSONObject root = new JSONObject(configJson == null ? "{}" : configJson);
|
|
423
|
+
|
|
424
|
+
SpeakerVerification.SpeakerVerificationConfig cfg = new SpeakerVerification.SpeakerVerificationConfig();
|
|
425
|
+
|
|
426
|
+
// Top-level fields (if you use them)
|
|
427
|
+
String modelPath = root.optString("modelPath", "");
|
|
428
|
+
int sampleRate = root.optInt("sampleRate", cfg.sampleRate);
|
|
429
|
+
int frameSize = root.optInt("frameSize", cfg.frameSize);
|
|
430
|
+
|
|
431
|
+
// iOS-style puts most stuff in "options"
|
|
432
|
+
JSONObject opts = root.optJSONObject("options");
|
|
433
|
+
if (opts != null) {
|
|
434
|
+
cfg.decisionThreshold = (float) opts.optDouble("decisionThreshold", cfg.decisionThreshold);
|
|
435
|
+
cfg.tailSeconds = (float) opts.optDouble("tailSeconds", cfg.tailSeconds);
|
|
436
|
+
cfg.maxTailSeconds = (float) opts.optDouble("maxTailSeconds", cfg.maxTailSeconds);
|
|
437
|
+
cfg.cmn = opts.optBoolean("cmn", cfg.cmn);
|
|
438
|
+
cfg.expectedLayoutBDT = opts.optBoolean("expectedLayoutBDT", cfg.expectedLayoutBDT);
|
|
439
|
+
|
|
440
|
+
// Prefer opts.frameSize if present
|
|
441
|
+
frameSize = opts.has("frameSize") ? opts.optInt("frameSize", frameSize) : frameSize;
|
|
442
|
+
sampleRate = opts.has("sampleRate") ? opts.optInt("sampleRate", sampleRate) : sampleRate;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
cfg.sampleRate = sampleRate > 0 ? sampleRate : 16000;
|
|
446
|
+
cfg.frameSize = frameSize > 0 ? frameSize : 1280;
|
|
447
|
+
|
|
448
|
+
// Resolve modelPath (MUST be real path for ORT)
|
|
449
|
+
if (modelPath != null && !modelPath.isEmpty()) {
|
|
450
|
+
cfg.modelPath = resolveToRealFilePath(modelPath);
|
|
451
|
+
} else {
|
|
452
|
+
// You can choose to throw here, but keeping consistent with iOS:
|
|
453
|
+
// iOS requires it; so Android should too.
|
|
454
|
+
throw new JSONException("Missing modelPath in configJson");
|
|
455
|
+
}
|
|
456
|
+
return cfg;
|
|
457
|
+
}
|
|
324
458
|
|
|
325
459
|
@ReactMethod
|
|
326
|
-
public void
|
|
327
|
-
|
|
460
|
+
public void createSpeakerVerifier(String engineId,
|
|
461
|
+
String modelPathOrName,
|
|
462
|
+
String enrollmentJsonPathOrName,
|
|
463
|
+
ReadableMap options,
|
|
464
|
+
Promise promise) {
|
|
465
|
+
if (svEngines.containsKey(engineId)) {
|
|
466
|
+
promise.reject("SVEngineExists", "Speaker verifier already exists with ID: " + engineId);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
new Thread(() -> {
|
|
471
|
+
try {
|
|
472
|
+
String modelPath = resolveToRealFilePath(modelPathOrName);
|
|
473
|
+
String jsonPath = resolveToRealFilePath(enrollmentJsonPathOrName);
|
|
474
|
+
|
|
475
|
+
// Build cfg from options (mirror iOS options)
|
|
476
|
+
SpeakerVerification.SpeakerVerificationConfig cfg = new SpeakerVerification.SpeakerVerificationConfig();
|
|
477
|
+
cfg.modelPath = modelPath;
|
|
478
|
+
|
|
479
|
+
if (options != null) {
|
|
480
|
+
if (options.hasKey("decisionThreshold")) cfg.decisionThreshold = (float) options.getDouble("decisionThreshold");
|
|
481
|
+
if (options.hasKey("frameSize")) cfg.frameSize = options.getInt("frameSize");
|
|
482
|
+
if (options.hasKey("tailSeconds")) cfg.tailSeconds = (float) options.getDouble("tailSeconds");
|
|
483
|
+
if (options.hasKey("maxTailSeconds")) cfg.maxTailSeconds = (float) options.getDouble("maxTailSeconds");
|
|
484
|
+
if (options.hasKey("cmn")) cfg.cmn = options.getBoolean("cmn");
|
|
485
|
+
if (options.hasKey("expectedLayoutBDT")) cfg.expectedLayoutBDT = options.getBoolean("expectedLayoutBDT");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Load enrollment json file -> string -> enrollment object
|
|
489
|
+
String enrollmentJson = readAllText(jsonPath);
|
|
490
|
+
SpeakerVerification.SpeakerEnrollment enrollment = SpeakerVerification.SpeakerEnrollment.fromJson(enrollmentJson);
|
|
491
|
+
|
|
492
|
+
SpeakerVerification.SpeakerVerificationEngine engine = new SpeakerVerification.SpeakerVerificationEngine(cfg);
|
|
493
|
+
engine.setEnrollment(enrollment);
|
|
494
|
+
|
|
495
|
+
SVEngineHolder h = new SVEngineHolder();
|
|
496
|
+
h.engineId = engineId;
|
|
497
|
+
h.cfg = cfg;
|
|
498
|
+
h.enrollment = enrollment;
|
|
499
|
+
h.engine = engine;
|
|
500
|
+
svEngines.put(engineId, h);
|
|
501
|
+
|
|
502
|
+
WritableMap out = Arguments.createMap();
|
|
503
|
+
out.putBoolean("ok", true);
|
|
504
|
+
out.putString("engineId", engineId);
|
|
505
|
+
out.putString("modelPath", modelPath);
|
|
506
|
+
out.putString("enrollmentJsonPath", jsonPath);
|
|
507
|
+
|
|
508
|
+
promise.resolve(out);
|
|
509
|
+
|
|
510
|
+
} catch (Throwable t) {
|
|
511
|
+
promise.reject("SVCreateError", String.valueOf(t.getMessage()), t);
|
|
512
|
+
}
|
|
513
|
+
}).start();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private String readAllText(String path) throws IOException {
|
|
517
|
+
try (InputStream is = new FileInputStream(path)) {
|
|
518
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
519
|
+
byte[] buf = new byte[64 * 1024];
|
|
520
|
+
int n;
|
|
521
|
+
while ((n = is.read(buf)) > 0) bos.write(buf, 0, n);
|
|
522
|
+
return bos.toString("UTF-8");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
@ReactMethod
|
|
527
|
+
public void verifySpeakerWavStreaming(String engineId,
|
|
528
|
+
String wavPathOrName,
|
|
529
|
+
boolean resetState,
|
|
530
|
+
Promise promise) {
|
|
531
|
+
SVEngineHolder h = svEngines.get(engineId);
|
|
532
|
+
if (h == null || h.engine == null) {
|
|
533
|
+
promise.reject("SVEngineNotFound", "No speaker verifier with ID: " + engineId);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
new Thread(() -> {
|
|
538
|
+
try {
|
|
539
|
+
String wavPath = resolveToRealFilePath(wavPathOrName);
|
|
540
|
+
|
|
541
|
+
if (resetState) {
|
|
542
|
+
h.engine.resetStreamingState();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
SpeakerVerification.SpeakerVerificationResult res =
|
|
546
|
+
runWavThroughEngineStreaming(h.engine, wavPath, h.cfg.frameSize);
|
|
547
|
+
|
|
548
|
+
WritableMap out = Arguments.createMap();
|
|
549
|
+
out.putBoolean("ok", true);
|
|
550
|
+
out.putString("engineId", engineId);
|
|
551
|
+
|
|
552
|
+
// Mirror iOS-ish keys (your JS can read bestScore/score etc)
|
|
553
|
+
out.putDouble("scoreBest", res.scoreBest);
|
|
554
|
+
out.putDouble("scoreMean", res.scoreMean);
|
|
555
|
+
out.putDouble("scoreWorst", res.scoreWorst);
|
|
556
|
+
out.putBoolean("isMatch", res.isMatch);
|
|
557
|
+
out.putInt("embeddingDim", res.embeddingDim);
|
|
558
|
+
out.putDouble("usedSeconds", res.usedSeconds);
|
|
559
|
+
|
|
560
|
+
promise.resolve(out);
|
|
561
|
+
|
|
562
|
+
} catch (Throwable t) {
|
|
563
|
+
promise.reject("SVVerifyError", String.valueOf(t.getMessage()), t);
|
|
564
|
+
}
|
|
565
|
+
}).start();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private SpeakerVerification.SpeakerVerificationResult runWavThroughEngineStreaming(
|
|
569
|
+
SpeakerVerification.SpeakerVerificationEngine engine,
|
|
570
|
+
String wavPath,
|
|
571
|
+
int frameSizeSamples
|
|
572
|
+
) throws Exception {
|
|
573
|
+
|
|
574
|
+
WavPcm16 wav = readWavPcm16Mono(wavPath);
|
|
575
|
+
if (wav.sampleRate != 16000) {
|
|
576
|
+
throw new IllegalArgumentException("WAV must be 16kHz. Got " + wav.sampleRate);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
final short[] pcm = wav.pcm16;
|
|
580
|
+
int i = 0;
|
|
581
|
+
|
|
582
|
+
while (i + frameSizeSamples <= pcm.length) {
|
|
583
|
+
float[] f = new float[frameSizeSamples];
|
|
584
|
+
for (int k = 0; k < frameSizeSamples; k++) {
|
|
585
|
+
f[k] = pcm[i + k] / 32768.0f;
|
|
586
|
+
}
|
|
587
|
+
i += frameSizeSamples;
|
|
588
|
+
|
|
589
|
+
SpeakerVerification.SpeakerVerificationOutput out = engine.processFrame(f);
|
|
590
|
+
if (out != null && out.type == SpeakerVerification.SpeakerVerificationOutput.Type.RESULT) {
|
|
591
|
+
return out.result;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
throw new IllegalStateException("NO_RESULT: WAV ended before engine produced RESULT");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Minimal WAV reader: PCM16 LE, mono.
|
|
599
|
+
private static final class WavPcm16 {
|
|
600
|
+
int sampleRate;
|
|
601
|
+
short[] pcm16;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private WavPcm16 readWavPcm16Mono(String path) throws IOException {
|
|
605
|
+
try (InputStream is = new BufferedInputStream(new FileInputStream(path))) {
|
|
606
|
+
byte[] header = new byte[12];
|
|
607
|
+
readFully(is, header);
|
|
608
|
+
|
|
609
|
+
// "RIFF" .... "WAVE"
|
|
610
|
+
if (!(header[0]=='R' && header[1]=='I' && header[2]=='F' && header[3]=='F')) {
|
|
611
|
+
throw new IOException("Not RIFF WAV");
|
|
612
|
+
}
|
|
613
|
+
if (!(header[8]=='W' && header[9]=='A' && header[10]=='V' && header[11]=='E')) {
|
|
614
|
+
throw new IOException("Not WAVE");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
int channels = -1, sampleRate = -1, bitsPerSample = -1;
|
|
618
|
+
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
|
619
|
+
|
|
620
|
+
while (true) {
|
|
621
|
+
byte[] chunkHdr = new byte[8];
|
|
622
|
+
int n = is.read(chunkHdr);
|
|
623
|
+
if (n < 0) break;
|
|
624
|
+
if (n != 8) throw new IOException("Bad WAV chunk header");
|
|
625
|
+
|
|
626
|
+
String id = new String(chunkHdr, 0, 4);
|
|
627
|
+
int size = ByteBuffer.wrap(chunkHdr, 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
|
628
|
+
|
|
629
|
+
if ("fmt ".equals(id)) {
|
|
630
|
+
byte[] fmt = new byte[size];
|
|
631
|
+
readFully(is, fmt);
|
|
632
|
+
|
|
633
|
+
int audioFormat = ByteBuffer.wrap(fmt, 0, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
|
634
|
+
channels = ByteBuffer.wrap(fmt, 2, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
|
635
|
+
sampleRate = ByteBuffer.wrap(fmt, 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
|
636
|
+
bitsPerSample = ByteBuffer.wrap(fmt, 14, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
|
637
|
+
|
|
638
|
+
if (audioFormat != 1) throw new IOException("WAV must be PCM (audioFormat=1). Got " + audioFormat);
|
|
639
|
+
} else if ("data".equals(id)) {
|
|
640
|
+
byte[] buf = new byte[64 * 1024];
|
|
641
|
+
int remaining = size;
|
|
642
|
+
while (remaining > 0) {
|
|
643
|
+
int r = is.read(buf, 0, Math.min(buf.length, remaining));
|
|
644
|
+
if (r < 0) throw new EOFException("WAV data truncated");
|
|
645
|
+
data.write(buf, 0, r);
|
|
646
|
+
remaining -= r;
|
|
647
|
+
}
|
|
648
|
+
} else {
|
|
649
|
+
// skip other chunks
|
|
650
|
+
long skipped = is.skip(size);
|
|
651
|
+
while (skipped < size) {
|
|
652
|
+
long s = is.skip(size - skipped);
|
|
653
|
+
if (s <= 0) break;
|
|
654
|
+
skipped += s;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// chunks are word-aligned
|
|
659
|
+
if ((size & 1) == 1) is.skip(1);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (channels != 1) throw new IOException("WAV must be mono. Got channels=" + channels);
|
|
663
|
+
if (bitsPerSample != 16) throw new IOException("WAV must be 16-bit. Got bits=" + bitsPerSample);
|
|
664
|
+
if (sampleRate <= 0) throw new IOException("Missing/invalid sampleRate");
|
|
665
|
+
|
|
666
|
+
byte[] raw = data.toByteArray();
|
|
667
|
+
short[] pcm16 = new short[raw.length / 2];
|
|
668
|
+
ByteBuffer bb = ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN);
|
|
669
|
+
for (int i = 0; i < pcm16.length; i++) pcm16[i] = bb.getShort();
|
|
670
|
+
|
|
671
|
+
WavPcm16 out = new WavPcm16();
|
|
672
|
+
out.sampleRate = sampleRate;
|
|
673
|
+
out.pcm16 = pcm16;
|
|
674
|
+
return out;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private void readFully(InputStream is, byte[] buf) throws IOException {
|
|
679
|
+
int off = 0;
|
|
680
|
+
while (off < buf.length) {
|
|
681
|
+
int n = is.read(buf, off, buf.length - off);
|
|
682
|
+
if (n < 0) throw new EOFException("Unexpected EOF");
|
|
683
|
+
off += n;
|
|
684
|
+
}
|
|
328
685
|
}
|
|
329
686
|
|
|
330
687
|
@ReactMethod
|
|
331
|
-
public void
|
|
332
|
-
|
|
688
|
+
public void destroySpeakerVerifier(String engineId, Promise promise) {
|
|
689
|
+
SVEngineHolder h = svEngines.remove(engineId);
|
|
690
|
+
if (h == null) {
|
|
691
|
+
promise.reject("SVEngineNotFound", "No speaker verifier with ID: " + engineId);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
WritableMap out = Arguments.createMap();
|
|
695
|
+
out.putBoolean("ok", true);
|
|
696
|
+
out.putString("engineId", engineId);
|
|
697
|
+
promise.resolve(out);
|
|
333
698
|
}
|
|
334
|
-
|
|
699
|
+
|
|
700
|
+
private WritableMap toWritableMap(Map<String, Object> m) {
|
|
701
|
+
WritableMap out = Arguments.createMap();
|
|
702
|
+
if (m == null) return out;
|
|
703
|
+
|
|
704
|
+
for (Map.Entry<String, Object> e : m.entrySet()) {
|
|
705
|
+
String k = e.getKey();
|
|
706
|
+
Object v = e.getValue();
|
|
707
|
+
if (v == null) continue;
|
|
708
|
+
|
|
709
|
+
if (v instanceof String) out.putString(k, (String) v);
|
|
710
|
+
else if (v instanceof Boolean) out.putBoolean(k, (Boolean) v);
|
|
711
|
+
else if (v instanceof Integer) out.putInt(k, (Integer) v);
|
|
712
|
+
else if (v instanceof Long) out.putDouble(k, ((Long) v).doubleValue());
|
|
713
|
+
else if (v instanceof Float) out.putDouble(k, ((Float) v).doubleValue());
|
|
714
|
+
else if (v instanceof Double) out.putDouble(k, (Double) v);
|
|
715
|
+
else out.putString(k, String.valueOf(v));
|
|
716
|
+
}
|
|
717
|
+
return out;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
private void sendEventUi(String eventName, WritableMap params) {
|
|
721
|
+
reactContext.runOnUiQueueThread(() -> sendEvent(eventName, params));
|
|
722
|
+
}
|
|
723
|
+
@ReactMethod
|
|
724
|
+
public void createSpeakerVerificationMicController(String controllerId,
|
|
725
|
+
String configJson,
|
|
726
|
+
Promise promise) {
|
|
727
|
+
if (svMicControllers.containsKey(controllerId)) {
|
|
728
|
+
promise.reject("SVMicExists", "Speaker mic controller already exists with ID: " + controllerId);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
new Thread(() -> {
|
|
733
|
+
try {
|
|
734
|
+
SpeakerVerification.SpeakerVerificationConfig cfg = parseSVMicConfigJson(configJson);
|
|
735
|
+
|
|
736
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl =
|
|
737
|
+
new SpeakerVerification.SpeakerVerificationMicController(reactContext, cfg);
|
|
738
|
+
|
|
739
|
+
// Delegate proxy -> RN events
|
|
740
|
+
ctrl.delegate = new SpeakerVerification.SpeakerVerificationNativeDelegate() {
|
|
741
|
+
@Override
|
|
742
|
+
public void svOnboardingProgress(Map<String, Object> info) {
|
|
743
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
744
|
+
m.put("controllerId", controllerId);
|
|
745
|
+
sendEventUi("onSpeakerVerificationOnboardingProgress", toWritableMap(m));
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
@Override
|
|
749
|
+
public void svOnboardingDone(Map<String, Object> info) {
|
|
750
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
751
|
+
m.put("controllerId", controllerId);
|
|
752
|
+
sendEventUi("onSpeakerVerificationOnboardingDone", toWritableMap(m));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
@Override
|
|
756
|
+
public void svVerifyResult(Map<String, Object> info) {
|
|
757
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
758
|
+
m.put("controllerId", controllerId);
|
|
759
|
+
sendEventUi("onSpeakerVerificationVerifyResult", toWritableMap(m));
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
@Override
|
|
763
|
+
public void svError(Map<String, Object> info) {
|
|
764
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
765
|
+
m.put("controllerId", controllerId);
|
|
766
|
+
sendEventUi("onSpeakerVerificationError", toWritableMap(m));
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
svMicControllers.put(controllerId, ctrl);
|
|
771
|
+
|
|
772
|
+
WritableMap out = Arguments.createMap();
|
|
773
|
+
out.putBoolean("ok", true);
|
|
774
|
+
out.putString("controllerId", controllerId);
|
|
775
|
+
promise.resolve(out);
|
|
776
|
+
|
|
777
|
+
} catch (Throwable t) {
|
|
778
|
+
promise.reject("SVMicCreateError", String.valueOf(t.getMessage()), t);
|
|
779
|
+
}
|
|
780
|
+
}).start();
|
|
781
|
+
}
|
|
782
|
+
@ReactMethod
|
|
783
|
+
public void destroySpeakerVerificationMicController(String controllerId, Promise promise) {
|
|
784
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.remove(controllerId);
|
|
785
|
+
if (ctrl == null) {
|
|
786
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
try { ctrl.stop(); } catch (Throwable ignore) {}
|
|
791
|
+
|
|
792
|
+
WritableMap out = Arguments.createMap();
|
|
793
|
+
out.putBoolean("ok", true);
|
|
794
|
+
out.putString("controllerId", controllerId);
|
|
795
|
+
promise.resolve(out);
|
|
796
|
+
}
|
|
797
|
+
@ReactMethod
|
|
798
|
+
public void svBeginOnboarding(String controllerId,
|
|
799
|
+
String enrollmentId,
|
|
800
|
+
int targetEmbeddingCount,
|
|
801
|
+
boolean reset,
|
|
802
|
+
Promise promise) {
|
|
803
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
804
|
+
if (ctrl == null) {
|
|
805
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
new Thread(() -> {
|
|
810
|
+
try {
|
|
811
|
+
ctrl.beginOnboarding(enrollmentId, targetEmbeddingCount, reset);
|
|
812
|
+
|
|
813
|
+
WritableMap out = Arguments.createMap();
|
|
814
|
+
out.putBoolean("ok", true);
|
|
815
|
+
out.putString("controllerId", controllerId);
|
|
816
|
+
out.putString("enrollmentId", enrollmentId);
|
|
817
|
+
out.putInt("target", targetEmbeddingCount);
|
|
818
|
+
promise.resolve(out);
|
|
819
|
+
} catch (Throwable t) {
|
|
820
|
+
promise.reject("SVMicBeginError", String.valueOf(t.getMessage()), t);
|
|
821
|
+
}
|
|
822
|
+
}).start();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
@ReactMethod
|
|
826
|
+
public void svGetNextEmbeddingFromMic(String controllerId, Promise promise) {
|
|
827
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
828
|
+
if (ctrl == null) {
|
|
829
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
new Thread(() -> {
|
|
834
|
+
try {
|
|
835
|
+
ctrl.getNextEmbeddingFromMic();
|
|
836
|
+
WritableMap out = Arguments.createMap();
|
|
837
|
+
out.putBoolean("ok", true);
|
|
838
|
+
out.putString("controllerId", controllerId);
|
|
839
|
+
promise.resolve(out);
|
|
840
|
+
} catch (Throwable t) {
|
|
841
|
+
promise.reject("SVMicGetNextError", String.valueOf(t.getMessage()), t);
|
|
842
|
+
}
|
|
843
|
+
}).start();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
@ReactMethod
|
|
847
|
+
public void svFinalizeOnboardingNow(String controllerId, Promise promise) {
|
|
848
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
849
|
+
if (ctrl == null) {
|
|
850
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
new Thread(() -> {
|
|
855
|
+
try {
|
|
856
|
+
ctrl.finalizeOnboardingNow();
|
|
857
|
+
WritableMap out = Arguments.createMap();
|
|
858
|
+
out.putBoolean("ok", true);
|
|
859
|
+
out.putString("controllerId", controllerId);
|
|
860
|
+
promise.resolve(out);
|
|
861
|
+
} catch (Throwable t) {
|
|
862
|
+
promise.reject("SVMicFinalizeError", String.valueOf(t.getMessage()), t);
|
|
863
|
+
}
|
|
864
|
+
}).start();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
@ReactMethod
|
|
868
|
+
public void svSetEnrollmentJson(String controllerId, String enrollmentJson, Promise promise) {
|
|
869
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
870
|
+
if (ctrl == null) {
|
|
871
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
new Thread(() -> {
|
|
876
|
+
try {
|
|
877
|
+
ctrl.setEnrollmentJson(enrollmentJson);
|
|
878
|
+
WritableMap out = Arguments.createMap();
|
|
879
|
+
out.putBoolean("ok", true);
|
|
880
|
+
out.putString("controllerId", controllerId);
|
|
881
|
+
promise.resolve(out);
|
|
882
|
+
} catch (Throwable t) {
|
|
883
|
+
promise.reject("SVMicSetEnrollError", String.valueOf(t.getMessage()), t);
|
|
884
|
+
}
|
|
885
|
+
}).start();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
@ReactMethod
|
|
889
|
+
public void svStartVerifyFromMic(String controllerId, boolean resetState, Promise promise) {
|
|
890
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
891
|
+
if (ctrl == null) {
|
|
892
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
new Thread(() -> {
|
|
897
|
+
try {
|
|
898
|
+
ctrl.startVerifyFromMic(resetState);
|
|
899
|
+
WritableMap out = Arguments.createMap();
|
|
900
|
+
out.putBoolean("ok", true);
|
|
901
|
+
out.putString("controllerId", controllerId);
|
|
902
|
+
out.putBoolean("resetState", resetState);
|
|
903
|
+
promise.resolve(out);
|
|
904
|
+
} catch (Throwable t) {
|
|
905
|
+
promise.reject("SVMicStartVerifyError", String.valueOf(t.getMessage()), t);
|
|
906
|
+
}
|
|
907
|
+
}).start();
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
@ReactMethod
|
|
911
|
+
public void svStartEndlessVerifyFromMic(String controllerId, double hopSeconds, boolean stopOnMatch, boolean resetState, Promise promise) {
|
|
912
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
913
|
+
if (ctrl == null) {
|
|
914
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
new Thread(() -> {
|
|
919
|
+
try {
|
|
920
|
+
ctrl.startEndlessVerifyFromMic((float) hopSeconds, stopOnMatch, resetState);
|
|
921
|
+
WritableMap out = Arguments.createMap();
|
|
922
|
+
out.putBoolean("ok", true);
|
|
923
|
+
out.putString("controllerId", controllerId);
|
|
924
|
+
out.putDouble("hopSeconds", hopSeconds);
|
|
925
|
+
out.putBoolean("stopOnMatch", stopOnMatch);
|
|
926
|
+
out.putBoolean("resetState", resetState);
|
|
927
|
+
promise.resolve(out);
|
|
928
|
+
} catch (Throwable t) {
|
|
929
|
+
promise.reject("SVMicStartEndlessVerifyError", String.valueOf(t.getMessage()), t);
|
|
930
|
+
}
|
|
931
|
+
}).start();
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
@ReactMethod
|
|
935
|
+
public void svStopMic(String controllerId, Promise promise) {
|
|
936
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
937
|
+
if (ctrl == null) {
|
|
938
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
try {
|
|
943
|
+
ctrl.stop();
|
|
944
|
+
WritableMap out = Arguments.createMap();
|
|
945
|
+
out.putBoolean("ok", true);
|
|
946
|
+
out.putString("controllerId", controllerId);
|
|
947
|
+
promise.resolve(out);
|
|
948
|
+
} catch (Throwable t) {
|
|
949
|
+
promise.reject("SVMicStopError", String.valueOf(t.getMessage()), t);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
335
953
|
}
|
package/package.json
CHANGED
|
@@ -41,12 +41,6 @@ function normalizePathOrName(input) {
|
|
|
41
41
|
return String(input);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function assertIOS() {
|
|
45
|
-
if (Platform.OS !== 'ios') {
|
|
46
|
-
throw new Error('Speaker verification bridge is currently implemented for iOS only.');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
44
|
function assertMethod(name) {
|
|
51
45
|
if (!KeyWordRNBridge?.[name]) {
|
|
52
46
|
throw new Error(`KeyWordRNBridge.${name} is not available (native not linked / iOS only?)`);
|
|
@@ -75,7 +69,7 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
75
69
|
* { decisionThreshold, frameSize, tailSeconds, maxTailSeconds, cmn, expectedLayoutBDT, logLevel }
|
|
76
70
|
*/
|
|
77
71
|
async create(modelPathOrName, enrollmentJsonPathOrName, options = {}) {
|
|
78
|
-
|
|
72
|
+
|
|
79
73
|
assertMethod('createSpeakerVerifier');
|
|
80
74
|
|
|
81
75
|
const modelArg = normalizePathOrName(modelPathOrName);
|
|
@@ -103,7 +97,7 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
103
97
|
* - if true, clears internal streaming state before verification
|
|
104
98
|
*/
|
|
105
99
|
async verifyWavStreaming(wavPathOrName, resetState = true) {
|
|
106
|
-
|
|
100
|
+
|
|
107
101
|
assertMethod('verifySpeakerWavStreaming');
|
|
108
102
|
|
|
109
103
|
const wavArg = normalizePathOrName(wavPathOrName);
|
|
@@ -117,11 +111,6 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
117
111
|
}
|
|
118
112
|
|
|
119
113
|
async destroy() {
|
|
120
|
-
|
|
121
|
-
if (Platform.OS !== 'ios') {
|
|
122
|
-
// no-op for now
|
|
123
|
-
return { ok: true, engineId: this.engineId, noop: true };
|
|
124
|
-
}
|
|
125
114
|
assertMethod('destroySpeakerVerifier');
|
|
126
115
|
return await KeyWordRNBridge.destroySpeakerVerifier(this.engineId);
|
|
127
116
|
}
|
|
@@ -143,7 +132,7 @@ export class SpeakerVerificationMicController {
|
|
|
143
132
|
}
|
|
144
133
|
|
|
145
134
|
async create(configJson) {
|
|
146
|
-
|
|
135
|
+
|
|
147
136
|
assertMethod('createSpeakerVerificationMicController');
|
|
148
137
|
const cfg =
|
|
149
138
|
typeof configJson === 'string'
|
|
@@ -159,7 +148,7 @@ export class SpeakerVerificationMicController {
|
|
|
159
148
|
}
|
|
160
149
|
|
|
161
150
|
async beginOnboarding(enrollmentId, targetEmbeddingCount, reset = true) {
|
|
162
|
-
|
|
151
|
+
|
|
163
152
|
assertMethod('svBeginOnboarding');
|
|
164
153
|
dbg('svBeginOnboarding args:', { controllerId: this.controllerId, enrollmentId, targetEmbeddingCount, reset: !!reset });
|
|
165
154
|
|
|
@@ -172,7 +161,7 @@ export class SpeakerVerificationMicController {
|
|
|
172
161
|
}
|
|
173
162
|
|
|
174
163
|
async getNextEmbeddingFromMic() {
|
|
175
|
-
|
|
164
|
+
|
|
176
165
|
assertMethod('svGetNextEmbeddingFromMic');
|
|
177
166
|
dbg('svGetNextEmbeddingFromMic args:', { controllerId: this.controllerId });
|
|
178
167
|
|
|
@@ -180,7 +169,7 @@ export class SpeakerVerificationMicController {
|
|
|
180
169
|
}
|
|
181
170
|
|
|
182
171
|
async finalizeOnboardingNow() {
|
|
183
|
-
|
|
172
|
+
|
|
184
173
|
assertMethod('svFinalizeOnboardingNow');
|
|
185
174
|
dbg('svFinalizeOnboardingNow args:', { controllerId: this.controllerId });
|
|
186
175
|
|
|
@@ -188,7 +177,7 @@ export class SpeakerVerificationMicController {
|
|
|
188
177
|
}
|
|
189
178
|
|
|
190
179
|
async setEnrollmentJson(enrollmentJson) {
|
|
191
|
-
|
|
180
|
+
|
|
192
181
|
assertMethod('svSetEnrollmentJson');
|
|
193
182
|
dbg('svSetEnrollmentJson args:', { controllerId: this.controllerId, len: String(enrollmentJson ?? '').length });
|
|
194
183
|
|
|
@@ -205,7 +194,7 @@ export class SpeakerVerificationMicController {
|
|
|
205
194
|
}
|
|
206
195
|
|
|
207
196
|
async startVerifyFromMic(resetState = true) {
|
|
208
|
-
|
|
197
|
+
|
|
209
198
|
assertMethod('svStartVerifyFromMic');
|
|
210
199
|
dbg('svStartVerifyFromMic args:', { controllerId: this.controllerId, resetState: !!resetState });
|
|
211
200
|
|
|
@@ -216,17 +205,11 @@ export class SpeakerVerificationMicController {
|
|
|
216
205
|
}
|
|
217
206
|
|
|
218
207
|
async stop() {
|
|
219
|
-
if (Platform.OS !== 'ios') {
|
|
220
|
-
return { ok: true, controllerId: this.controllerId, noop: true };
|
|
221
|
-
}
|
|
222
208
|
assertMethod('svStopMic');
|
|
223
209
|
return await KeyWordRNBridge.svStopMic(this.controllerId);
|
|
224
210
|
}
|
|
225
211
|
|
|
226
212
|
async destroy() {
|
|
227
|
-
if (Platform.OS !== 'ios') {
|
|
228
|
-
return { ok: true, controllerId: this.controllerId, noop: true };
|
|
229
|
-
}
|
|
230
213
|
assertMethod('destroySpeakerVerificationMicController');
|
|
231
214
|
return await KeyWordRNBridge.destroySpeakerVerificationMicController(this.controllerId);
|
|
232
215
|
}
|
|
@@ -237,7 +220,6 @@ export const createSpeakerVerificationMicController = async (controllerId) => {
|
|
|
237
220
|
};
|
|
238
221
|
|
|
239
222
|
async function verifyFromMicWithEnrollment(enrollmentJson, setUiMessage) {
|
|
240
|
-
if (Platform.OS !== 'ios') return;
|
|
241
223
|
|
|
242
224
|
const micConfig = {
|
|
243
225
|
modelPath: 'speaker_model.onnx',
|