react-native-wakeword 1.1.56 → 1.1.58
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/KeyWordRNBridge.podspec +1 -1
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
- 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 +764 -85
- package/android/src/main/java/com/davoice/speakeridrn/SpeakerIdRNBridge.java_not_used_yet +588 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +56 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Info.plist +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/KeyWordDetection +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.abi.json +8970 -2462
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.private.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +112 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Info.plist +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/KeyWordDetection +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.abi.json +8970 -2462
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json +8970 -2462
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeResources +34 -34
- package/ios/KeyWordRNBridge/KeyWordRNBridge.m +704 -3
- package/package.json +1 -1
- package/wakewords/SpeakerVerificationRNBridge.d.ts +49 -0
- package/wakewords/SpeakerVerificationRNBridge.js +294 -0
- package/wakewords/index.d.ts +18 -10
- package/wakewords/index.js +28 -0
- package/android/src/main/assets/coca_cola_model_28_05052025.dm +0 -0
- package/wakewords/index.d.ts.chat_idiot +0 -2
|
@@ -1,18 +1,55 @@
|
|
|
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;
|
|
7
17
|
import android.util.Log;
|
|
8
18
|
import java.util.HashMap;
|
|
9
19
|
import java.util.Map;
|
|
20
|
+
import java.util.concurrent.ExecutorService;
|
|
21
|
+
import java.util.concurrent.Executors;
|
|
22
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
23
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
24
|
+
|
|
10
25
|
|
|
11
26
|
public class KeyWordRNBridge extends ReactContextBaseJavaModule {
|
|
12
27
|
|
|
13
28
|
private final String TAG = "KeyWordsDetection";
|
|
14
29
|
private static final String REACT_CLASS = "KeyWordRNBridge";
|
|
15
30
|
private static ReactApplicationContext reactContext;
|
|
31
|
+
// ===============================
|
|
32
|
+
// Speaker Verification holders
|
|
33
|
+
// ===============================
|
|
34
|
+
private static final String SV_TAG = "SV.RNBridge";
|
|
35
|
+
|
|
36
|
+
private static final class SVEngineHolder {
|
|
37
|
+
String engineId;
|
|
38
|
+
SpeakerVerification.SpeakerVerificationConfig cfg;
|
|
39
|
+
SpeakerVerification.SpeakerVerificationEngine engine;
|
|
40
|
+
SpeakerVerification.SpeakerEnrollment enrollment;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private final Map<String, SVEngineHolder> svEngines = new ConcurrentHashMap<>();
|
|
44
|
+
|
|
45
|
+
private final Map<String, SpeakerVerification.SpeakerVerificationMicController> svMicControllers = new ConcurrentHashMap<>();
|
|
46
|
+
private final Map<String, Boolean> svAutoOnboarding = new ConcurrentHashMap<>();
|
|
47
|
+
private final Map<String, Integer> svAutoTarget = new ConcurrentHashMap<>();
|
|
48
|
+
private final Map<String, Integer> svAutoCollected = new ConcurrentHashMap<>();
|
|
49
|
+
private final Map<String, String> svAutoEnrollmentId = new ConcurrentHashMap<>();
|
|
50
|
+
// IMPORTANT: serialize ALL SV ops to avoid races with mic frames / onboarding state.
|
|
51
|
+
private final ExecutorService svExec = Executors.newSingleThreadExecutor();
|
|
52
|
+
private final AtomicInteger svJobN = new AtomicInteger(0);
|
|
16
53
|
|
|
17
54
|
// VAD API:
|
|
18
55
|
private final Map<String, Float> vadThresholdByInstance = new HashMap<>();
|
|
@@ -223,113 +260,755 @@ public class KeyWordRNBridge extends ReactContextBaseJavaModule {
|
|
|
223
260
|
.emit(eventName, params);
|
|
224
261
|
}
|
|
225
262
|
|
|
226
|
-
|
|
263
|
+
// VAD API:
|
|
227
264
|
|
|
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
|
-
|
|
265
|
+
// ===== Add: VAD parity methods (anywhere among other @ReactMethod methods) =====
|
|
266
|
+
@ReactMethod
|
|
267
|
+
public void getVoiceProps(String instanceId, Promise promise) {
|
|
268
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
269
|
+
if (instance == null) {
|
|
270
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
@SuppressWarnings("unchecked")
|
|
275
|
+
Map<String, Object> props = instance.getVoiceProps();
|
|
276
|
+
WritableMap out = Arguments.createMap();
|
|
240
277
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
278
|
+
Object err = props.get("error");
|
|
279
|
+
Object prob = props.get("voiceProbability");
|
|
280
|
+
Object last = props.get("lastTimeHumanVoiceHeard");
|
|
244
281
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
282
|
+
out.putString("error", err == null ? "" : String.valueOf(err));
|
|
283
|
+
out.putDouble("voiceProbability", prob instanceof Number ? ((Number) prob).doubleValue() : 0.0);
|
|
284
|
+
out.putDouble("lastTimeHumanVoiceHeard", last instanceof Number ? ((Number) last).doubleValue() : 0.0);
|
|
248
285
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
286
|
+
promise.resolve(out);
|
|
287
|
+
} catch (Exception e) {
|
|
288
|
+
promise.reject("GetVoicePropsError", e.getMessage());
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@ReactMethod
|
|
293
|
+
public void startVADDetection(String instanceId, Promise promise) {
|
|
294
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
295
|
+
if (instance == null) {
|
|
296
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
// after: API-21 safe
|
|
301
|
+
Float _thr = vadThresholdByInstance.get(instanceId);
|
|
302
|
+
float thr = (_thr != null) ? _thr : DEFAULT_VAD_THRESHOLD;
|
|
303
|
+
Integer _win = vadMsWindowByInstance.get(instanceId);
|
|
304
|
+
int win = (_win != null) ? _win : DEFAULT_VAD_MSWINDOW;
|
|
305
|
+
instance.setVADParams(thr, win);
|
|
306
|
+
boolean ok = instance.startVADListening();
|
|
307
|
+
promise.resolve(ok);
|
|
308
|
+
} catch (Exception e) {
|
|
309
|
+
promise.reject("StartVADError", e.getMessage());
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@ReactMethod
|
|
314
|
+
public void stopVADDetection(String instanceId, Promise promise) {
|
|
315
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
316
|
+
if (instance == null) {
|
|
317
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
instance.stopVADListening();
|
|
322
|
+
promise.resolve("Stopped VAD for instance: " + instanceId);
|
|
323
|
+
} catch (Exception e) {
|
|
324
|
+
promise.reject("StopVADError", e.getMessage());
|
|
325
|
+
}
|
|
252
326
|
}
|
|
253
|
-
}
|
|
254
327
|
|
|
255
|
-
@ReactMethod
|
|
256
|
-
public void
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
328
|
+
@ReactMethod
|
|
329
|
+
public void setVADParams(String instanceId, double threshold, int msWindow, Promise promise) {
|
|
330
|
+
KeyWordsDetection instance = instances.get(instanceId);
|
|
331
|
+
if (instance == null) {
|
|
332
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
float thr = (float) threshold;
|
|
337
|
+
vadThresholdByInstance.put(instanceId, thr);
|
|
338
|
+
vadMsWindowByInstance.put(instanceId, msWindow);
|
|
339
|
+
instance.setVADParams(thr, msWindow);
|
|
340
|
+
promise.resolve(null);
|
|
341
|
+
} catch (Exception e) {
|
|
342
|
+
promise.reject("SetVADParamsError", e.getMessage());
|
|
343
|
+
}
|
|
261
344
|
}
|
|
262
|
-
|
|
263
|
-
|
|
345
|
+
|
|
346
|
+
@ReactMethod
|
|
347
|
+
public void getVADParams(String instanceId, Promise promise) {
|
|
348
|
+
if (!instances.containsKey(instanceId)) {
|
|
349
|
+
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
WritableMap out = Arguments.createMap();
|
|
264
353
|
Float _thr = vadThresholdByInstance.get(instanceId);
|
|
265
354
|
float thr = (_thr != null) ? _thr : DEFAULT_VAD_THRESHOLD;
|
|
266
355
|
Integer _win = vadMsWindowByInstance.get(instanceId);
|
|
267
356
|
int win = (_win != null) ? _win : DEFAULT_VAD_MSWINDOW;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
promise.resolve(
|
|
271
|
-
}
|
|
272
|
-
|
|
357
|
+
out.putDouble("threshold", (double) thr);
|
|
358
|
+
out.putInt("msWindow", win);
|
|
359
|
+
promise.resolve(out);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@ReactMethod
|
|
363
|
+
public void addListener(String eventName) {
|
|
364
|
+
// Set up any upstream listeners or background tasks as necessary
|
|
273
365
|
}
|
|
274
|
-
}
|
|
275
366
|
|
|
276
|
-
@ReactMethod
|
|
277
|
-
public void
|
|
278
|
-
|
|
279
|
-
if (instance == null) {
|
|
280
|
-
promise.reject("InstanceNotFound", "No instance found with ID: " + instanceId);
|
|
281
|
-
return;
|
|
367
|
+
@ReactMethod
|
|
368
|
+
public void removeListeners(Integer count) {
|
|
369
|
+
// Remove upstream listeners, stop unnecessary background tasks
|
|
282
370
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
371
|
+
|
|
372
|
+
// Implement other methods as needed, ensuring to use instanceId
|
|
373
|
+
private String stripFileSchemeAndroid(String s) {
|
|
374
|
+
if (s == null) return null;
|
|
375
|
+
if (s.startsWith("file://")) return s.replace("file://", "");
|
|
376
|
+
return s;
|
|
288
377
|
}
|
|
289
|
-
}
|
|
290
378
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
379
|
+
// ********** SPEAKER IDENTIFICATION APIs **********
|
|
380
|
+
// Resolves:
|
|
381
|
+
// - absolute filesystem path -> returns as-is
|
|
382
|
+
// - "asset:/name.onnx" or "assets:/name.onnx" -> copies from APK assets to cache, returns real path
|
|
383
|
+
// - "name.onnx" (bundle asset name) -> copies from assets to cache
|
|
384
|
+
private String resolveToRealFilePath(String input) throws IOException {
|
|
385
|
+
input = stripFileSchemeAndroid(input);
|
|
386
|
+
if (input == null || input.isEmpty()) return input;
|
|
387
|
+
|
|
388
|
+
// Absolute existing path
|
|
389
|
+
if (input.startsWith("/") && new File(input).exists()) {
|
|
390
|
+
return input;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// RN Android require(...) often yields "asset:/..."
|
|
394
|
+
String assetName = input;
|
|
395
|
+
if (assetName.startsWith("asset:/")) assetName = assetName.substring("asset:/".length());
|
|
396
|
+
if (assetName.startsWith("assets:/")) assetName = assetName.substring("assets:/".length());
|
|
397
|
+
|
|
398
|
+
// If still contains directories like "assets/.../name.onnx", keep last component
|
|
399
|
+
// (your iOS resolver scans bundle; Android assets are flat-ish but can be nested)
|
|
400
|
+
// We'll try direct open first; if fails, try lastPathComponent.
|
|
401
|
+
String try1 = assetName;
|
|
402
|
+
String try2 = new File(assetName).getName();
|
|
403
|
+
|
|
404
|
+
File out1 = copyAssetToCacheIfExists(try1);
|
|
405
|
+
if (out1 != null) return out1.getAbsolutePath();
|
|
406
|
+
|
|
407
|
+
File out2 = copyAssetToCacheIfExists(try2);
|
|
408
|
+
if (out2 != null) return out2.getAbsolutePath();
|
|
409
|
+
|
|
410
|
+
// Not found
|
|
411
|
+
throw new FileNotFoundException("Cannot resolve asset/path: " + input + " (tried " + try1 + " and " + try2 + ")");
|
|
306
412
|
}
|
|
307
|
-
}
|
|
308
413
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
414
|
+
private File copyAssetToCacheIfExists(String assetName) {
|
|
415
|
+
if (assetName == null || assetName.isEmpty()) return null;
|
|
416
|
+
try (InputStream is = reactContext.getAssets().open(assetName)) {
|
|
417
|
+
File out = new File(reactContext.getCacheDir(), assetName);
|
|
418
|
+
// ensure parent
|
|
419
|
+
File parent = out.getParentFile();
|
|
420
|
+
if (parent != null) parent.mkdirs();
|
|
421
|
+
|
|
422
|
+
try (OutputStream os = new FileOutputStream(out)) {
|
|
423
|
+
byte[] buf = new byte[64 * 1024];
|
|
424
|
+
int n;
|
|
425
|
+
while ((n = is.read(buf)) > 0) os.write(buf, 0, n);
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
} catch (Throwable ignore) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private SpeakerVerification.SpeakerVerificationConfig parseSVMicConfigJson(String configJson) throws JSONException, IOException {
|
|
434
|
+
JSONObject root = new JSONObject(configJson == null ? "{}" : configJson);
|
|
435
|
+
|
|
436
|
+
SpeakerVerification.SpeakerVerificationConfig cfg = new SpeakerVerification.SpeakerVerificationConfig();
|
|
437
|
+
|
|
438
|
+
// Top-level fields (if you use them)
|
|
439
|
+
String modelPath = root.optString("modelPath", "");
|
|
440
|
+
int sampleRate = root.optInt("sampleRate", cfg.sampleRate);
|
|
441
|
+
int frameSize = root.optInt("frameSize", cfg.frameSize);
|
|
442
|
+
|
|
443
|
+
// iOS-style puts most stuff in "options"
|
|
444
|
+
JSONObject opts = root.optJSONObject("options");
|
|
445
|
+
if (opts != null) {
|
|
446
|
+
cfg.decisionThreshold = (float) opts.optDouble("decisionThreshold", cfg.decisionThreshold);
|
|
447
|
+
cfg.tailSeconds = (float) opts.optDouble("tailSeconds", cfg.tailSeconds);
|
|
448
|
+
cfg.maxTailSeconds = (float) opts.optDouble("maxTailSeconds", cfg.maxTailSeconds);
|
|
449
|
+
cfg.cmn = opts.optBoolean("cmn", cfg.cmn);
|
|
450
|
+
cfg.expectedLayoutBDT = opts.optBoolean("expectedLayoutBDT", cfg.expectedLayoutBDT);
|
|
451
|
+
|
|
452
|
+
// Prefer opts.frameSize if present
|
|
453
|
+
frameSize = opts.has("frameSize") ? opts.optInt("frameSize", frameSize) : frameSize;
|
|
454
|
+
sampleRate = opts.has("sampleRate") ? opts.optInt("sampleRate", sampleRate) : sampleRate;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
cfg.sampleRate = sampleRate > 0 ? sampleRate : 16000;
|
|
458
|
+
cfg.frameSize = frameSize > 0 ? frameSize : 1280;
|
|
459
|
+
|
|
460
|
+
// Resolve modelPath (MUST be real path for ORT)
|
|
461
|
+
if (modelPath != null && !modelPath.isEmpty()) {
|
|
462
|
+
cfg.modelPath = resolveToRealFilePath(modelPath);
|
|
463
|
+
} else {
|
|
464
|
+
// You can choose to throw here, but keeping consistent with iOS:
|
|
465
|
+
// iOS requires it; so Android should too.
|
|
466
|
+
throw new JSONException("Missing modelPath in configJson");
|
|
467
|
+
}
|
|
468
|
+
return cfg;
|
|
469
|
+
}
|
|
324
470
|
|
|
325
471
|
@ReactMethod
|
|
326
|
-
public void
|
|
327
|
-
|
|
472
|
+
public void createSpeakerVerifier(String engineId,
|
|
473
|
+
String modelPathOrName,
|
|
474
|
+
String enrollmentJsonPathOrName,
|
|
475
|
+
ReadableMap options,
|
|
476
|
+
Promise promise) {
|
|
477
|
+
if (svEngines.containsKey(engineId)) {
|
|
478
|
+
promise.reject("SVEngineExists", "Speaker verifier already exists with ID: " + engineId);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
new Thread(() -> {
|
|
483
|
+
try {
|
|
484
|
+
String modelPath = resolveToRealFilePath(modelPathOrName);
|
|
485
|
+
String jsonPath = resolveToRealFilePath(enrollmentJsonPathOrName);
|
|
486
|
+
|
|
487
|
+
// Build cfg from options (mirror iOS options)
|
|
488
|
+
SpeakerVerification.SpeakerVerificationConfig cfg = new SpeakerVerification.SpeakerVerificationConfig();
|
|
489
|
+
cfg.modelPath = modelPath;
|
|
490
|
+
|
|
491
|
+
if (options != null) {
|
|
492
|
+
if (options.hasKey("decisionThreshold")) cfg.decisionThreshold = (float) options.getDouble("decisionThreshold");
|
|
493
|
+
if (options.hasKey("frameSize")) cfg.frameSize = options.getInt("frameSize");
|
|
494
|
+
if (options.hasKey("tailSeconds")) cfg.tailSeconds = (float) options.getDouble("tailSeconds");
|
|
495
|
+
if (options.hasKey("maxTailSeconds")) cfg.maxTailSeconds = (float) options.getDouble("maxTailSeconds");
|
|
496
|
+
if (options.hasKey("cmn")) cfg.cmn = options.getBoolean("cmn");
|
|
497
|
+
if (options.hasKey("expectedLayoutBDT")) cfg.expectedLayoutBDT = options.getBoolean("expectedLayoutBDT");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Load enrollment json file -> string -> enrollment object
|
|
501
|
+
String enrollmentJson = readAllText(jsonPath);
|
|
502
|
+
SpeakerVerification.SpeakerEnrollment enrollment = SpeakerVerification.SpeakerEnrollment.fromJson(enrollmentJson);
|
|
503
|
+
|
|
504
|
+
SpeakerVerification.SpeakerVerificationEngine engine = new SpeakerVerification.SpeakerVerificationEngine(cfg);
|
|
505
|
+
engine.setEnrollment(enrollment);
|
|
506
|
+
|
|
507
|
+
SVEngineHolder h = new SVEngineHolder();
|
|
508
|
+
h.engineId = engineId;
|
|
509
|
+
h.cfg = cfg;
|
|
510
|
+
h.enrollment = enrollment;
|
|
511
|
+
h.engine = engine;
|
|
512
|
+
svEngines.put(engineId, h);
|
|
513
|
+
|
|
514
|
+
WritableMap out = Arguments.createMap();
|
|
515
|
+
out.putBoolean("ok", true);
|
|
516
|
+
out.putString("engineId", engineId);
|
|
517
|
+
out.putString("modelPath", modelPath);
|
|
518
|
+
out.putString("enrollmentJsonPath", jsonPath);
|
|
519
|
+
|
|
520
|
+
promise.resolve(out);
|
|
521
|
+
|
|
522
|
+
} catch (Throwable t) {
|
|
523
|
+
promise.reject("SVCreateError", String.valueOf(t.getMessage()), t);
|
|
524
|
+
}
|
|
525
|
+
}).start();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private String readAllText(String path) throws IOException {
|
|
529
|
+
try (InputStream is = new FileInputStream(path)) {
|
|
530
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
531
|
+
byte[] buf = new byte[64 * 1024];
|
|
532
|
+
int n;
|
|
533
|
+
while ((n = is.read(buf)) > 0) bos.write(buf, 0, n);
|
|
534
|
+
return bos.toString("UTF-8");
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@ReactMethod
|
|
539
|
+
public void verifySpeakerWavStreaming(String engineId,
|
|
540
|
+
String wavPathOrName,
|
|
541
|
+
boolean resetState,
|
|
542
|
+
Promise promise) {
|
|
543
|
+
SVEngineHolder h = svEngines.get(engineId);
|
|
544
|
+
if (h == null || h.engine == null) {
|
|
545
|
+
promise.reject("SVEngineNotFound", "No speaker verifier with ID: " + engineId);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
new Thread(() -> {
|
|
550
|
+
try {
|
|
551
|
+
String wavPath = resolveToRealFilePath(wavPathOrName);
|
|
552
|
+
|
|
553
|
+
if (resetState) {
|
|
554
|
+
h.engine.resetStreamingState();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
SpeakerVerification.SpeakerVerificationResult res =
|
|
558
|
+
runWavThroughEngineStreaming(h.engine, wavPath, h.cfg.frameSize);
|
|
559
|
+
|
|
560
|
+
WritableMap out = Arguments.createMap();
|
|
561
|
+
out.putBoolean("ok", true);
|
|
562
|
+
out.putString("engineId", engineId);
|
|
563
|
+
|
|
564
|
+
// Mirror iOS-ish keys (your JS can read bestScore/score etc)
|
|
565
|
+
out.putDouble("scoreBest", res.scoreBest);
|
|
566
|
+
out.putDouble("scoreMean", res.scoreMean);
|
|
567
|
+
out.putDouble("scoreWorst", res.scoreWorst);
|
|
568
|
+
out.putBoolean("isMatch", res.isMatch);
|
|
569
|
+
out.putInt("embeddingDim", res.embeddingDim);
|
|
570
|
+
out.putDouble("usedSeconds", res.usedSeconds);
|
|
571
|
+
|
|
572
|
+
promise.resolve(out);
|
|
573
|
+
|
|
574
|
+
} catch (Throwable t) {
|
|
575
|
+
promise.reject("SVVerifyError", String.valueOf(t.getMessage()), t);
|
|
576
|
+
}
|
|
577
|
+
}).start();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private SpeakerVerification.SpeakerVerificationResult runWavThroughEngineStreaming(
|
|
581
|
+
SpeakerVerification.SpeakerVerificationEngine engine,
|
|
582
|
+
String wavPath,
|
|
583
|
+
int frameSizeSamples
|
|
584
|
+
) throws Exception {
|
|
585
|
+
|
|
586
|
+
WavPcm16 wav = readWavPcm16Mono(wavPath);
|
|
587
|
+
if (wav.sampleRate != 16000) {
|
|
588
|
+
throw new IllegalArgumentException("WAV must be 16kHz. Got " + wav.sampleRate);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
final short[] pcm = wav.pcm16;
|
|
592
|
+
int i = 0;
|
|
593
|
+
|
|
594
|
+
while (i + frameSizeSamples <= pcm.length) {
|
|
595
|
+
float[] f = new float[frameSizeSamples];
|
|
596
|
+
for (int k = 0; k < frameSizeSamples; k++) {
|
|
597
|
+
f[k] = pcm[i + k] / 32768.0f;
|
|
598
|
+
}
|
|
599
|
+
i += frameSizeSamples;
|
|
600
|
+
|
|
601
|
+
SpeakerVerification.SpeakerVerificationOutput out = engine.processFrame(f);
|
|
602
|
+
if (out != null && out.type == SpeakerVerification.SpeakerVerificationOutput.Type.RESULT) {
|
|
603
|
+
return out.result;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
throw new IllegalStateException("NO_RESULT: WAV ended before engine produced RESULT");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Minimal WAV reader: PCM16 LE, mono.
|
|
611
|
+
private static final class WavPcm16 {
|
|
612
|
+
int sampleRate;
|
|
613
|
+
short[] pcm16;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private WavPcm16 readWavPcm16Mono(String path) throws IOException {
|
|
617
|
+
try (InputStream is = new BufferedInputStream(new FileInputStream(path))) {
|
|
618
|
+
byte[] header = new byte[12];
|
|
619
|
+
readFully(is, header);
|
|
620
|
+
|
|
621
|
+
// "RIFF" .... "WAVE"
|
|
622
|
+
if (!(header[0]=='R' && header[1]=='I' && header[2]=='F' && header[3]=='F')) {
|
|
623
|
+
throw new IOException("Not RIFF WAV");
|
|
624
|
+
}
|
|
625
|
+
if (!(header[8]=='W' && header[9]=='A' && header[10]=='V' && header[11]=='E')) {
|
|
626
|
+
throw new IOException("Not WAVE");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
int channels = -1, sampleRate = -1, bitsPerSample = -1;
|
|
630
|
+
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
|
631
|
+
|
|
632
|
+
while (true) {
|
|
633
|
+
byte[] chunkHdr = new byte[8];
|
|
634
|
+
int n = is.read(chunkHdr);
|
|
635
|
+
if (n < 0) break;
|
|
636
|
+
if (n != 8) throw new IOException("Bad WAV chunk header");
|
|
637
|
+
|
|
638
|
+
String id = new String(chunkHdr, 0, 4);
|
|
639
|
+
int size = ByteBuffer.wrap(chunkHdr, 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
|
640
|
+
|
|
641
|
+
if ("fmt ".equals(id)) {
|
|
642
|
+
byte[] fmt = new byte[size];
|
|
643
|
+
readFully(is, fmt);
|
|
644
|
+
|
|
645
|
+
int audioFormat = ByteBuffer.wrap(fmt, 0, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
|
646
|
+
channels = ByteBuffer.wrap(fmt, 2, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
|
647
|
+
sampleRate = ByteBuffer.wrap(fmt, 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
|
648
|
+
bitsPerSample = ByteBuffer.wrap(fmt, 14, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
|
649
|
+
|
|
650
|
+
if (audioFormat != 1) throw new IOException("WAV must be PCM (audioFormat=1). Got " + audioFormat);
|
|
651
|
+
} else if ("data".equals(id)) {
|
|
652
|
+
byte[] buf = new byte[64 * 1024];
|
|
653
|
+
int remaining = size;
|
|
654
|
+
while (remaining > 0) {
|
|
655
|
+
int r = is.read(buf, 0, Math.min(buf.length, remaining));
|
|
656
|
+
if (r < 0) throw new EOFException("WAV data truncated");
|
|
657
|
+
data.write(buf, 0, r);
|
|
658
|
+
remaining -= r;
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
// skip other chunks
|
|
662
|
+
long skipped = is.skip(size);
|
|
663
|
+
while (skipped < size) {
|
|
664
|
+
long s = is.skip(size - skipped);
|
|
665
|
+
if (s <= 0) break;
|
|
666
|
+
skipped += s;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// chunks are word-aligned
|
|
671
|
+
if ((size & 1) == 1) is.skip(1);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (channels != 1) throw new IOException("WAV must be mono. Got channels=" + channels);
|
|
675
|
+
if (bitsPerSample != 16) throw new IOException("WAV must be 16-bit. Got bits=" + bitsPerSample);
|
|
676
|
+
if (sampleRate <= 0) throw new IOException("Missing/invalid sampleRate");
|
|
677
|
+
|
|
678
|
+
byte[] raw = data.toByteArray();
|
|
679
|
+
short[] pcm16 = new short[raw.length / 2];
|
|
680
|
+
ByteBuffer bb = ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN);
|
|
681
|
+
for (int i = 0; i < pcm16.length; i++) pcm16[i] = bb.getShort();
|
|
682
|
+
|
|
683
|
+
WavPcm16 out = new WavPcm16();
|
|
684
|
+
out.sampleRate = sampleRate;
|
|
685
|
+
out.pcm16 = pcm16;
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private void readFully(InputStream is, byte[] buf) throws IOException {
|
|
691
|
+
int off = 0;
|
|
692
|
+
while (off < buf.length) {
|
|
693
|
+
int n = is.read(buf, off, buf.length - off);
|
|
694
|
+
if (n < 0) throw new EOFException("Unexpected EOF");
|
|
695
|
+
off += n;
|
|
696
|
+
}
|
|
328
697
|
}
|
|
329
698
|
|
|
330
699
|
@ReactMethod
|
|
331
|
-
public void
|
|
332
|
-
|
|
700
|
+
public void destroySpeakerVerifier(String engineId, Promise promise) {
|
|
701
|
+
SVEngineHolder h = svEngines.remove(engineId);
|
|
702
|
+
if (h == null) {
|
|
703
|
+
promise.reject("SVEngineNotFound", "No speaker verifier with ID: " + engineId);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
WritableMap out = Arguments.createMap();
|
|
707
|
+
out.putBoolean("ok", true);
|
|
708
|
+
out.putString("engineId", engineId);
|
|
709
|
+
promise.resolve(out);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
private WritableMap toWritableMap(Map<String, Object> m) {
|
|
713
|
+
WritableMap out = Arguments.createMap();
|
|
714
|
+
if (m == null) return out;
|
|
715
|
+
|
|
716
|
+
for (Map.Entry<String, Object> e : m.entrySet()) {
|
|
717
|
+
String k = e.getKey();
|
|
718
|
+
Object v = e.getValue();
|
|
719
|
+
if (v == null) continue;
|
|
720
|
+
|
|
721
|
+
if (v instanceof String) out.putString(k, (String) v);
|
|
722
|
+
else if (v instanceof Boolean) out.putBoolean(k, (Boolean) v);
|
|
723
|
+
else if (v instanceof Integer) out.putInt(k, (Integer) v);
|
|
724
|
+
else if (v instanceof Long) out.putDouble(k, ((Long) v).doubleValue());
|
|
725
|
+
else if (v instanceof Float) out.putDouble(k, ((Float) v).doubleValue());
|
|
726
|
+
else if (v instanceof Double) out.putDouble(k, (Double) v);
|
|
727
|
+
else out.putString(k, String.valueOf(v));
|
|
728
|
+
}
|
|
729
|
+
return out;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
private void sendEventUi(String eventName, WritableMap params) {
|
|
733
|
+
final ReactApplicationContext rc = reactContext;
|
|
734
|
+
if (rc == null) return;
|
|
735
|
+
if (!rc.hasActiveCatalystInstance()) return;
|
|
736
|
+
rc.runOnUiQueueThread(() -> {
|
|
737
|
+
try {
|
|
738
|
+
if (!rc.hasActiveCatalystInstance()) return;
|
|
739
|
+
rc.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
740
|
+
.emit(eventName, params);
|
|
741
|
+
} catch (Throwable t) {
|
|
742
|
+
// don't crash native if RN is tearing down
|
|
743
|
+
Log.w(SV_TAG, "sendEventUi failed event=" + eventName + " err=" + t);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
@ReactMethod
|
|
748
|
+
public void createSpeakerVerificationMicController(String controllerId,
|
|
749
|
+
String configJson,
|
|
750
|
+
Promise promise) {
|
|
751
|
+
if (svMicControllers.containsKey(controllerId)) {
|
|
752
|
+
promise.reject("SVMicExists", "Speaker mic controller already exists with ID: " + controllerId);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
new Thread(() -> {
|
|
757
|
+
try {
|
|
758
|
+
SpeakerVerification.SpeakerVerificationConfig cfg = parseSVMicConfigJson(configJson);
|
|
759
|
+
|
|
760
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl =
|
|
761
|
+
new SpeakerVerification.SpeakerVerificationMicController(reactContext, cfg);
|
|
762
|
+
|
|
763
|
+
// Delegate proxy -> RN events
|
|
764
|
+
ctrl.delegate = new SpeakerVerification.SpeakerVerificationNativeDelegate() {
|
|
765
|
+
@Override
|
|
766
|
+
public void svOnboardingProgress(Map<String, Object> info) {
|
|
767
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
768
|
+
m.put("controllerId", controllerId);
|
|
769
|
+
sendEventUi("onSpeakerVerificationOnboardingProgress", toWritableMap(m));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@Override
|
|
773
|
+
public void svOnboardingDone(Map<String, Object> info) {
|
|
774
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
775
|
+
m.put("controllerId", controllerId);
|
|
776
|
+
sendEventUi("onSpeakerVerificationOnboardingDone", toWritableMap(m));
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
@Override
|
|
780
|
+
public void svVerifyResult(Map<String, Object> info) {
|
|
781
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
782
|
+
m.put("controllerId", controllerId);
|
|
783
|
+
sendEventUi("onSpeakerVerificationVerifyResult", toWritableMap(m));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
@Override
|
|
787
|
+
public void svError(Map<String, Object> info) {
|
|
788
|
+
Map<String, Object> m = info == null ? new HashMap<>() : new HashMap<>(info);
|
|
789
|
+
m.put("controllerId", controllerId);
|
|
790
|
+
sendEventUi("onSpeakerVerificationError", toWritableMap(m));
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
svMicControllers.put(controllerId, ctrl);
|
|
795
|
+
|
|
796
|
+
WritableMap out = Arguments.createMap();
|
|
797
|
+
out.putBoolean("ok", true);
|
|
798
|
+
out.putString("controllerId", controllerId);
|
|
799
|
+
promise.resolve(out);
|
|
800
|
+
|
|
801
|
+
} catch (Throwable t) {
|
|
802
|
+
promise.reject("SVMicCreateError", String.valueOf(t.getMessage()), t);
|
|
803
|
+
}
|
|
804
|
+
}).start();
|
|
805
|
+
}
|
|
806
|
+
@ReactMethod
|
|
807
|
+
public void destroySpeakerVerificationMicController(String controllerId, Promise promise) {
|
|
808
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
809
|
+
if (ctrl == null) {
|
|
810
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
final int job = svJobN.incrementAndGet();
|
|
815
|
+
svExec.execute(() -> {
|
|
816
|
+
try {
|
|
817
|
+
try { ctrl.stop(); } catch (Throwable ignore) {}
|
|
818
|
+
svMicControllers.remove(controllerId); // remove only after stop
|
|
819
|
+
WritableMap out = Arguments.createMap();
|
|
820
|
+
out.putBoolean("ok", true);
|
|
821
|
+
out.putString("controllerId", controllerId);
|
|
822
|
+
out.putInt("job", job);
|
|
823
|
+
promise.resolve(out);
|
|
824
|
+
} catch (Throwable t) {
|
|
825
|
+
promise.reject("SVMicDestroyError", String.valueOf(t.getMessage()), t);
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
@ReactMethod
|
|
831
|
+
public void svBeginOnboarding(String controllerId,
|
|
832
|
+
String enrollmentId,
|
|
833
|
+
int targetEmbeddingCount,
|
|
834
|
+
boolean reset,
|
|
835
|
+
Promise promise) {
|
|
836
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
837
|
+
if (ctrl == null) {
|
|
838
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
final int job = svJobN.incrementAndGet();
|
|
843
|
+
svExec.execute(() -> {
|
|
844
|
+
try {
|
|
845
|
+
ctrl.beginOnboarding(enrollmentId, targetEmbeddingCount, reset);
|
|
846
|
+
WritableMap out = Arguments.createMap();
|
|
847
|
+
out.putBoolean("ok", true);
|
|
848
|
+
out.putString("controllerId", controllerId);
|
|
849
|
+
out.putString("enrollmentId", enrollmentId);
|
|
850
|
+
out.putInt("target", targetEmbeddingCount);
|
|
851
|
+
out.putInt("job", job);
|
|
852
|
+
promise.resolve(out);
|
|
853
|
+
} catch (Throwable t) {
|
|
854
|
+
promise.reject("SVMicBeginError", String.valueOf(t.getMessage()), t);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
@ReactMethod
|
|
860
|
+
public void svGetNextEmbeddingFromMic(String controllerId, Promise promise) {
|
|
861
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
862
|
+
if (ctrl == null) {
|
|
863
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
final int job = svJobN.incrementAndGet();
|
|
868
|
+
svExec.execute(() -> {
|
|
869
|
+
try {
|
|
870
|
+
ctrl.getNextEmbeddingFromMic();
|
|
871
|
+
WritableMap out = Arguments.createMap();
|
|
872
|
+
out.putBoolean("ok", true);
|
|
873
|
+
out.putString("controllerId", controllerId);
|
|
874
|
+
out.putInt("job", job);
|
|
875
|
+
promise.resolve(out);
|
|
876
|
+
} catch (Throwable t) {
|
|
877
|
+
promise.reject("SVMicGetNextError", String.valueOf(t.getMessage()), t);
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
@ReactMethod
|
|
884
|
+
public void svFinalizeOnboardingNow(String controllerId, Promise promise) {
|
|
885
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
886
|
+
if (ctrl == null) {
|
|
887
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
final int job = svJobN.incrementAndGet();
|
|
892
|
+
svExec.execute(() -> {
|
|
893
|
+
try {
|
|
894
|
+
ctrl.finalizeOnboardingNow();
|
|
895
|
+
WritableMap out = Arguments.createMap();
|
|
896
|
+
out.putBoolean("ok", true);
|
|
897
|
+
out.putString("controllerId", controllerId);
|
|
898
|
+
out.putInt("job", job);
|
|
899
|
+
promise.resolve(out);
|
|
900
|
+
} catch (Throwable t) {
|
|
901
|
+
promise.reject("SVMicFinalizeError", String.valueOf(t.getMessage()), t);
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
@ReactMethod
|
|
907
|
+
public void svSetEnrollmentJson(String controllerId, String enrollmentJson, Promise promise) {
|
|
908
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
909
|
+
if (ctrl == null) {
|
|
910
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
final int job = svJobN.incrementAndGet();
|
|
914
|
+
svExec.execute(() -> {
|
|
915
|
+
try {
|
|
916
|
+
ctrl.setEnrollmentJson(enrollmentJson);
|
|
917
|
+
WritableMap out = Arguments.createMap();
|
|
918
|
+
out.putBoolean("ok", true);
|
|
919
|
+
out.putString("controllerId", controllerId);
|
|
920
|
+
out.putInt("job", job);
|
|
921
|
+
promise.resolve(out);
|
|
922
|
+
} catch (Throwable t) {
|
|
923
|
+
promise.reject("SVMicSetEnrollError", String.valueOf(t.getMessage()), t);
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
@ReactMethod
|
|
930
|
+
public void svStartVerifyFromMic(String controllerId, boolean resetState, Promise promise) {
|
|
931
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
932
|
+
if (ctrl == null) {
|
|
933
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
final int job = svJobN.incrementAndGet();
|
|
938
|
+
svExec.execute(() -> {
|
|
939
|
+
try {
|
|
940
|
+
ctrl.startVerifyFromMic(resetState);
|
|
941
|
+
WritableMap out = Arguments.createMap();
|
|
942
|
+
out.putBoolean("ok", true);
|
|
943
|
+
out.putString("controllerId", controllerId);
|
|
944
|
+
out.putBoolean("resetState", resetState);
|
|
945
|
+
out.putInt("job", job);
|
|
946
|
+
promise.resolve(out);
|
|
947
|
+
} catch (Throwable t) {
|
|
948
|
+
promise.reject("SVMicStartVerifyError", String.valueOf(t.getMessage()), t);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
@ReactMethod
|
|
955
|
+
public void svStartEndlessVerifyFromMic(String controllerId, double hopSeconds, boolean stopOnMatch, boolean resetState, Promise promise) {
|
|
956
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
957
|
+
if (ctrl == null) {
|
|
958
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
final int job = svJobN.incrementAndGet();
|
|
963
|
+
svExec.execute(() -> {
|
|
964
|
+
try {
|
|
965
|
+
ctrl.startEndlessVerifyFromMic((float) hopSeconds, stopOnMatch, resetState);
|
|
966
|
+
WritableMap out = Arguments.createMap();
|
|
967
|
+
out.putBoolean("ok", true);
|
|
968
|
+
out.putString("controllerId", controllerId);
|
|
969
|
+
out.putDouble("hopSeconds", hopSeconds);
|
|
970
|
+
out.putBoolean("stopOnMatch", stopOnMatch);
|
|
971
|
+
out.putBoolean("resetState", resetState);
|
|
972
|
+
out.putInt("job", job);
|
|
973
|
+
promise.resolve(out);
|
|
974
|
+
} catch (Throwable t) {
|
|
975
|
+
promise.reject("SVMicStartEndlessVerifyError", String.valueOf(t.getMessage()), t);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
@ReactMethod
|
|
981
|
+
public void svStopMic(String controllerId, Promise promise) {
|
|
982
|
+
SpeakerVerification.SpeakerVerificationMicController ctrl = svMicControllers.get(controllerId);
|
|
983
|
+
if (ctrl == null) {
|
|
984
|
+
promise.reject("SVMicNotFound", "No speaker mic controller with ID: " + controllerId);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
final int job = svJobN.incrementAndGet();
|
|
989
|
+
svExec.execute(() -> {
|
|
990
|
+
try {
|
|
991
|
+
ctrl.stop();
|
|
992
|
+
WritableMap out = Arguments.createMap();
|
|
993
|
+
out.putBoolean("ok", true);
|
|
994
|
+
out.putString("controllerId", controllerId);
|
|
995
|
+
out.putInt("job", job);
|
|
996
|
+
promise.resolve(out);
|
|
997
|
+
} catch (Throwable t) {
|
|
998
|
+
promise.reject("SVMicStopError", String.valueOf(t.getMessage()), t);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
}
|
|
1003
|
+
@ReactMethod
|
|
1004
|
+
public void startVerifyContinuousFromMic(String controllerId, boolean resetState, double hopSeconds, Promise promise) {
|
|
1005
|
+
// parity: continuous verify == endless verify
|
|
1006
|
+
// choose stopOnMatch=false by default (continuous)
|
|
1007
|
+
svStartEndlessVerifyFromMic(controllerId, hopSeconds, false, resetState, promise);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
@ReactMethod
|
|
1011
|
+
public void stopVerifyContinuousFromMic(String controllerId, Promise promise) {
|
|
1012
|
+
svStopMic(controllerId, promise);
|
|
333
1013
|
}
|
|
334
|
-
// Implement other methods as needed, ensuring to use instanceId
|
|
335
1014
|
}
|