react-native-wakeword 1.1.76 → 1.1.78

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.
Files changed (13) hide show
  1. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar +0 -0
  2. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.md5 +1 -1
  3. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.sha1 +1 -1
  4. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/KeyWordDetection +0 -0
  5. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.abi.json +94 -80
  6. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/KeyWordDetection +0 -0
  7. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.abi.json +94 -80
  8. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json +94 -80
  9. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeDirectory +0 -0
  10. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeRequirements-1 +0 -0
  11. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeResources +12 -12
  12. package/package.json +1 -1
  13. package/android/src/main/java/com/davoice/speakeridrn/SpeakerIdRNBridge.java_not_used_yet +0 -588
@@ -18,7 +18,7 @@
18
18
  </data>
19
19
  <key>Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
20
20
  <data>
21
- pZY754IcQnDZyzoCAiOc4ZT0f4k=
21
+ MbMSFPHQDJFJSLnclC4QcNeQEwA=
22
22
  </data>
23
23
  <key>Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
24
24
  <data>
@@ -34,11 +34,11 @@
34
34
  </data>
35
35
  <key>Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftmodule</key>
36
36
  <data>
37
- bbKHjPdhwas1yvijAjAfbKYrq64=
37
+ jsl273vYZYsmnRSSrvg9hyxHa6s=
38
38
  </data>
39
39
  <key>Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
40
40
  <data>
41
- pZY754IcQnDZyzoCAiOc4ZT0f4k=
41
+ MbMSFPHQDJFJSLnclC4QcNeQEwA=
42
42
  </data>
43
43
  <key>Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
44
44
  <data>
@@ -54,7 +54,7 @@
54
54
  </data>
55
55
  <key>Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
56
56
  <data>
57
- tnSsRIAtNUF8aoV3LgDmXHjJAmY=
57
+ AVb1zCidoGqDwEfrrJpLHBcGHpo=
58
58
  </data>
59
59
  <key>Modules/module.modulemap</key>
60
60
  <data>
@@ -89,11 +89,11 @@
89
89
  <dict>
90
90
  <key>hash</key>
91
91
  <data>
92
- pZY754IcQnDZyzoCAiOc4ZT0f4k=
92
+ MbMSFPHQDJFJSLnclC4QcNeQEwA=
93
93
  </data>
94
94
  <key>hash2</key>
95
95
  <data>
96
- N4DD1Po32NcOkCcYbqmpwYlnasAHcjiZRHrxsMuXW4Q=
96
+ tyc0/NCJ+I4It7qUti/efTh59gQ9K74/DteYoTlOdf4=
97
97
  </data>
98
98
  </dict>
99
99
  <key>Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
@@ -133,22 +133,22 @@
133
133
  <dict>
134
134
  <key>hash</key>
135
135
  <data>
136
- bbKHjPdhwas1yvijAjAfbKYrq64=
136
+ jsl273vYZYsmnRSSrvg9hyxHa6s=
137
137
  </data>
138
138
  <key>hash2</key>
139
139
  <data>
140
- IrR7zRSF1TcDdaDN7uNJXxJ3qwBIlKy7oKhLabTVRK4=
140
+ hYFBFeQXtFRIqkscXqZpZg9I+owF2pr/2mNLPpRfC48=
141
141
  </data>
142
142
  </dict>
143
143
  <key>Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
144
144
  <dict>
145
145
  <key>hash</key>
146
146
  <data>
147
- pZY754IcQnDZyzoCAiOc4ZT0f4k=
147
+ MbMSFPHQDJFJSLnclC4QcNeQEwA=
148
148
  </data>
149
149
  <key>hash2</key>
150
150
  <data>
151
- N4DD1Po32NcOkCcYbqmpwYlnasAHcjiZRHrxsMuXW4Q=
151
+ tyc0/NCJ+I4It7qUti/efTh59gQ9K74/DteYoTlOdf4=
152
152
  </data>
153
153
  </dict>
154
154
  <key>Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
@@ -188,11 +188,11 @@
188
188
  <dict>
189
189
  <key>hash</key>
190
190
  <data>
191
- tnSsRIAtNUF8aoV3LgDmXHjJAmY=
191
+ AVb1zCidoGqDwEfrrJpLHBcGHpo=
192
192
  </data>
193
193
  <key>hash2</key>
194
194
  <data>
195
- SEem6N3CPxWbIkpgnLCJt2Wi2bhwK0G4VWrNJ0hMsIM=
195
+ g/qgwtXnuGM710V1ovWtn4PX1dmeEbEu+/nqCRGXW/A=
196
196
  </data>
197
197
  </dict>
198
198
  <key>Modules/module.modulemap</key>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-wakeword",
3
- "version": "1.1.76",
3
+ "version": "1.1.78",
4
4
  "description": "Voice/Wake-word detection library for React Native",
5
5
  "main": "wakewords/index.js",
6
6
  "types": "wakewords/index.d.ts",
@@ -1,588 +0,0 @@
1
- package com.davoice.speakeridrn;
2
-
3
- import com.facebook.react.bridge.*;
4
- import com.facebook.react.modules.core.DeviceEventManagerModule;
5
- import androidx.annotation.Nullable;
6
-
7
- import android.content.Context;
8
- import android.net.Uri;
9
- import android.os.Handler;
10
- import android.os.Looper;
11
-
12
- import ai.onnxruntime.OrtEnvironment;
13
- import ai.onnxruntime.OrtException;
14
-
15
- import com.davoice.speakerid.*;
16
-
17
- import java.io.File;
18
- import java.util.HashMap;
19
- import java.util.Map;
20
- import java.util.concurrent.*;
21
- import android.content.res.AssetManager;
22
- import java.io.InputStream;
23
- import java.io.BufferedInputStream;
24
- import java.io.ByteArrayOutputStream;
25
- import java.io.IOException;
26
- import java.util.Locale;
27
- import android.util.Log;
28
-
29
- /**
30
- * React Native bridge for the speaker-id Android library.
31
- * It wraps SpeakerIdApi: onboarding (mic/stream/wav) + verification (mic/stream/wav).
32
- */
33
- public class SpeakerIdRNBridge extends ReactContextBaseJavaModule {
34
-
35
- private static final String REACT_CLASS = "SpeakerIdRNBridge";
36
- private static ReactApplicationContext reactContext;
37
-
38
- // Keep multiple instances if you want (keyed by id)
39
- private final Map<String, SpeakerIdApi> instances = new HashMap<>();
40
-
41
- // Streaming onboarding per instance
42
- private final Map<String, SpeakerIdApi.OnboardingStream> onboardingStreams = new HashMap<>();
43
-
44
- // Run heavy calls off the UI thread
45
- private final ExecutorService exec = Executors.newCachedThreadPool();
46
- private final Handler main = new Handler(Looper.getMainLooper());
47
-
48
- public SpeakerIdRNBridge(ReactApplicationContext context) {
49
- super(context);
50
- reactContext = context;
51
- }
52
-
53
- @Override public String getName() { return REACT_CLASS; }
54
-
55
- // -------------------- Helpers --------------------
56
-
57
- private static WritableMap toJs(OnboardingResult r) {
58
- WritableMap m = Arguments.createMap();
59
- m.putInt("clusterSize", r.clusterSize);
60
- m.putInt("embeddingDim", r.embDim);
61
- return m;
62
- }
63
-
64
- private static WritableMap toJs(VerificationResult r) {
65
- WritableMap m = Arguments.createMap();
66
- m.putDouble("fullSec", r.fullSec);
67
- m.putDouble("voicedSec", r.voicedSec);
68
- m.putDouble("bestScore", r.bestScore);
69
- m.putString("bestStrategy", r.bestStrategy);
70
- m.putString("bestTargetLabel", r.bestTargetLabel);
71
-
72
- // Flatten perTargetStrategy -> { strategyKey: { label: score } }
73
- WritableMap strat = Arguments.createMap();
74
- if (r.perTargetStrategy != null) {
75
- for (Map.Entry<String, Map<String, Float>> e : r.perTargetStrategy.entrySet()) {
76
- WritableMap col = Arguments.createMap();
77
- for (Map.Entry<String, Float> colE : e.getValue().entrySet()) {
78
- col.putDouble(colE.getKey(), colE.getValue());
79
- }
80
- strat.putMap(e.getKey(), col);
81
- }
82
- }
83
- m.putMap("perTargetStrategy", strat);
84
- return m;
85
- }
86
-
87
- private static File fileFromPath(String path) {
88
- return new File(path);
89
- }
90
-
91
- // -------------------- Lifecycle / Creation --------------------
92
-
93
- /**
94
- * Create a SpeakerIdApi instance.
95
- * @param instanceId unique id you choose
96
- */
97
- @ReactMethod
98
- public void createInstance(String instanceId, Promise promise) {
99
- if (instances.containsKey(instanceId)) {
100
- promise.reject("InstanceExists", "Instance already exists: " + instanceId);
101
- return;
102
- }
103
- exec.submit(() -> {
104
- try {
105
- SpeakerIdApi api = SpeakerIdApi.create(reactContext); // <-- resolves speaker_id.dm + layer1.dm internally
106
- synchronized (instances) { instances.put(instanceId, api); }
107
- main.post(() -> promise.resolve(true));
108
- } catch (Exception e) {
109
- main.post(() -> promise.reject("CreateError", e.getMessage(), e));
110
- }
111
- });
112
- }
113
-
114
- @ReactMethod
115
- public void initVerificationUsingCurrentConfig(String instanceId, Promise promise) {
116
- SpeakerIdApi api = instances.get(instanceId);
117
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
118
- try {
119
- boolean ok = api.initVerificationUsingCurrentConfig();
120
- promise.resolve(ok);
121
- } catch (Throwable t) {
122
- promise.reject("E_INIT_CHECK", t);
123
- }
124
- }
125
-
126
- // ======= External-audio cluster API (RN) =======
127
-
128
- @ReactMethod
129
- public void initCluster(String instanceId, int numOfEmb, Promise promise) {
130
- SpeakerIdApi api = instances.get(instanceId);
131
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
132
- exec.submit(() -> {
133
- try {
134
- int cid = api.initCluster(numOfEmb);
135
- main.post(() -> promise.resolve(cid));
136
- } catch (Throwable t) {
137
- main.post(() -> promise.reject("InitClusterError", t.getMessage(), t));
138
- }
139
- });
140
- }
141
-
142
- @ReactMethod
143
- public void createAndPushEmbeddingsToCluster(String instanceId, int clusterId, ReadableArray pcmI16, int length, Promise promise) {
144
- SpeakerIdApi api = instances.get(instanceId);
145
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
146
- // Convert RN int[] -> short[]
147
- int n = Math.min(length, pcmI16.size());
148
- final short[] block = new short[n];
149
- for (int i = 0; i < n; i++) block[i] = (short) pcmI16.getInt(i);
150
-
151
- exec.submit(() -> {
152
- try {
153
- api.createAndPushEmbeddingsToCluster(clusterId, block, n);
154
- main.post(() -> promise.resolve(null));
155
- } catch (Throwable t) {
156
- main.post(() -> promise.reject("PushClusterError", t.getMessage(), t));
157
- }
158
- });
159
- }
160
-
161
- @ReactMethod
162
- public void createAndVerifyEmbeddingsFromCluster(String instanceId, int clusterId, ReadableArray pcmI16, int length, Promise promise) {
163
- SpeakerIdApi api = instances.get(instanceId);
164
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
165
- int n = Math.min(length, pcmI16.size());
166
- final short[] block = new short[n];
167
- for (int i = 0; i < n; i++) block[i] = (short) pcmI16.getInt(i);
168
-
169
- exec.submit(() -> {
170
- try {
171
- float score = api.createAndVerifyEmbeddingsFromCluster(clusterId, block, n);
172
- main.post(() -> promise.resolve(score));
173
- } catch (Throwable t) {
174
- main.post(() -> promise.reject("VerifyClusterError", t.getMessage(), t));
175
- }
176
- });
177
- }
178
-
179
- @ReactMethod
180
- public void destroyInstance(String instanceId, Promise promise) {
181
- SpeakerIdApi api;
182
- synchronized (instances) { api = instances.remove(instanceId); }
183
- if (api != null) {
184
- try { api.close(); } catch (Exception ignore) {}
185
- onboardingStreams.remove(instanceId);
186
- promise.resolve(true);
187
- } else {
188
- promise.reject("InstanceNotFound", "No such instance: " + instanceId);
189
- }
190
- }
191
-
192
- // -------------------- Verification init (targets) --------------------
193
-
194
- @ReactMethod
195
- public void initVerificationUsingDefaults(String instanceId, Promise promise) {
196
- SpeakerIdApi api = instances.get(instanceId);
197
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
198
- promise.resolve(api.initVerificationUsingDefaults(reactContext));
199
- }
200
-
201
- /**
202
- * Use explicit files for mean & cluster (paths to .npy files).
203
- */
204
- @ReactMethod
205
- public void initVerificationWithFiles(String instanceId,
206
- String meanEmbPath,
207
- String clusterPath,
208
- Promise promise) {
209
- SpeakerIdApi api = instances.get(instanceId);
210
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
211
- boolean ok = api.initVerificationWithFiles(fileFromPath(meanEmbPath),
212
- fileFromPath(clusterPath));
213
- promise.resolve(ok);
214
- }
215
-
216
- // -------------------- Onboarding: Microphone --------------------
217
-
218
- /**
219
- * Opens mic, captures first VAD-delimited utterance, enrolls.
220
- * JS must have RECORD_AUDIO permission granted beforehand.
221
- * @param maxMillis timeout in ms
222
- */
223
- @ReactMethod
224
- public void onboardFromMicrophone(String instanceId, double maxMillis, Promise promise) {
225
- SpeakerIdApi api = instances.get(instanceId);
226
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
227
-
228
- exec.submit(() -> {
229
- try {
230
- OnboardingResult r = api.onboardFromMicrophone((long)maxMillis);
231
- WritableMap out = toJs(r);
232
- main.post(() -> promise.resolve(out));
233
- } catch (SecurityException se) {
234
- main.post(() -> promise.reject("MicPermission", "RECORD_AUDIO permission missing", se));
235
- } catch (Exception e) {
236
- main.post(() -> promise.reject("OnboardMicError", e.getMessage(), e));
237
- }
238
- });
239
- }
240
-
241
- // -------------------- Onboarding: Stream --------------------
242
-
243
- @ReactMethod
244
- public void startOnboardingStream(String instanceId, Promise promise) {
245
- SpeakerIdApi api = instances.get(instanceId);
246
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
247
- SpeakerIdApi.OnboardingStream stream = api.startOnboardingStream();
248
- onboardingStreams.put(instanceId, stream);
249
- promise.resolve(true);
250
- }
251
-
252
- /**
253
- * Feed PCM16 block (short[]) as a base64 string? RN doesn’t pass short[] directly.
254
- * For simplicity here, pass an int array of 16-bit values (range -32768..32767).
255
- */
256
- @ReactMethod
257
- public void feedOnboardingStream(String instanceId, ReadableArray pcmI16, Promise promise) {
258
- SpeakerIdApi.OnboardingStream stream = onboardingStreams.get(instanceId);
259
- if (stream == null) { promise.reject("StreamNotStarted", instanceId); return; }
260
-
261
- short[] block = new short[pcmI16.size()];
262
- for (int i = 0; i < pcmI16.size(); i++) block[i] = (short) pcmI16.getInt(i);
263
-
264
- exec.submit(() -> {
265
- try {
266
- OnboardingResult r = stream.feed(block);
267
- if (r != null) {
268
- onboardingStreams.remove(instanceId);
269
- WritableMap out = toJs(r);
270
- main.post(() -> promise.resolve(out));
271
- } else {
272
- main.post(() -> promise.resolve(null)); // not done yet
273
- }
274
- } catch (Exception e) {
275
- main.post(() -> promise.reject("OnboardFeedError", e.getMessage(), e));
276
- }
277
- });
278
- }
279
-
280
- @ReactMethod
281
- public void finishOnboardingStream(String instanceId, Promise promise) {
282
- SpeakerIdApi.OnboardingStream stream = onboardingStreams.remove(instanceId);
283
- if (stream == null) { promise.reject("StreamNotStarted", instanceId); return; }
284
- exec.submit(() -> {
285
- try {
286
- OnboardingResult r = stream.finish();
287
- WritableMap out = (r == null) ? null : toJs(r);
288
- main.post(() -> promise.resolve(out));
289
- } catch (Exception e) {
290
- main.post(() -> promise.reject("OnboardFinishError", e.getMessage(), e));
291
- }
292
- });
293
- }
294
-
295
- // -------------------- Onboarding: WAV --------------------
296
-
297
- @ReactMethod
298
- public void onboardFromWav(String instanceId, String wavPath, Promise promise) {
299
- SpeakerIdApi api = instances.get(instanceId);
300
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
301
-
302
- exec.submit(() -> {
303
- try {
304
- OnboardingResult r = api.onboardFromWav(fileFromPath(wavPath));
305
- WritableMap out = toJs(r);
306
- main.post(() -> promise.resolve(out));
307
- } catch (Exception e) {
308
- main.post(() -> promise.reject("OnboardWavError", e.getMessage(), e));
309
- }
310
- });
311
- }
312
-
313
- // -------------------- Verification: Microphone --------------------
314
-
315
- @ReactMethod
316
- public void verifyFromMicrophone(String instanceId, double maxMillis, Promise promise) {
317
- SpeakerIdApi api = instances.get(instanceId);
318
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
319
- exec.submit(() -> {
320
- try {
321
- VerificationResult r = api.verifyFromMicrophone((long)maxMillis);
322
- WritableMap out = (r == null) ? null : toJs(r);
323
- main.post(() -> promise.resolve(out));
324
- } catch (SecurityException se) {
325
- main.post(() -> promise.reject("MicPermission", "RECORD_AUDIO permission missing", se));
326
- } catch (Exception e) {
327
- main.post(() -> promise.reject("VerifyMicError", e.getMessage(), e));
328
- }
329
- });
330
- }
331
-
332
- // -------------------- Verification: Stream --------------------
333
-
334
- @ReactMethod
335
- public void verifyStreamPush(String instanceId, ReadableArray pcmI16, Promise promise) {
336
- SpeakerIdApi api = instances.get(instanceId);
337
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
338
- short[] block = new short[pcmI16.size()];
339
- for (int i = 0; i < pcmI16.size(); i++) block[i] = (short) pcmI16.getInt(i);
340
- exec.submit(() -> {
341
- try {
342
- VerificationResult r = api.verifyStreamPush(block);
343
- WritableMap out = (r == null) ? null : toJs(r);
344
- main.post(() -> promise.resolve(out));
345
- } catch (Exception e) {
346
- main.post(() -> promise.reject("VerifyStreamPushError", e.getMessage(), e));
347
- }
348
- });
349
- }
350
-
351
- @ReactMethod
352
- public void verifyStreamFinish(String instanceId, Promise promise) {
353
- SpeakerIdApi api = instances.get(instanceId);
354
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
355
- exec.submit(() -> {
356
- try {
357
- VerificationResult r = api.verifyStreamFinish();
358
- WritableMap out = (r == null) ? null : toJs(r);
359
- main.post(() -> promise.resolve(out));
360
- } catch (Exception e) {
361
- main.post(() -> promise.reject("VerifyStreamFinishError", e.getMessage(), e));
362
- }
363
- });
364
- }
365
-
366
- // -------------------- Verification: WAV --------------------
367
-
368
- @ReactMethod
369
- public void verifyFromWav(String instanceId, String wavPath, Promise promise) {
370
- SpeakerIdApi api = instances.get(instanceId);
371
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
372
- exec.submit(() -> {
373
- try {
374
- VerificationResult r = api.verifyFromWav(fileFromPath(wavPath));
375
- WritableMap out = (r == null) ? null : toJs(r);
376
- main.post(() -> promise.resolve(out));
377
- } catch (Exception e) {
378
- main.post(() -> promise.reject("VerifyWavError", e.getMessage(), e));
379
- }
380
- });
381
- }
382
-
383
- // ======= WWD: create instance tuned for wake-word =======
384
-
385
- private static final boolean DEBUG_SID_ASSETS = true; // set false for production
386
-
387
- @ReactMethod
388
- public void createInstanceWWD(String instanceId, Promise promise) {
389
- if (instances.containsKey(instanceId)) {
390
- promise.reject("InstanceExists", "Instance already exists: " + instanceId);
391
- return;
392
- }
393
- Log.d("WWD DEBUG", "createInstanceWWD");
394
-
395
- exec.submit(() -> {
396
- try {
397
- Log.d("WWD DEBUG", "createInstanceWWD2");
398
- SpeakerIdApi api = SpeakerIdApi.createWWD(reactContext);
399
- Log.d("WWD DEBUG", "createInstanceWWD3");
400
- synchronized (instances) { instances.put(instanceId, api); }
401
- Log.d("WWD DEBUG", "createInstanceWWD4");
402
- main.post(() -> promise.resolve(true));
403
- // Then run debug async if enabled
404
- if (DEBUG_SID_ASSETS) {
405
- Log.d("WWD DEBUG", "Calling debugOnAssetsWWD");
406
- exec.submit(() -> debugOnAssetsWWD(instanceId));
407
- }
408
-
409
- } catch (Exception e) {
410
- main.post(() -> promise.reject("CreateWWDError", e.getMessage(), e));
411
- }
412
- });
413
- }
414
-
415
- // ======= WWD: onboard from mic (embNum x 1s) =======
416
- @ReactMethod
417
- public void onboardFromMicrophoneWWD(String instanceId, int embNum, double maxMillis, Promise promise) {
418
- SpeakerIdApi api = instances.get(instanceId);
419
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
420
- exec.submit(() -> {
421
- try {
422
- OnboardingResult r = api.onboardFromMicrophoneWWD(embNum, (long)maxMillis);
423
- WritableMap out = toJs(r);
424
- main.post(() -> promise.resolve(out));
425
- } catch (SecurityException se) {
426
- main.post(() -> promise.reject("MicPermission", "RECORD_AUDIO permission missing", se));
427
- } catch (Exception e) {
428
- main.post(() -> promise.reject("OnboardWWDError", e.getMessage(), e));
429
- }
430
- });
431
- }
432
-
433
- // ======= WWD: verify from mic (exactly 1s) =======
434
- @ReactMethod
435
- public void verifyFromMicrophoneWWD(String instanceId, double maxMillis, Promise promise) {
436
- SpeakerIdApi api = instances.get(instanceId);
437
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
438
- exec.submit(() -> {
439
- try {
440
- VerificationResult r = api.verifyFromMicrophoneWWD((long)maxMillis);
441
- WritableMap out = (r == null) ? null : toJs(r);
442
- main.post(() -> promise.resolve(out));
443
- } catch (SecurityException se) {
444
- main.post(() -> promise.reject("MicPermission", "RECORD_AUDIO permission missing", se));
445
- } catch (Exception e) {
446
- main.post(() -> promise.reject("VerifyWWDError", e.getMessage(), e));
447
- }
448
- });
449
- }
450
-
451
- // -------------------- Export helpers --------------------
452
-
453
- @ReactMethod
454
- public void exportDefaultClusterToDownloads(String instanceId, Promise promise) {
455
- SpeakerIdApi api = instances.get(instanceId);
456
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
457
- exec.submit(() -> {
458
- try {
459
- Uri uri = api.exportDefaultClusterToDownloads(reactContext);
460
- main.post(() -> promise.resolve(uri.toString()));
461
- } catch (Exception e) {
462
- main.post(() -> promise.reject("ExportClusterError", e.getMessage(), e));
463
- }
464
- });
465
- }
466
-
467
- @ReactMethod
468
- public void exportDefaultMeanToDownloads(String instanceId, Promise promise) {
469
- SpeakerIdApi api = instances.get(instanceId);
470
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
471
- exec.submit(() -> {
472
- try {
473
- Uri uri = api.exportDefaultMeanToDownloads(reactContext);
474
- main.post(() -> promise.resolve(uri.toString()));
475
- } catch (Exception e) {
476
- main.post(() -> promise.reject("ExportMeanError", e.getMessage(), e));
477
- }
478
- });
479
- }
480
-
481
- @ReactMethod
482
- public void exportDefaultMeanCountToDownloads(String instanceId, Promise promise) {
483
- SpeakerIdApi api = instances.get(instanceId);
484
- if (api == null) { promise.reject("InstanceNotFound", instanceId); return; }
485
- exec.submit(() -> {
486
- try {
487
- Uri uri = api.exportDefaultMeanCountToDownloads(reactContext);
488
- main.post(() -> promise.resolve(uri.toString()));
489
- } catch (Exception e) {
490
- main.post(() -> promise.reject("ExportMeanCountError", e.getMessage(), e));
491
- }
492
- });
493
- }
494
-
495
- // RN event helpers (if you later want to emit async events)
496
- private void sendEvent(String eventName, @Nullable WritableMap params) {
497
- reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
498
- .emit(eventName, params);
499
- }
500
-
501
- // No-op listener hooks
502
- @ReactMethod public void addListener(String eventName) {}
503
- @ReactMethod public void removeListeners(Integer count) {}
504
-
505
- @Override
506
- public void onCatalystInstanceDestroy() {
507
- for (SpeakerIdApi api : instances.values()) try { api.close(); } catch (Exception ignore) {}
508
- instances.clear();
509
- onboardingStreams.clear();
510
- exec.shutdownNow();
511
- super.onCatalystInstanceDestroy();
512
- }
513
-
514
- // ======= DEBUG harness (assets) =======
515
-
516
- private void emitDebug(String msg) {
517
- Log.d("WWD DEBUG", msg);
518
- WritableMap m = Arguments.createMap();
519
- m.putString("line", msg);
520
- sendEvent("SpeakerIdDebug", m);
521
- }
522
-
523
- private void debugOnAssetsWWD(String instanceId) {
524
- try {
525
- SpeakerIdApi api = instances.get(instanceId);
526
- if (api == null) { emitDebug("debugOnAssetsWWD: instance gone: " + instanceId); return; }
527
-
528
- // 1) Init a small FIFO cluster (K=3)
529
- final int K = 3;
530
- int clusterId = api.initCluster(K);
531
- emitDebug("WWD DEBUG: initCluster -> " + clusterId + " (K=" + K + ")");
532
-
533
- // 2) Onboarding from assets
534
- String[] onboard = new String[] {
535
- "onboard1.raw","onboard2.raw","onboard3.raw","onboard4.raw"
536
- };
537
- for (int i = 0; i < onboard.length; i++) {
538
- short[] pcm = readPcm16LeRawAsset(onboard[i]);
539
- api.createAndPushEmbeddingsToCluster(clusterId, pcm, pcm.length);
540
- String line = String.format(Locale.US, "WWD DEBUG: push #%d '%s' samples=%d", i+1, onboard[i], pcm.length);
541
- emitDebug(line);
542
- }
543
- emitDebug("WWD DEBUG: onboarding done; cluster+mean saved.");
544
-
545
- // 3) Verification from assets
546
- String[] tests = new String[] {
547
- "test1.raw","test2.raw","test3.raw","test4.raw","test5.raw","test6.raw","test7.raw"
548
- };
549
- for (String tf : tests) {
550
- short[] pcm = readPcm16LeRawAsset(tf);
551
- float score = api.createAndVerifyEmbeddingsFromCluster(clusterId, pcm, pcm.length);
552
- String line = String.format(Locale.US, "WWD DEBUG: verify '%s' samples=%d -> score=%.4f",
553
- tf, pcm.length, score);
554
- emitDebug(line);
555
- }
556
- emitDebug("WWD DEBUG: verification pass completed.");
557
- } catch (Throwable t) {
558
- emitDebug("WWD DEBUG ERROR: " + t.getMessage());
559
- }
560
- }
561
-
562
- /** Read RAW little-endian PCM16 asset → short[] (exactly your requested method) */
563
- private short[] readPcm16LeRawAsset(String assetName) throws IOException {
564
- AssetManager am = reactContext.getAssets();
565
- try (InputStream in = am.open(assetName);
566
- BufferedInputStream bis = new BufferedInputStream(in);
567
- ByteArrayOutputStream baos = new ByteArrayOutputStream(32 * 1024)) {
568
-
569
- byte[] buf = new byte[8192];
570
- int n;
571
- while ((n = bis.read(buf)) != -1) {
572
- baos.write(buf, 0, n);
573
- }
574
- byte[] all = baos.toByteArray();
575
- if ((all.length & 1) != 0) {
576
- throw new IOException("Asset RAW has odd byte length: " + assetName + " (" + all.length + " bytes)");
577
- }
578
- int samples = all.length / 2;
579
- short[] pcm = new short[samples];
580
- for (int i = 0, s = 0; i < all.length; i += 2, s++) {
581
- int lo = (all[i] & 0xFF);
582
- int hi = (all[i + 1] << 8);
583
- pcm[s] = (short) (hi | lo);
584
- }
585
- return pcm;
586
- }
587
- }
588
- }