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.
Files changed (39) hide show
  1. package/KeyWordRNBridge.podspec +1 -1
  2. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  4. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  5. package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
  6. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar +0 -0
  7. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.md5 +1 -1
  8. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.sha1 +1 -1
  9. package/android/src/main/java/com/davoice/keywordspotting/KeyWordRNBridge.java +764 -85
  10. package/android/src/main/java/com/davoice/speakeridrn/SpeakerIdRNBridge.java_not_used_yet +588 -0
  11. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +56 -0
  12. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Info.plist +0 -0
  13. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/KeyWordDetection +0 -0
  14. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.abi.json +8970 -2462
  15. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.private.swiftinterface +133 -0
  16. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  17. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftinterface +133 -0
  18. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +112 -0
  19. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Info.plist +0 -0
  20. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/KeyWordDetection +0 -0
  21. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.abi.json +8970 -2462
  22. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +133 -0
  23. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  24. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftinterface +133 -0
  25. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json +8970 -2462
  26. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +133 -0
  27. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  28. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +133 -0
  29. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeDirectory +0 -0
  30. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeRequirements-1 +0 -0
  31. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeResources +34 -34
  32. package/ios/KeyWordRNBridge/KeyWordRNBridge.m +704 -3
  33. package/package.json +1 -1
  34. package/wakewords/SpeakerVerificationRNBridge.d.ts +49 -0
  35. package/wakewords/SpeakerVerificationRNBridge.js +294 -0
  36. package/wakewords/index.d.ts +18 -10
  37. package/wakewords/index.js +28 -0
  38. package/android/src/main/assets/coca_cola_model_28_05052025.dm +0 -0
  39. package/wakewords/index.d.ts.chat_idiot +0 -2
@@ -5,6 +5,9 @@
5
5
  #import <React/RCTLog.h>
6
6
  #import <React/RCTEventEmitter.h>
7
7
  //#import "KeyWordsDetection.h" // Import your KeyWordsDetection library header
8
+ // Speaker verification bridge (Swift) is called dynamically via objc_msgSend
9
+ #import <objc/message.h>
10
+
8
11
 
9
12
  // Ensure the protocol is correctly imported or declared
10
13
  // Assuming the protocol is named 'KeywordDetectionRNDelegate'
@@ -88,9 +91,303 @@
88
91
 
89
92
  @end
90
93
 
94
+
95
+ // ============================================================
96
+ // MARK: - Speaker Verification: native holder (opaque Swift object)
97
+ // ============================================================
98
+
99
+ @interface SVVerifierHolder : NSObject
100
+ @property (nonatomic, strong) id engine; // opaque Swift object
101
+ @property (nonatomic, strong) NSString *engineId;
102
+ @end
103
+
104
+ @implementation SVVerifierHolder
105
+ @end
106
+
107
+ // ============================================================
108
+ // MARK: - Speaker Verification Mic Controller holder + delegate proxy
109
+ // ============================================================
110
+
111
+ @interface SVMicHolder : NSObject
112
+ @property (nonatomic, strong) id controller; // opaque Swift object (SpeakerVerificationMicController)
113
+ @property (nonatomic, strong) NSString *controllerId;
114
+ @property (nonatomic, strong) id delegateProxy; // ObjC proxy that receives Swift callbacks
115
+ @end
116
+ @implementation SVMicHolder @end
117
+
118
+ @interface SVMicDelegateProxy : NSObject
119
+ @property (nonatomic, weak) KeyWordRNBridge *bridge;
120
+ @property (nonatomic, strong) NSString *controllerId;
121
+ @end
122
+
123
+ @implementation SVMicDelegateProxy
124
+ - (void)svOnboardingProgress:(NSDictionary *)info {
125
+ NSMutableDictionary *m = info ? [info mutableCopy] : [NSMutableDictionary new];
126
+ m[@"controllerId"] = self.controllerId ?: @"";
127
+ [self.bridge sendEventWithName:@"onSpeakerVerificationOnboardingProgress" body:m];
128
+ }
129
+ - (void)svOnboardingDone:(NSDictionary *)info {
130
+ NSMutableDictionary *m = info ? [info mutableCopy] : [NSMutableDictionary new];
131
+ m[@"controllerId"] = self.controllerId ?: @"";
132
+ [self.bridge sendEventWithName:@"onSpeakerVerificationOnboardingDone" body:m];
133
+ }
134
+ - (void)svVerifyResult:(NSDictionary *)info {
135
+ NSMutableDictionary *m = info ? [info mutableCopy] : [NSMutableDictionary new];
136
+ m[@"controllerId"] = self.controllerId ?: @"";
137
+ [self.bridge sendEventWithName:@"onSpeakerVerificationVerifyResult" body:m];
138
+ }
139
+ - (void)svError:(NSDictionary *)info {
140
+ NSMutableDictionary *m = info ? [info mutableCopy] : [NSMutableDictionary new];
141
+ m[@"controllerId"] = self.controllerId ?: @"";
142
+ [self.bridge sendEventWithName:@"onSpeakerVerificationError" body:m];
143
+ }
144
+ @end
145
+
146
+
147
+ // Resolve a file path from:
148
+ // 1) absolute path (exists)
149
+ // 2) main bundle resource "name.ext"
150
+ // 3) bundle scan by lastPathComponent (RN sometimes hashes folders)
151
+ // 4) if found in bundle but not a stable path, copy to tmp and return tmp path
152
+ static NSString * _Nullable SVResolveFilePath(NSString *input) {
153
+ if (!input || input.length == 0) return nil;
154
+
155
+ // Absolute path?
156
+ if ([input hasPrefix:@"/"] && [[NSFileManager defaultManager] fileExistsAtPath:input]) {
157
+ return input;
158
+ }
159
+
160
+ NSString *fileName = [input lastPathComponent];
161
+ NSString *base = [fileName stringByDeletingPathExtension];
162
+ NSString *ext = [fileName pathExtension];
163
+
164
+ // Direct bundle lookup
165
+ NSString *p = [[NSBundle mainBundle] pathForResource:base ofType:ext.length ? ext : nil];
166
+ if (p && [[NSFileManager defaultManager] fileExistsAtPath:p]) {
167
+ return p;
168
+ }
169
+
170
+ // Scan bundle for lastPathComponent (covers RN "assets/..." or nested resources)
171
+ NSArray<NSString *> *candidatesExt = ext.length ? @[ext] : @[@"onnx", @"json", @"wav"];
172
+ for (NSString *e in candidatesExt) {
173
+ NSArray<NSString *> *paths = [[NSBundle mainBundle] pathsForResourcesOfType:e inDirectory:nil];
174
+ for (NSString *pp in paths) {
175
+ if ([[pp lastPathComponent] isEqualToString:fileName]) {
176
+ return pp;
177
+ }
178
+ }
179
+ }
180
+
181
+ return nil;
182
+ }
183
+
184
+ static NSString * _Nullable SVCopyToTempIfNeeded(NSString *path, NSString *preferredName) {
185
+ if (!path) return nil;
186
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
187
+ // Bundle paths are valid; ORT needs a file path, bundle path is fine.
188
+ // But keep copy logic for safety (some resources could be non-file URLs in edge cases).
189
+ return path;
190
+ }
191
+
192
+ // Fallback: try to copy from bundle URL
193
+ NSURL *url = [[NSBundle mainBundle] URLForResource:[preferredName stringByDeletingPathExtension]
194
+ withExtension:[preferredName pathExtension]];
195
+ if (!url) return nil;
196
+
197
+ NSString *tmp = [NSTemporaryDirectory() stringByAppendingPathComponent:preferredName];
198
+ [[NSFileManager defaultManager] removeItemAtPath:tmp error:nil];
199
+ NSError *err = nil;
200
+ BOOL ok = [[NSFileManager defaultManager] copyItemAtURL:url toURL:[NSURL fileURLWithPath:tmp] error:&err];
201
+ if (!ok || err) return nil;
202
+ return tmp;
203
+ }
204
+
205
+ static NSDictionary *SVErrDict(NSString *code, NSString *msg) {
206
+ return @{ @"code": code ?: @"Error", @"message": msg ?: @"Unknown error" };
207
+ }
208
+
209
+ // Dynamic Swift bridge:
210
+ // Expect a Swift class annotated: @objc(SpeakerVerificationRNFacade)
211
+ // with ObjC-visible selectors:
212
+ // + (id)createEngineWithModelPath:(NSString*)modelPath enrollmentJsonPath:(NSString*)jsonPath options:(NSDictionary*)options error:(NSError**)error;
213
+ // + (NSDictionary*)verifyWavWithEngine:(id)engine wavPath:(NSString*)wavPath reset:(BOOL)reset error:(NSError**)error;
214
+ static Class SVFacadeClass(void) {
215
+ return NSClassFromString(@"SpeakerVerificationRNFacade");
216
+ }
217
+
218
+ // ============================================================
219
+ // MARK: - Speaker Verification Mic Config JSON resolver (modelPath)
220
+ // ============================================================
221
+
222
+ // Resolve "modelPath" inside mic controller config JSON (bundle name -> absolute file path)
223
+ // so Swift/ORT always receives a real filesystem path.
224
+ static NSString * _Nullable SVResolveMicConfigJson(NSString *configJson, NSError **error) {
225
+ if (!configJson || configJson.length == 0) {
226
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: empty configJson");
227
+ return configJson;
228
+ }
229
+
230
+ NSData *data = [configJson dataUsingEncoding:NSUTF8StringEncoding];
231
+ if (!data) {
232
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: failed to make NSData from JSON string (passing through)");
233
+ return configJson;
234
+ }
235
+
236
+ NSError *jsonErr = nil;
237
+ id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
238
+ if (jsonErr || ![obj isKindOfClass:[NSDictionary class]]) {
239
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: JSON parse failed (passing through). err=%@ json=%@", jsonErr, configJson);
240
+ return configJson;
241
+ }
242
+
243
+ NSMutableDictionary *cfg = [(NSDictionary *)obj mutableCopy];
244
+ id mp = cfg[@"modelPath"];
245
+ if (![mp isKindOfClass:[NSString class]] || ((NSString *)mp).length == 0) {
246
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: missing/invalid modelPath (passing through). keys=%@", cfg.allKeys);
247
+ return configJson;
248
+ }
249
+
250
+ NSString *modelPathIn = (NSString *)mp;
251
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: modelPath(in)='%@'", modelPathIn);
252
+
253
+ // If already absolute and exists: keep
254
+ if ([modelPathIn hasPrefix:@"/"] && [[NSFileManager defaultManager] fileExistsAtPath:modelPathIn]) {
255
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: modelPath absolute & exists ✅");
256
+ return configJson;
257
+ }
258
+
259
+ // Resolve using the SAME logic as createSpeakerVerifier
260
+ NSString *resolved = SVResolveFilePath(modelPathIn);
261
+ if (!resolved) {
262
+ NSArray<NSString *> *onnx = [[NSBundle mainBundle] pathsForResourcesOfType:@"onnx" inDirectory:nil];
263
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: ❌ cannot resolve modelPath='%@' in bundle. onnxCount=%lu sample=%@",
264
+ modelPathIn, (unsigned long)onnx.count, onnx.count ? onnx.firstObject : @"(none)");
265
+ if (error) {
266
+ *error = [NSError errorWithDomain:@"SV" code:420 userInfo:@{
267
+ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Mic config model not found in app bundle: %@", modelPathIn]
268
+ }];
269
+ }
270
+ return nil;
271
+ }
272
+
273
+ NSString *stable = SVCopyToTempIfNeeded(resolved, [modelPathIn lastPathComponent]) ?: resolved;
274
+ cfg[@"modelPath"] = stable;
275
+
276
+ NSData *outData = [NSJSONSerialization dataWithJSONObject:cfg options:0 error:&jsonErr];
277
+ if (jsonErr || !outData) {
278
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: re-encode failed (passing original). err=%@", jsonErr);
279
+ return configJson;
280
+ }
281
+
282
+ NSString *outJson = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
283
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: modelPath(resolved)='%@'", stable);
284
+ NSLog(@"[SV][ObjC] SVResolveMicConfigJson: json(out)=%@", outJson);
285
+ return outJson;
286
+ }
287
+
288
+
289
+ // =========================
290
+ // MARK: - Mic controller dynamic bridge
291
+ // =========================
292
+
293
+ static id _Nullable SVCreateMicController(NSString *configJson, NSError **error) {
294
+ NSLog(@"[SV][ObjC] SVCreateMicController: in.jsonLen=%lu json=%@",
295
+ (unsigned long)(configJson ? configJson.length : 0),
296
+ configJson ?: @"(null)");
297
+
298
+ // Resolve modelPath inside JSON BEFORE calling Swift
299
+ NSError *resolveErr = nil;
300
+ NSString *fixedJson = SVResolveMicConfigJson(configJson, &resolveErr);
301
+ if (resolveErr || !fixedJson) {
302
+ if (error) *error = resolveErr;
303
+ NSLog(@"[SV][ObjC] SVCreateMicController: ❌ SVResolveMicConfigJson failed: %@", resolveErr.localizedDescription);
304
+ return nil;
305
+ }
306
+ if (![fixedJson isEqualToString:configJson]) {
307
+ NSLog(@"[SV][ObjC] SVCreateMicController: using RESOLVED jsonLen=%lu", (unsigned long)fixedJson.length);
308
+ } else {
309
+ NSLog(@"[SV][ObjC] SVCreateMicController: json unchanged (no modelPath fix needed)");
310
+ }
311
+
312
+
313
+ Class c = SVFacadeClass();
314
+ if (!c) {
315
+ if (error) *error = [NSError errorWithDomain:@"SV" code:10 userInfo:@{NSLocalizedDescriptionKey: @"Swift class SpeakerVerificationRNFacade not found"}];
316
+ return nil;
317
+ }
318
+ SEL sel = NSSelectorFromString(@"createMicControllerWithConfigJson:error:");
319
+ if (![c respondsToSelector:sel]) {
320
+ if (error) *error = [NSError errorWithDomain:@"SV" code:11 userInfo:@{NSLocalizedDescriptionKey: @"Missing selector createMicControllerWithConfigJson:error:"}];
321
+ return nil;
322
+ }
323
+ id (*msgSend)(id, SEL, NSString*, NSError**) = (void*)objc_msgSend;
324
+ id out = msgSend(c, sel, fixedJson, error);
325
+ if (*error) {
326
+ NSLog(@"[SV][ObjC] SVCreateMicController: ❌ Swift createMicController failed: %@", (*error).localizedDescription);
327
+ } else {
328
+ NSLog(@"[SV][ObjC] SVCreateMicController: ✅ created controller=%@", out);
329
+ }
330
+ return out;
331
+ }
332
+
333
+ static BOOL SVCallBool2(id obj, SEL sel, NSString *s1, NSInteger i1, BOOL b1, NSError **error) {
334
+ BOOL (*msgSend)(id, SEL, NSString*, NSInteger, BOOL, NSError**) = (void*)objc_msgSend;
335
+ return msgSend(obj, sel, s1, i1, b1, error);
336
+ }
337
+ static BOOL SVCallBool1(id obj, SEL sel, NSError **error) {
338
+ BOOL (*msgSend)(id, SEL, NSError**) = (void*)objc_msgSend;
339
+ return msgSend(obj, sel, error);
340
+ }
341
+ static BOOL SVCallBoolReset(id obj, SEL sel, BOOL b1, NSError **error) {
342
+ BOOL (*msgSend)(id, SEL, BOOL, NSError**) = (void*)objc_msgSend;
343
+ return msgSend(obj, sel, b1, error);
344
+ }
345
+ static BOOL SVCallBoolHopStop(id obj, SEL sel, NSNumber *hopSeconds, BOOL stopOnMatch, NSError **error) {
346
+ BOOL (*msgSend)(id, SEL, NSNumber*, BOOL, NSError**) = (void*)objc_msgSend;
347
+ return msgSend(obj, sel, hopSeconds, stopOnMatch, error);
348
+ }
349
+
350
+ static void SVSetDelegate(id obj, id delegateObj) {
351
+ void (*msgSend)(id, SEL, id) = (void*)objc_msgSend;
352
+ msgSend(obj, NSSelectorFromString(@"setDelegate:"), delegateObj);
353
+ }
354
+
355
+
356
+ static id _Nullable SVCreateEngine(NSString *modelPath, NSString *jsonPath, NSDictionary *options, NSError **error) {
357
+ Class c = SVFacadeClass();
358
+ if (!c) {
359
+ if (error) *error = [NSError errorWithDomain:@"SV" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Swift class SpeakerVerificationRNFacade not found (did you add it?)"}];
360
+ return nil;
361
+ }
362
+ SEL sel = NSSelectorFromString(@"createEngineWithModelPath:enrollmentJsonPath:options:error:");
363
+ if (![c respondsToSelector:sel]) {
364
+ if (error) *error = [NSError errorWithDomain:@"SV" code:2 userInfo:@{NSLocalizedDescriptionKey: @"Missing selector createEngineWithModelPath:enrollmentJsonPath:options:error:"}];
365
+ return nil;
366
+ }
367
+ id (*msgSend)(id, SEL, NSString*, NSString*, NSDictionary*, NSError**) = (void*)objc_msgSend;
368
+ return msgSend(c, sel, modelPath, jsonPath, options ?: @{}, error);
369
+ }
370
+
371
+ static NSDictionary * _Nullable SVVerifyWav(id engine, NSString *wavPath, BOOL reset, NSError **error) {
372
+ Class c = SVFacadeClass();
373
+ if (!c) {
374
+ if (error) *error = [NSError errorWithDomain:@"SV" code:3 userInfo:@{NSLocalizedDescriptionKey: @"Swift class SpeakerVerificationRNFacade not found"}];
375
+ return nil;
376
+ }
377
+ SEL sel = NSSelectorFromString(@"verifyWavWithEngine:wavPath:reset:error:");
378
+ if (![c respondsToSelector:sel]) {
379
+ if (error) *error = [NSError errorWithDomain:@"SV" code:4 userInfo:@{NSLocalizedDescriptionKey: @"Missing selector verifyWavWithEngine:wavPath:reset:error:"}];
380
+ return nil;
381
+ }
382
+ NSDictionary* (*msgSend)(id, SEL, id, NSString*, BOOL, NSError**) = (void*)objc_msgSend;
383
+ return msgSend(c, sel, engine, wavPath, reset, error);
384
+ }
385
+
91
386
  @interface KeyWordRNBridge () <RCTBridgeModule>
92
387
 
93
388
  @property (nonatomic, strong) NSMutableDictionary *instances;
389
+ @property (nonatomic, strong) NSMutableDictionary *speakerVerifiers; // { engineId: SVVerifierHolder }
390
+ @property (nonatomic, strong) NSMutableDictionary *speakerMicControllers; // { controllerId: SVMicHolder }
94
391
 
95
392
  @end
96
393
 
@@ -101,6 +398,8 @@ RCT_EXPORT_MODULE();
101
398
  - (instancetype)init {
102
399
  if (self = [super init]) {
103
400
  _instances = [NSMutableDictionary new];
401
+ _speakerVerifiers = [NSMutableDictionary new];
402
+ _speakerMicControllers = [NSMutableDictionary new];
104
403
  }
105
404
  return self;
106
405
  }
@@ -110,9 +409,411 @@ RCT_EXPORT_MODULE();
110
409
  return YES;
111
410
  }
112
411
 
412
+ // NOTE: Extend supported events with Speaker Verification mic events
113
413
  - (NSArray<NSString *> *)supportedEvents {
114
414
  return @[@"onKeywordDetectionEvent",
115
- @"onVADDetectionEvent"]; // NEW
415
+ @"onVADDetectionEvent",
416
+ @"onSpeakerVerificationOnboardingProgress",
417
+ @"onSpeakerVerificationOnboardingDone",
418
+ @"onSpeakerVerificationVerifyResult",
419
+ @"onSpeakerVerificationError"];
420
+ }
421
+
422
+
423
+ // ============================================================
424
+ // MARK: - Speaker Verification (Swift) - RN APIs
425
+ // ============================================================
426
+
427
+ // Create a speaker verifier engine from:
428
+ // - modelPathOrName: absolute path OR "speaker_model.dm" in app bundle
429
+ // - enrollmentJsonPathOrName: absolute path OR "kesku_enrollment.json" in app bundle
430
+ //
431
+ // JS should call this once, then call verifySpeakerWavStreaming(engineId, wavPath)
432
+ RCT_EXPORT_METHOD(createSpeakerVerifier:(NSString *)engineId
433
+ modelPathOrName:(NSString *)modelPathOrName
434
+ enrollmentJsonPathOrName:(NSString *)enrollmentJsonPathOrName
435
+ options:(NSDictionary *)options
436
+ resolver:(RCTPromiseResolveBlock)resolve
437
+ rejecter:(RCTPromiseRejectBlock)reject)
438
+ {
439
+ if (self.speakerVerifiers[engineId]) {
440
+ reject(@"SVEngineExists", [NSString stringWithFormat:@"Speaker verifier already exists with ID: %@", engineId], nil);
441
+ return;
442
+ }
443
+
444
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
445
+ @autoreleasepool {
446
+ NSString *modelResolved = SVResolveFilePath(modelPathOrName);
447
+ NSString *jsonResolved = SVResolveFilePath(enrollmentJsonPathOrName);
448
+
449
+ if (!modelResolved) {
450
+ reject(@"SVModelNotFound", [NSString stringWithFormat:@"Model file not found: %@", modelPathOrName], nil);
451
+ return;
452
+ }
453
+ if (!jsonResolved) {
454
+ reject(@"SVEnrollmentNotFound", [NSString stringWithFormat:@"Enrollment JSON not found: %@", enrollmentJsonPathOrName], nil);
455
+ return;
456
+ }
457
+
458
+ // Optional: ensure stable file path by copying to tmp if needed
459
+ NSString *modelPath = SVCopyToTempIfNeeded(modelResolved, [modelPathOrName lastPathComponent]) ?: modelResolved;
460
+ NSString *jsonPath = SVCopyToTempIfNeeded(jsonResolved, [enrollmentJsonPathOrName lastPathComponent]) ?: jsonResolved;
461
+
462
+ NSError *err = nil;
463
+ id engine = SVCreateEngine(modelPath, jsonPath, options ?: @{}, &err);
464
+ if (err || !engine) {
465
+ reject(@"SVCreateError",
466
+ [NSString stringWithFormat:@"Failed to create speaker verifier: %@", err.localizedDescription ?: @"unknown"],
467
+ err);
468
+ return;
469
+ }
470
+
471
+ SVVerifierHolder *h = [SVVerifierHolder new];
472
+ h.engineId = engineId;
473
+ h.engine = engine;
474
+ self.speakerVerifiers[engineId] = h;
475
+ resolve(@{ @"ok": @YES, @"engineId": engineId, @"modelPath": modelPath, @"enrollmentJsonPath": jsonPath });
476
+ }
477
+ });
478
+ }
479
+
480
+ // Verify a WAV file by streaming frames internally via the Swift engine.
481
+ // wavPathOrName can be absolute path OR "test.wav" in bundle.
482
+ RCT_EXPORT_METHOD(verifySpeakerWavStreaming:(NSString *)engineId
483
+ wavPathOrName:(NSString *)wavPathOrName
484
+ resetState:(BOOL)resetState
485
+ resolver:(RCTPromiseResolveBlock)resolve
486
+ rejecter:(RCTPromiseRejectBlock)reject)
487
+ {
488
+ SVVerifierHolder *h = self.speakerVerifiers[engineId];
489
+ if (!h || !h.engine) {
490
+ reject(@"SVEngineNotFound", [NSString stringWithFormat:@"No speaker verifier with ID: %@", engineId], nil);
491
+ return;
492
+ }
493
+
494
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
495
+ @autoreleasepool {
496
+ NSString *wavResolved = SVResolveFilePath(wavPathOrName);
497
+ if (!wavResolved) {
498
+ reject(@"SVWavNotFound", [NSString stringWithFormat:@"WAV not found: %@", wavPathOrName], nil);
499
+ return;
500
+ }
501
+ NSString *wavPath = SVCopyToTempIfNeeded(wavResolved, [wavPathOrName lastPathComponent]) ?: wavResolved;
502
+
503
+ NSError *err = nil;
504
+ NSDictionary *out = SVVerifyWav(h.engine, wavPath, resetState, &err);
505
+ if (err || !out) {
506
+ reject(@"SVVerifyError",
507
+ [NSString stringWithFormat:@"Failed to verify wav: %@", err.localizedDescription ?: @"unknown"],
508
+ err);
509
+ return;
510
+ }
511
+ resolve(out);
512
+ }
513
+ });
514
+ }
515
+
516
+ RCT_EXPORT_METHOD(destroySpeakerVerifier:(NSString *)engineId
517
+ resolver:(RCTPromiseResolveBlock)resolve
518
+ rejecter:(RCTPromiseRejectBlock)reject)
519
+ {
520
+ SVVerifierHolder *h = self.speakerVerifiers[engineId];
521
+ if (!h) {
522
+ reject(@"SVEngineNotFound", [NSString stringWithFormat:@"No speaker verifier with ID: %@", engineId], nil);
523
+ return;
524
+ }
525
+ [self.speakerVerifiers removeObjectForKey:engineId];
526
+ resolve(@{ @"ok": @YES, @"engineId": engineId });
527
+ }
528
+
529
+
530
+ // ============================================================
531
+ // MARK: - Speaker Verification Mic Controller (Swift) - RN APIs
532
+ // ============================================================
533
+
534
+ // Create mic controller from config JSON (SpeakerVerificationConfig).
535
+ // Returns controllerId.
536
+ RCT_EXPORT_METHOD(createSpeakerVerificationMicController:(NSString *)controllerId
537
+ configJson:(NSString *)configJson
538
+ resolver:(RCTPromiseResolveBlock)resolve
539
+ rejecter:(RCTPromiseRejectBlock)reject)
540
+ {
541
+ NSLog(@"[SV][ObjC] createSpeakerVerificationMicController: controllerId=%@ jsonLen=%lu json=%@",
542
+ controllerId,
543
+ (unsigned long)(configJson ? configJson.length : 0),
544
+ configJson ?: @"(null)");
545
+
546
+ if (self.speakerMicControllers[controllerId]) {
547
+ reject(@"SVMicExists", [NSString stringWithFormat:@"Speaker mic controller already exists with ID: %@", controllerId], nil);
548
+ return;
549
+ }
550
+
551
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
552
+ @autoreleasepool {
553
+ NSError *err = nil;
554
+ id ctrl = SVCreateMicController(configJson, &err);
555
+ if (err || !ctrl) {
556
+ NSLog(@"[SV][ObjC] createSpeakerVerificationMicController: ❌ FAILED err=%@", err.localizedDescription ?: @"(null)");
557
+
558
+ reject(@"SVMicCreateError",
559
+ [NSString stringWithFormat:@"Failed to create mic controller: %@", err.localizedDescription ?: @"unknown"],
560
+ err);
561
+ return;
562
+ }
563
+ NSLog(@"[SV][ObjC] createSpeakerVerificationMicController: ✅ ctrl=%@", ctrl);
564
+
565
+
566
+ // Create delegate proxy that will forward Swift callbacks -> RN events
567
+ SVMicDelegateProxy *proxy = [SVMicDelegateProxy new];
568
+ proxy.bridge = self;
569
+ proxy.controllerId = controllerId;
570
+
571
+ // Set delegate dynamically (no header needed)
572
+ SVSetDelegate(ctrl, proxy);
573
+ NSLog(@"[SV][ObjC] createSpeakerVerificationMicController: delegate set proxy=%@", proxy);
574
+
575
+ SVMicHolder *h = [SVMicHolder new];
576
+ h.controllerId = controllerId;
577
+ h.controller = ctrl;
578
+ h.delegateProxy = proxy; // keep strong ref
579
+ self.speakerMicControllers[controllerId] = h;
580
+ NSLog(@"[SV][ObjC] createSpeakerVerificationMicController: stored holder. count=%lu",
581
+ (unsigned long)self.speakerMicControllers.count);
582
+
583
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId });
584
+ }
585
+ });
586
+ }
587
+
588
+ RCT_EXPORT_METHOD(destroySpeakerVerificationMicController:(NSString *)controllerId
589
+ resolver:(RCTPromiseResolveBlock)resolve
590
+ rejecter:(RCTPromiseRejectBlock)reject)
591
+ {
592
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
593
+ if (!h) {
594
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
595
+ return;
596
+ }
597
+
598
+ // Best-effort stop (no throw)
599
+ if (h.controller && [h.controller respondsToSelector:NSSelectorFromString(@"stop")]) {
600
+ void (*msgSend)(id, SEL) = (void*)objc_msgSend;
601
+ msgSend(h.controller, NSSelectorFromString(@"stop"));
602
+ }
603
+
604
+ [self.speakerMicControllers removeObjectForKey:controllerId];
605
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId });
606
+ }
607
+
608
+ // beginOnboarding(enrollmentId, targetEmbeddingCount, reset)
609
+ RCT_EXPORT_METHOD(svBeginOnboarding:(NSString *)controllerId
610
+ enrollmentId:(NSString *)enrollmentId
611
+ targetEmbeddingCount:(NSInteger)targetEmbeddingCount
612
+ reset:(BOOL)reset
613
+ resolver:(RCTPromiseResolveBlock)resolve
614
+ rejecter:(RCTPromiseRejectBlock)reject)
615
+ {
616
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
617
+ if (!h || !h.controller) {
618
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
619
+ return;
620
+ }
621
+
622
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
623
+ @autoreleasepool {
624
+ NSError *err = nil;
625
+ SEL sel = NSSelectorFromString(@"beginOnboardingWithEnrollmentId:targetEmbeddingCount:reset:error:");
626
+ if (![h.controller respondsToSelector:sel]) {
627
+ reject(@"SVMicMissingSelector", @"Mic controller missing beginOnboardingWithEnrollmentId:targetEmbeddingCount:reset:error:", nil);
628
+ return;
629
+ }
630
+ BOOL ok = SVCallBool2(h.controller, sel, enrollmentId, targetEmbeddingCount, reset, &err);
631
+ if (!ok || err) {
632
+ reject(@"SVMicBeginError",
633
+ [NSString stringWithFormat:@"beginOnboarding failed: %@", err.localizedDescription ?: @"unknown"],
634
+ err);
635
+ return;
636
+ }
637
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId, @"enrollmentId": enrollmentId, @"target": @(targetEmbeddingCount) });
638
+ }
639
+ });
640
+ }
641
+
642
+ // getNextEmbeddingFromMic(): starts mic, collects ONE embedding, then stops mic.
643
+ // Result is delivered via event:
644
+ // onSpeakerVerificationOnboardingProgress / onSpeakerVerificationOnboardingDone
645
+ RCT_EXPORT_METHOD(svGetNextEmbeddingFromMic:(NSString *)controllerId
646
+ resolver:(RCTPromiseResolveBlock)resolve
647
+ rejecter:(RCTPromiseRejectBlock)reject)
648
+ {
649
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
650
+ if (!h || !h.controller) {
651
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
652
+ return;
653
+ }
654
+
655
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
656
+ @autoreleasepool {
657
+ NSError *err = nil;
658
+ SEL sel = NSSelectorFromString(@"getNextEmbeddingFromMicAndReturnError:");
659
+ if (![h.controller respondsToSelector:sel]) {
660
+ reject(@"SVMicMissingSelector", @"Mic controller missing getNextEmbeddingFromMicAndReturnError:", nil);
661
+ return;
662
+ }
663
+ BOOL ok = SVCallBool1(h.controller, sel, &err);
664
+ if (!ok || err) {
665
+ reject(@"SVMicGetNextError",
666
+ [NSString stringWithFormat:@"getNextEmbeddingFromMic failed: %@", err.localizedDescription ?: @"unknown"],
667
+ err);
668
+ return;
669
+ }
670
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId });
671
+ }
672
+ });
673
+ }
674
+
675
+ // finalizeOnboardingNow(): forces finalize and emits onSpeakerVerificationOnboardingDone (from Swift delegate)
676
+ RCT_EXPORT_METHOD(svFinalizeOnboardingNow:(NSString *)controllerId
677
+ resolver:(RCTPromiseResolveBlock)resolve
678
+ rejecter:(RCTPromiseRejectBlock)reject)
679
+ {
680
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
681
+ if (!h || !h.controller) {
682
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
683
+ return;
684
+ }
685
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
686
+ @autoreleasepool {
687
+ NSError *err = nil;
688
+ SEL sel = NSSelectorFromString(@"finalizeOnboardingNowAndReturnError:");
689
+ if (![h.controller respondsToSelector:sel]) {
690
+ reject(@"SVMicMissingSelector", @"Mic controller missing finalizeOnboardingNowAndReturnError:", nil);
691
+ return;
692
+ }
693
+ BOOL ok = SVCallBool1(h.controller, sel, &err);
694
+ if (!ok || err) {
695
+ reject(@"SVMicFinalizeError",
696
+ [NSString stringWithFormat:@"finalizeOnboardingNow failed: %@", err.localizedDescription ?: @"unknown"],
697
+ err);
698
+ return;
699
+ }
700
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId });
701
+ }
702
+ });
703
+ }
704
+
705
+ // setEnrollmentJson(enrollmentJson): sets enrollment for verification mode
706
+ RCT_EXPORT_METHOD(svSetEnrollmentJson:(NSString *)controllerId
707
+ enrollmentJson:(NSString *)enrollmentJson
708
+ resolver:(RCTPromiseResolveBlock)resolve
709
+ rejecter:(RCTPromiseRejectBlock)reject)
710
+ {
711
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
712
+ if (!h || !h.controller) {
713
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
714
+ return;
715
+ }
716
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
717
+ @autoreleasepool {
718
+ NSError *err = nil;
719
+ SEL sel = NSSelectorFromString(@"setEnrollmentJson:error:");
720
+ if (![h.controller respondsToSelector:sel]) {
721
+ reject(@"SVMicMissingSelector", @"Mic controller missing setEnrollmentJson:error:", nil);
722
+ return;
723
+ }
724
+ BOOL (*msgSend)(id, SEL, NSString*, NSError**) = (void*)objc_msgSend;
725
+ BOOL ok = msgSend(h.controller, sel, enrollmentJson, &err);
726
+ if (!ok || err) {
727
+ reject(@"SVMicSetEnrollError",
728
+ [NSString stringWithFormat:@"setEnrollmentJson failed: %@", err.localizedDescription ?: @"unknown"],
729
+ err);
730
+ return;
731
+ }
732
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId });
733
+ }
734
+ });
735
+ }
736
+
737
+ // startVerifyFromMic(resetState): starts mic until a verification result is produced.
738
+ // Result delivered via event: onSpeakerVerificationVerifyResult
739
+ RCT_EXPORT_METHOD(svStartVerifyFromMic:(NSString *)controllerId
740
+ resetState:(BOOL)resetState
741
+ resolver:(RCTPromiseResolveBlock)resolve
742
+ rejecter:(RCTPromiseRejectBlock)reject)
743
+ {
744
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
745
+ if (!h || !h.controller) {
746
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
747
+ return;
748
+ }
749
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
750
+ @autoreleasepool {
751
+ NSError *err = nil;
752
+ SEL sel = NSSelectorFromString(@"startVerifyFromMicWithResetState:error:");
753
+ if (![h.controller respondsToSelector:sel]) {
754
+ reject(@"SVMicMissingSelector", @"Mic controller missing startVerifyFromMicWithResetState:error:", nil);
755
+ return;
756
+ }
757
+ BOOL ok = SVCallBoolReset(h.controller, sel, resetState, &err);
758
+ if (!ok || err) {
759
+ reject(@"SVMicStartVerifyError",
760
+ [NSString stringWithFormat:@"startVerifyFromMic failed: %@", err.localizedDescription ?: @"unknown"],
761
+ err);
762
+ return;
763
+ }
764
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId, @"resetState": @(resetState) });
765
+ }
766
+ });
767
+ }
768
+ // startEndlessVerifyFromMic(hopSeconds, stopOnMatch): starts mic and keeps verifying repeatedly.
769
+ // Results delivered via event: onSpeakerVerificationVerifyResult
770
+ RCT_EXPORT_METHOD(svStartEndlessVerifyFromMic:(NSString *)controllerId
771
+ hopSeconds:(nonnull NSNumber *)hopSeconds
772
+ stopOnMatch:(BOOL)stopOnMatch
773
+ resolver:(RCTPromiseResolveBlock)resolve
774
+ rejecter:(RCTPromiseRejectBlock)reject)
775
+ {
776
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
777
+ if (!h || !h.controller) {
778
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
779
+ return;
780
+ }
781
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
782
+ @autoreleasepool {
783
+ NSError *err = nil;
784
+ SEL sel = NSSelectorFromString(@"startEndlessVerifyFromMicWithHopSeconds:stopOnMatch:error:");
785
+ if (![h.controller respondsToSelector:sel]) {
786
+ reject(@"SVMicMissingSelector", @"Mic controller missing startEndlessVerifyFromMicWithHopSeconds:stopOnMatch:error:", nil);
787
+ return;
788
+ }
789
+ BOOL ok = SVCallBoolHopStop(h.controller, sel, hopSeconds, stopOnMatch, &err);
790
+ if (!ok || err) {
791
+ reject(@"SVMicStartEndlessVerifyError",
792
+ [NSString stringWithFormat:@"startEndlessVerifyFromMic failed: %@", err.localizedDescription ?: @"unknown"],
793
+ err);
794
+ return;
795
+ }
796
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId, @"hopSeconds": hopSeconds, @"stopOnMatch": @(stopOnMatch) });
797
+ }
798
+ });
799
+ }
800
+
801
+
802
+ // stop(): stop mic immediately
803
+ RCT_EXPORT_METHOD(svStopMic:(NSString *)controllerId
804
+ resolver:(RCTPromiseResolveBlock)resolve
805
+ rejecter:(RCTPromiseRejectBlock)reject)
806
+ {
807
+ SVMicHolder *h = self.speakerMicControllers[controllerId];
808
+ if (!h || !h.controller) {
809
+ reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
810
+ return;
811
+ }
812
+ if ([h.controller respondsToSelector:NSSelectorFromString(@"stop")]) {
813
+ void (*msgSend)(id, SEL) = (void*)objc_msgSend;
814
+ msgSend(h.controller, NSSelectorFromString(@"stop"));
815
+ }
816
+ resolve(@{ @"ok": @YES, @"controllerId": controllerId });
116
817
  }
117
818
 
118
819
  RCT_EXPORT_METHOD(createInstanceMulti:(NSString *)instanceId
@@ -185,7 +886,7 @@ RCT_EXPORT_METHOD(setAudioRoutingConfig:(NSString *)jsonConfig
185
886
  RCT_EXPORT_METHOD(disableDucking:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
186
887
  {
187
888
  [AudioSessionAndDuckingManager.shared disableDucking];
188
- resolve(@"enabled");
889
+ resolve(@"disabled");
189
890
  }
190
891
 
191
892
  RCT_EXPORT_METHOD(initAudioSessAndDuckManage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@@ -413,4 +1114,4 @@ RCT_EXPORT_METHOD(setVADParams:(NSString *)instanceId
413
1114
 
414
1115
  // You can add more methods here as needed, ensuring they use the instanceId
415
1116
 
416
- @end
1117
+ @end