react-native-wakeword-sid 1.1.55

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 (77) hide show
  1. package/KeyWordRNBridge.podspec +58 -0
  2. package/LICENSE +21 -0
  3. package/README.md +281 -0
  4. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  5. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  6. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  7. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/8.9/gc.properties +0 -0
  9. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  10. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  11. package/android/.gradle/vcs-1/gc.properties +0 -0
  12. package/android/build.gradle +48 -0
  13. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar +0 -0
  14. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.md5 +1 -0
  15. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.sha1 +1 -0
  16. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.pom +10 -0
  17. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.pom.md5 +1 -0
  18. package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.pom.sha1 +1 -0
  19. package/android/settings.gradle +2 -0
  20. package/android/src/main/AndroidManifest.xml +13 -0
  21. package/android/src/main/assets/hey_lookdeep.dm +0 -0
  22. package/android/src/main/assets/layer1.dm +0 -0
  23. package/android/src/main/assets/need_help_now.dm +0 -0
  24. package/android/src/main/java/com/davoice/DaVoiceUnifiedPackage.java +29 -0
  25. package/android/src/main/java/com/davoice/keywordspotting/KeyWordRNBridge.java +335 -0
  26. package/android/src/main/java/com/davoice/speakeridrn/SpeakerIdRNBridge.java_not_used_yet +588 -0
  27. package/android/src/main/libs/MyLibrary-release.aar +0 -0
  28. package/app.plugin.js +60 -0
  29. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/Info.plist +44 -0
  30. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +386 -0
  31. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Headers/KeyWordDetection.h +18 -0
  32. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Info.plist +0 -0
  33. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/KeyWordDetection +0 -0
  34. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.abi.json +5758 -0
  35. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.private.swiftinterface +159 -0
  36. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  37. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftinterface +159 -0
  38. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/module.modulemap +11 -0
  39. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +768 -0
  40. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Headers/KeyWordDetection.h +18 -0
  41. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Info.plist +0 -0
  42. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/KeyWordDetection +0 -0
  43. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.abi.json +5758 -0
  44. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +159 -0
  45. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  46. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftinterface +159 -0
  47. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json +5758 -0
  48. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +159 -0
  49. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  50. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +159 -0
  51. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/module.modulemap +11 -0
  52. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeDirectory +0 -0
  53. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeRequirements +0 -0
  54. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeRequirements-1 +0 -0
  55. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeResources +297 -0
  56. package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeSignature +0 -0
  57. package/ios/KeyWordRNBridge/KeyWordRNBridge.h +19 -0
  58. package/ios/KeyWordRNBridge/KeyWordRNBridge.m +642 -0
  59. package/ios/KeyWordRNBridge/KeyWordRNBridge.mm +416 -0
  60. package/ios/KeyWordRNBridge/models/coca_cola_model_28_05052025.onnx +0 -0
  61. package/ios/KeyWordRNBridge/models/embedding_model.onnx +0 -0
  62. package/ios/KeyWordRNBridge/models/hey_lookdeep.onnx +0 -0
  63. package/ios/KeyWordRNBridge/models/melspectrogram.onnx +0 -0
  64. package/ios/KeyWordRNBridge/models/need_help_now.onnx +0 -0
  65. package/ios/KeyWordRNBridge/models/silero_vad.onnx +0 -0
  66. package/package.json +74 -0
  67. package/react-native.config.js +10 -0
  68. package/wakewords/KeyWordRNBridge.d.ts +38 -0
  69. package/wakewords/KeyWordRNBridge.js +228 -0
  70. package/wakewords/SpeakerVerificationRNBridge.d.ts +32 -0
  71. package/wakewords/SpeakerVerificationRNBridge.js +124 -0
  72. package/wakewords/audioRoutingConfig.d.ts +17 -0
  73. package/wakewords/audioRoutingConfig.ts +28 -0
  74. package/wakewords/index.d.ts +16 -0
  75. package/wakewords/index.js +34 -0
  76. package/wakewords/useModel.d.ts +19 -0
  77. package/wakewords/useModel.tsx +235 -0
@@ -0,0 +1,642 @@
1
+ //ios/KeyWordRNBridge.m
2
+
3
+ #import "KeyWordRNBridge.h"
4
+ #import <React/RCTBridge.h>
5
+ #import <React/RCTLog.h>
6
+ #import <React/RCTEventEmitter.h>
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
+
11
+
12
+ // Ensure the protocol is correctly imported or declared
13
+ // Assuming the protocol is named 'KeywordDetectionRNDelegate'
14
+ @interface KeyWordsDetectionWrapper : NSObject <KeywordDetectionRNDelegate>
15
+
16
+ @property (nonatomic, strong) KeyWordsDetection *keyWordsDetection;
17
+ @property (nonatomic, strong) NSString *instanceId;
18
+ @property (nonatomic, weak) KeyWordRNBridge *bridge;
19
+
20
+ - (instancetype)initWithInstanceId:(NSString *)instanceId
21
+ modelName:(NSString *)modelName
22
+ threshold:(float)threshold
23
+ bufferCnt:(NSInteger)bufferCnt
24
+ bridge:(KeyWordRNBridge *)bridge
25
+ error:(NSError **)error;
26
+
27
+ - (instancetype)initWithInstanceId:(NSString *)instanceId
28
+ modelNames:(NSArray<NSString *> *)modelNames
29
+ thresholds:(NSArray<NSNumber *> *)thresholds
30
+ bufferCnts:(NSArray<NSNumber *> *)bufferCnts
31
+ msBetweenCallback:(NSArray<NSNumber *> *)msBetweenCallback
32
+ bridge:(KeyWordRNBridge *)bridge
33
+ error:(NSError **)error;
34
+
35
+ @end
36
+
37
+ @implementation KeyWordsDetectionWrapper
38
+
39
+ - (instancetype)initWithInstanceId:(NSString *)instanceId
40
+ modelName:(NSString *)modelName
41
+ threshold:(float)threshold
42
+ bufferCnt:(NSInteger)bufferCnt
43
+ bridge:(KeyWordRNBridge *)bridge
44
+ error:(NSError **)error
45
+ {
46
+ if (self = [super init]) {
47
+ _instanceId = instanceId;
48
+ _bridge = bridge;
49
+ _keyWordsDetection = [[KeyWordsDetection alloc] initWithModelPath:modelName threshold:threshold bufferCnt:bufferCnt error:error];
50
+ if (*error) {
51
+ return nil;
52
+ }
53
+ _keyWordsDetection.delegate = self;
54
+ }
55
+ return self;
56
+ }
57
+
58
+ - (instancetype)initWithInstanceId:(NSString *)instanceId
59
+ modelNames:(NSArray<NSString *> *)modelNames
60
+ thresholds:(NSArray<NSNumber *> *)thresholds
61
+ bufferCnts:(NSArray<NSNumber *> *)bufferCnts
62
+ msBetweenCallback:(NSArray<NSNumber *> *)msBetweenCallback
63
+ bridge:(KeyWordRNBridge *)bridge
64
+ error:(NSError **)error {
65
+ if (self = [super init]) {
66
+ _instanceId = instanceId;
67
+ _bridge = bridge;
68
+
69
+ NSMutableArray<NSNumber *> *floatThresholds = [NSMutableArray array];
70
+ for (NSNumber *num in thresholds) {
71
+ [floatThresholds addObject:@(num.floatValue)];
72
+ }
73
+
74
+ _keyWordsDetection = [[KeyWordsDetection alloc] initWithModelPaths:modelNames
75
+ thresholds:floatThresholds
76
+ bufferCnts:bufferCnts
77
+ msBetweenCallback:msBetweenCallback
78
+ error:error];
79
+ if (*error) return nil;
80
+ _keyWordsDetection.delegate = self;
81
+ }
82
+ return self;
83
+ }
84
+
85
+ // Implement the delegate method
86
+ - (void)KeywordDetectionDidDetectEvent:(NSDictionary *)eventInfo {
87
+ NSMutableDictionary *mutableEventInfo = [eventInfo mutableCopy];
88
+ mutableEventInfo[@"instanceId"] = self.instanceId;
89
+ [_bridge sendEventWithName:@"onKeywordDetectionEvent" body:mutableEventInfo];
90
+ }
91
+
92
+ @end
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
+ // Resolve a file path from:
108
+ // 1) absolute path (exists)
109
+ // 2) main bundle resource "name.ext"
110
+ // 3) bundle scan by lastPathComponent (RN sometimes hashes folders)
111
+ // 4) if found in bundle but not a stable path, copy to tmp and return tmp path
112
+ static NSString * _Nullable SVResolveFilePath(NSString *input) {
113
+ if (!input || input.length == 0) return nil;
114
+
115
+ // Absolute path?
116
+ if ([input hasPrefix:@"/"] && [[NSFileManager defaultManager] fileExistsAtPath:input]) {
117
+ return input;
118
+ }
119
+
120
+ NSString *fileName = [input lastPathComponent];
121
+ NSString *base = [fileName stringByDeletingPathExtension];
122
+ NSString *ext = [fileName pathExtension];
123
+
124
+ // Direct bundle lookup
125
+ NSString *p = [[NSBundle mainBundle] pathForResource:base ofType:ext.length ? ext : nil];
126
+ if (p && [[NSFileManager defaultManager] fileExistsAtPath:p]) {
127
+ return p;
128
+ }
129
+
130
+ // Scan bundle for lastPathComponent (covers RN "assets/..." or nested resources)
131
+ NSArray<NSString *> *candidatesExt = ext.length ? @[ext] : @[@"onnx", @"json", @"wav"];
132
+ for (NSString *e in candidatesExt) {
133
+ NSArray<NSString *> *paths = [[NSBundle mainBundle] pathsForResourcesOfType:e inDirectory:nil];
134
+ for (NSString *pp in paths) {
135
+ if ([[pp lastPathComponent] isEqualToString:fileName]) {
136
+ return pp;
137
+ }
138
+ }
139
+ }
140
+
141
+ return nil;
142
+ }
143
+
144
+ static NSString * _Nullable SVCopyToTempIfNeeded(NSString *path, NSString *preferredName) {
145
+ if (!path) return nil;
146
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
147
+ // Bundle paths are valid; ORT needs a file path, bundle path is fine.
148
+ // But keep copy logic for safety (some resources could be non-file URLs in edge cases).
149
+ return path;
150
+ }
151
+
152
+ // Fallback: try to copy from bundle URL
153
+ NSURL *url = [[NSBundle mainBundle] URLForResource:[preferredName stringByDeletingPathExtension]
154
+ withExtension:[preferredName pathExtension]];
155
+ if (!url) return nil;
156
+
157
+ NSString *tmp = [NSTemporaryDirectory() stringByAppendingPathComponent:preferredName];
158
+ [[NSFileManager defaultManager] removeItemAtPath:tmp error:nil];
159
+ NSError *err = nil;
160
+ BOOL ok = [[NSFileManager defaultManager] copyItemAtURL:url toURL:[NSURL fileURLWithPath:tmp] error:&err];
161
+ if (!ok || err) return nil;
162
+ return tmp;
163
+ }
164
+
165
+ static NSDictionary *SVErrDict(NSString *code, NSString *msg) {
166
+ return @{ @"code": code ?: @"Error", @"message": msg ?: @"Unknown error" };
167
+ }
168
+
169
+ // Dynamic Swift bridge:
170
+ // Expect a Swift class annotated: @objc(SpeakerVerificationRNFacade)
171
+ // with ObjC-visible selectors:
172
+ // + (id)createEngineWithModelPath:(NSString*)modelPath enrollmentJsonPath:(NSString*)jsonPath options:(NSDictionary*)options error:(NSError**)error;
173
+ // + (NSDictionary*)verifyWavWithEngine:(id)engine wavPath:(NSString*)wavPath reset:(BOOL)reset error:(NSError**)error;
174
+ static Class SVFacadeClass(void) {
175
+ return NSClassFromString(@"SpeakerVerificationRNFacade");
176
+ }
177
+
178
+ static id _Nullable SVCreateEngine(NSString *modelPath, NSString *jsonPath, NSDictionary *options, NSError **error) {
179
+ Class c = SVFacadeClass();
180
+ if (!c) {
181
+ if (error) *error = [NSError errorWithDomain:@"SV" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Swift class SpeakerVerificationRNFacade not found (did you add it?)"}];
182
+ return nil;
183
+ }
184
+ SEL sel = NSSelectorFromString(@"createEngineWithModelPath:enrollmentJsonPath:options:error:");
185
+ if (![c respondsToSelector:sel]) {
186
+ if (error) *error = [NSError errorWithDomain:@"SV" code:2 userInfo:@{NSLocalizedDescriptionKey: @"Missing selector createEngineWithModelPath:enrollmentJsonPath:options:error:"}];
187
+ return nil;
188
+ }
189
+ id (*msgSend)(id, SEL, NSString*, NSString*, NSDictionary*, NSError**) = (void*)objc_msgSend;
190
+ return msgSend(c, sel, modelPath, jsonPath, options ?: @{}, error);
191
+ }
192
+
193
+ static NSDictionary * _Nullable SVVerifyWav(id engine, NSString *wavPath, BOOL reset, NSError **error) {
194
+ Class c = SVFacadeClass();
195
+ if (!c) {
196
+ if (error) *error = [NSError errorWithDomain:@"SV" code:3 userInfo:@{NSLocalizedDescriptionKey: @"Swift class SpeakerVerificationRNFacade not found"}];
197
+ return nil;
198
+ }
199
+ SEL sel = NSSelectorFromString(@"verifyWavWithEngine:wavPath:reset:error:");
200
+ if (![c respondsToSelector:sel]) {
201
+ if (error) *error = [NSError errorWithDomain:@"SV" code:4 userInfo:@{NSLocalizedDescriptionKey: @"Missing selector verifyWavWithEngine:wavPath:reset:error:"}];
202
+ return nil;
203
+ }
204
+ NSDictionary* (*msgSend)(id, SEL, id, NSString*, BOOL, NSError**) = (void*)objc_msgSend;
205
+ return msgSend(c, sel, engine, wavPath, reset, error);
206
+ }
207
+
208
+ @interface KeyWordRNBridge () <RCTBridgeModule>
209
+
210
+ @property (nonatomic, strong) NSMutableDictionary *instances;
211
+ @property (nonatomic, strong) NSMutableDictionary *speakerVerifiers; // { engineId: SVVerifierHolder }
212
+
213
+ @end
214
+
215
+ @implementation KeyWordRNBridge
216
+
217
+ RCT_EXPORT_MODULE();
218
+
219
+ - (instancetype)init {
220
+ if (self = [super init]) {
221
+ _instances = [NSMutableDictionary new];
222
+ _speakerVerifiers = [NSMutableDictionary new];
223
+ }
224
+ return self;
225
+ }
226
+
227
+ + (BOOL)requiresMainQueueSetup
228
+ {
229
+ return YES;
230
+ }
231
+
232
+ - (NSArray<NSString *> *)supportedEvents {
233
+ return @[@"onKeywordDetectionEvent",
234
+ @"onVADDetectionEvent"]; // NEW
235
+ }
236
+
237
+ // ============================================================
238
+ // MARK: - Speaker Verification (Swift) - RN APIs
239
+ // ============================================================
240
+
241
+ // Create a speaker verifier engine from:
242
+ // - modelPathOrName: absolute path OR "speaker_model.onnx" in app bundle
243
+ // - enrollmentJsonPathOrName: absolute path OR "kesku_enrollment.json" in app bundle
244
+ //
245
+ // JS should call this once, then call verifySpeakerWavStreaming(engineId, wavPath)
246
+ RCT_EXPORT_METHOD(createSpeakerVerifier:(NSString *)engineId
247
+ modelPathOrName:(NSString *)modelPathOrName
248
+ enrollmentJsonPathOrName:(NSString *)enrollmentJsonPathOrName
249
+ options:(NSDictionary *)options
250
+ resolver:(RCTPromiseResolveBlock)resolve
251
+ rejecter:(RCTPromiseRejectBlock)reject)
252
+ {
253
+ if (self.speakerVerifiers[engineId]) {
254
+ reject(@"SVEngineExists", [NSString stringWithFormat:@"Speaker verifier already exists with ID: %@", engineId], nil);
255
+ return;
256
+ }
257
+
258
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
259
+ @autoreleasepool {
260
+ NSString *modelResolved = SVResolveFilePath(modelPathOrName);
261
+ NSString *jsonResolved = SVResolveFilePath(enrollmentJsonPathOrName);
262
+
263
+ if (!modelResolved) {
264
+ reject(@"SVModelNotFound", [NSString stringWithFormat:@"Model file not found: %@", modelPathOrName], nil);
265
+ return;
266
+ }
267
+ if (!jsonResolved) {
268
+ reject(@"SVEnrollmentNotFound", [NSString stringWithFormat:@"Enrollment JSON not found: %@", enrollmentJsonPathOrName], nil);
269
+ return;
270
+ }
271
+
272
+ // Optional: ensure stable file path by copying to tmp if needed
273
+ NSString *modelPath = SVCopyToTempIfNeeded(modelResolved, [modelPathOrName lastPathComponent]) ?: modelResolved;
274
+ NSString *jsonPath = SVCopyToTempIfNeeded(jsonResolved, [enrollmentJsonPathOrName lastPathComponent]) ?: jsonResolved;
275
+
276
+ NSError *err = nil;
277
+ id engine = SVCreateEngine(modelPath, jsonPath, options ?: @{}, &err);
278
+ if (err || !engine) {
279
+ reject(@"SVCreateError",
280
+ [NSString stringWithFormat:@"Failed to create speaker verifier: %@", err.localizedDescription ?: @"unknown"],
281
+ err);
282
+ return;
283
+ }
284
+
285
+ SVVerifierHolder *h = [SVVerifierHolder new];
286
+ h.engineId = engineId;
287
+ h.engine = engine;
288
+ self.speakerVerifiers[engineId] = h;
289
+ resolve(@{ @"ok": @YES, @"engineId": engineId, @"modelPath": modelPath, @"enrollmentJsonPath": jsonPath });
290
+ }
291
+ });
292
+ }
293
+
294
+ // Verify a WAV file by streaming frames internally via the Swift engine.
295
+ // wavPathOrName can be absolute path OR "test.wav" in bundle.
296
+ RCT_EXPORT_METHOD(verifySpeakerWavStreaming:(NSString *)engineId
297
+ wavPathOrName:(NSString *)wavPathOrName
298
+ resetState:(BOOL)resetState
299
+ resolver:(RCTPromiseResolveBlock)resolve
300
+ rejecter:(RCTPromiseRejectBlock)reject)
301
+ {
302
+ SVVerifierHolder *h = self.speakerVerifiers[engineId];
303
+ if (!h || !h.engine) {
304
+ reject(@"SVEngineNotFound", [NSString stringWithFormat:@"No speaker verifier with ID: %@", engineId], nil);
305
+ return;
306
+ }
307
+
308
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
309
+ @autoreleasepool {
310
+ NSString *wavResolved = SVResolveFilePath(wavPathOrName);
311
+ if (!wavResolved) {
312
+ reject(@"SVWavNotFound", [NSString stringWithFormat:@"WAV not found: %@", wavPathOrName], nil);
313
+ return;
314
+ }
315
+ NSString *wavPath = SVCopyToTempIfNeeded(wavResolved, [wavPathOrName lastPathComponent]) ?: wavResolved;
316
+
317
+ NSError *err = nil;
318
+ NSDictionary *out = SVVerifyWav(h.engine, wavPath, resetState, &err);
319
+ if (err || !out) {
320
+ reject(@"SVVerifyError",
321
+ [NSString stringWithFormat:@"Failed to verify wav: %@", err.localizedDescription ?: @"unknown"],
322
+ err);
323
+ return;
324
+ }
325
+ resolve(out);
326
+ }
327
+ });
328
+ }
329
+
330
+ RCT_EXPORT_METHOD(destroySpeakerVerifier:(NSString *)engineId
331
+ resolver:(RCTPromiseResolveBlock)resolve
332
+ rejecter:(RCTPromiseRejectBlock)reject)
333
+ {
334
+ SVVerifierHolder *h = self.speakerVerifiers[engineId];
335
+ if (!h) {
336
+ reject(@"SVEngineNotFound", [NSString stringWithFormat:@"No speaker verifier with ID: %@", engineId], nil);
337
+ return;
338
+ }
339
+ [self.speakerVerifiers removeObjectForKey:engineId];
340
+ resolve(@{ @"ok": @YES, @"engineId": engineId });
341
+ }
342
+
343
+
344
+ RCT_EXPORT_METHOD(createInstanceMulti:(NSString *)instanceId
345
+ modelPaths:(NSArray<NSString *> *)modelPaths
346
+ thresholds:(NSArray<NSNumber *> *)thresholds
347
+ bufferCnts:(NSArray<NSNumber *> *)bufferCnts
348
+ msBetweenCallback:(NSArray<NSNumber *> *)msBetweenCallback
349
+ resolver:(RCTPromiseResolveBlock)resolve
350
+ rejecter:(RCTPromiseRejectBlock)reject) {
351
+ if (self.instances[instanceId]) {
352
+ reject(@"InstanceExists", [NSString stringWithFormat:@"Instance already exists with ID: %@", instanceId], nil);
353
+ return;
354
+ }
355
+
356
+ NSError *error = nil;
357
+ KeyWordsDetectionWrapper *wrapper = [[KeyWordsDetectionWrapper alloc]
358
+ initWithInstanceId:instanceId
359
+ modelNames:modelPaths
360
+ thresholds:thresholds
361
+ bufferCnts:bufferCnts
362
+ msBetweenCallback:msBetweenCallback
363
+ bridge:self
364
+ error:&error];
365
+ if (error) {
366
+ reject(@"CreateError", [NSString stringWithFormat:@"Failed to create multi-model instance: %@", error.localizedDescription], nil);
367
+ } else {
368
+ self.instances[instanceId] = wrapper;
369
+ resolve([NSString stringWithFormat:@"Multi-model instance created with ID: %@", instanceId]);
370
+ }
371
+ }
372
+
373
+ RCT_EXPORT_METHOD(createInstance:(NSString *)instanceId modelName:(NSString *)modelName threshold:(float)threshold bufferCnt:(NSInteger)bufferCnt resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
374
+ {
375
+ if (self.instances[instanceId]) {
376
+ reject(@"InstanceExists", [NSString stringWithFormat:@"Instance already exists with ID: %@", instanceId], nil);
377
+ return;
378
+ }
379
+
380
+ NSError *error = nil;
381
+ KeyWordsDetectionWrapper *wrapper = [[KeyWordsDetectionWrapper alloc] initWithInstanceId:instanceId modelName:modelName threshold:threshold bufferCnt:bufferCnt bridge:self error:&error];
382
+ if (error) {
383
+ reject(@"CreateError", [NSString stringWithFormat:@"Failed to create instance: %@", error.localizedDescription], nil);
384
+ } else {
385
+ self.instances[instanceId] = wrapper;
386
+ resolve([NSString stringWithFormat:@"Instance created with ID: %@", instanceId]);
387
+ }
388
+ }
389
+
390
+ // NEW: receive global wakeword audio routing config from JS
391
+ RCT_EXPORT_METHOD(setAudioRoutingConfig:(NSString *)jsonConfig
392
+ resolver:(RCTPromiseResolveBlock)resolve
393
+ rejecter:(RCTPromiseRejectBlock)reject)
394
+ {
395
+ @try {
396
+ // Hand off to your audio/session manager (you implement this)
397
+ // e.g. in AudioSessionAndDuckingManager:
398
+ // - (void)setWakewordAudioRoutingConfigFromJSONString:(NSString *)jsonConfig;
399
+ [AudioSessionAndDuckingManager.shared setWakewordAudioRoutingConfigFromJSONString:jsonConfig];
400
+
401
+ NSLog(@"[KeyWordRNBridge] setAudioRoutingConfig JSON = %@", jsonConfig);
402
+ resolve(@"ok");
403
+ }
404
+ @catch (NSException *e) {
405
+ reject(@"AudioRoutingConfigError",
406
+ [NSString stringWithFormat:@"Failed to set audio routing config: %@", e.reason],
407
+ nil);
408
+ }
409
+ }
410
+
411
+ RCT_EXPORT_METHOD(disableDucking:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
412
+ {
413
+ [AudioSessionAndDuckingManager.shared disableDucking];
414
+ resolve(@"enabled");
415
+ }
416
+
417
+ RCT_EXPORT_METHOD(initAudioSessAndDuckManage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
418
+ {
419
+ [AudioSessionAndDuckingManager.shared initAudioSessAndDuckManage];
420
+ resolve(@"enabled");
421
+ }
422
+
423
+ RCT_EXPORT_METHOD(restartListeningAfterDucking:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
424
+ {
425
+ [AudioSessionAndDuckingManager.shared restartListeningAfterDucking];
426
+ resolve(@"disabled");
427
+ }
428
+
429
+ RCT_EXPORT_METHOD(enableAggressiveDucking:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
430
+ {
431
+ [AudioSessionAndDuckingManager.shared enableAggressiveDucking];
432
+ resolve(@"enabled");
433
+ }
434
+
435
+ RCT_EXPORT_METHOD(disableDuckingAndCleanup:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
436
+ {
437
+ [AudioSessionAndDuckingManager.shared disableDuckingAndCleanup];
438
+ resolve(@"disabled");
439
+ }
440
+
441
+ RCT_EXPORT_METHOD(setKeywordDetectionLicense:(NSString *)instanceId licenseKey:(NSString *)licenseKey resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
442
+ {
443
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
444
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
445
+ BOOL isLicensed = NO;
446
+ if (instance) {
447
+ isLicensed = [instance setLicenseWithLicenseKey:licenseKey];
448
+ NSLog(@"License is valid?: %@", isLicensed ? @"YES" : @"NO");
449
+ resolve(@(isLicensed)); // Wrap BOOL in NSNumber
450
+ } else {
451
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
452
+ }
453
+ }
454
+
455
+ RCT_EXPORT_METHOD(replaceKeywordDetectionModel:(NSString *)instanceId modelName:(NSString *)modelName threshold:(float)threshold bufferCnt:(NSInteger)bufferCnt resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
456
+ {
457
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
458
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
459
+ if (instance) {
460
+ NSError *error = nil;
461
+ [instance replaceKeywordDetectionModelWithModelPath:modelName threshold:threshold bufferCnt:bufferCnt error:&error];
462
+ if (error) {
463
+ reject(@"ReplaceError", [NSString stringWithFormat:@"Failed to replace model: %@", error.localizedDescription], nil);
464
+ } else {
465
+ resolve([NSString stringWithFormat:@"Instance ID: %@ changed model to %@", instanceId, modelName]);
466
+ }
467
+ } else {
468
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
469
+ }
470
+ }
471
+
472
+ RCT_EXPORT_METHOD(startKeywordDetection:(NSString *)instanceId
473
+ threshold:(float)threshold
474
+ noExternalActivation:(BOOL)noExternalActivation
475
+ duckOthers:(BOOL)duckOthers
476
+ mixWithOthers:(BOOL)mixWithOthers
477
+ defaultToSpeaker:(BOOL)defaultToSpeaker
478
+ resolver:(RCTPromiseResolveBlock)resolve
479
+ rejecter:(RCTPromiseRejectBlock)reject)
480
+ {
481
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
482
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
483
+ if (instance) {
484
+ BOOL success = [instance startListeningWithNoExternalActivation:noExternalActivation
485
+ duckOthers:duckOthers
486
+ mixWithOthers:mixWithOthers
487
+ defaultToSpeaker:defaultToSpeaker];
488
+ if (success == false) {
489
+ reject(@"StartError", [NSString stringWithFormat:@"Failed to start detection"], nil);
490
+ } else {
491
+ resolve([NSString stringWithFormat:@"Started detection for instance: %@", instanceId]);
492
+ }
493
+ } else {
494
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
495
+ }
496
+ }
497
+
498
+ RCT_EXPORT_METHOD(stopKeywordDetection:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
499
+ {
500
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
501
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
502
+ if (instance) {
503
+ [instance stopListening];
504
+ resolve([NSString stringWithFormat:@"Stopped detection for instance: %@", instanceId]);
505
+ } else {
506
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
507
+ }
508
+ }
509
+
510
+ RCT_EXPORT_METHOD(destroyInstance:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
511
+ {
512
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
513
+ if (wrapper) {
514
+ [wrapper.keyWordsDetection stopListening];
515
+ [self.instances removeObjectForKey:instanceId];
516
+ resolve([NSString stringWithFormat:@"Destroyed instance: %@", instanceId]);
517
+ } else {
518
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
519
+ }
520
+ }
521
+
522
+ // Keeping all APIs even if not called in JS yet
523
+
524
+ RCT_EXPORT_METHOD(getKeywordDetectionModel:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
525
+ {
526
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
527
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
528
+ if (instance) {
529
+ NSString *modelName = [instance getKeywordDetectionModel];
530
+ resolve(modelName);
531
+ } else {
532
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
533
+ }
534
+ }
535
+
536
+ RCT_EXPORT_METHOD(getRecordingWav:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
537
+ {
538
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
539
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
540
+ if (instance) {
541
+ NSString *recWavPath = [instance getRecordingWav];
542
+ resolve(recWavPath);
543
+ } else {
544
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
545
+ }
546
+ }
547
+
548
+ RCT_EXPORT_METHOD(getVoiceProps:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
549
+ {
550
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
551
+ KeyWordsDetection *instance = wrapper.keyWordsDetection;
552
+ if (instance) {
553
+ @try {
554
+ NSDictionary *voiceProps = [instance getVoiceProps];
555
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
556
+ result[@"error"] = voiceProps[@"error"] ?: @"No Error";
557
+ result[@"voiceProbability"] = @([voiceProps[@"voiceProbability"] floatValue]);
558
+ result[@"lastTimeHumanVoiceHeard"] = @([voiceProps[@"lastTimeHumanVoiceHeard"] longLongValue]);
559
+ resolve(result);
560
+ } @catch (NSException *exception) {
561
+ reject(@"VoicePropsError", [NSString stringWithFormat:@"Failed to get voice properties: %@", exception.reason], nil);
562
+ }
563
+ } else {
564
+ reject(@"InstanceNotFound", [NSString stringWithFormat:@"No instance found with ID: %@", instanceId], nil);
565
+ }
566
+ }
567
+
568
+ // Start/stop silent VAD (iOS only)
569
+ RCT_EXPORT_METHOD(startSilentVADDetection:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
570
+ {
571
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
572
+ if (wrapper && wrapper.keyWordsDetection) {
573
+ BOOL success = [wrapper.keyWordsDetection startSilentListening];
574
+ success ? resolve(@"Started silent VAD detection") :
575
+ reject(@"StartError", @"Failed to start silent VAD detection", nil);
576
+ } else {
577
+ reject(@"InstanceNotFound", @"No instance found", nil);
578
+ }
579
+ }
580
+
581
+ RCT_EXPORT_METHOD(stopSilentVADDetection:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
582
+ {
583
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
584
+ if (wrapper && wrapper.keyWordsDetection) {
585
+ [wrapper.keyWordsDetection stopSilentListening];
586
+ resolve(@"Stopped silent VAD detection");
587
+ } else {
588
+ reject(@"InstanceNotFound", @"No instance found", nil);
589
+ }
590
+ }
591
+
592
+ // Start/stop explicit VAD
593
+ RCT_EXPORT_METHOD(startVADDetection:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
594
+ {
595
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
596
+ if (wrapper && wrapper.keyWordsDetection) {
597
+ BOOL success = [wrapper.keyWordsDetection startVADListening];
598
+ success ? resolve(@"Started VAD detection") :
599
+ reject(@"StartError", @"Failed to start VAD detection", nil);
600
+ } else {
601
+ reject(@"InstanceNotFound", @"No instance found", nil);
602
+ }
603
+ }
604
+
605
+ RCT_EXPORT_METHOD(stopVADDetection:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
606
+ {
607
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
608
+ if (wrapper && wrapper.keyWordsDetection) {
609
+ [wrapper.keyWordsDetection stopVADListening];
610
+ resolve(@"Stopped VAD detection");
611
+ } else {
612
+ reject(@"InstanceNotFound", @"No instance found", nil);
613
+ }
614
+ }
615
+
616
+ RCT_EXPORT_METHOD(setVADParams:(NSString *)instanceId
617
+ threshold:(float)threshold
618
+ msWindow:(NSInteger)msWindow
619
+ resolver:(RCTPromiseResolveBlock)resolve
620
+ rejecter:(RCTPromiseRejectBlock)reject)
621
+ {
622
+ KeyWordsDetectionWrapper *wrapper = self.instances[instanceId];
623
+ if (wrapper && wrapper.keyWordsDetection) {
624
+ NSError *err = nil;
625
+ BOOL ok = [wrapper.keyWordsDetection setVADParamsWithThreshold:threshold
626
+ msWindow:msWindow
627
+ error:&err];
628
+ if (!ok || err) {
629
+ reject(@"VADParamsError",
630
+ err ? err.localizedDescription : @"Failed to set VAD params",
631
+ err);
632
+ } else {
633
+ resolve(@"VAD params updated");
634
+ }
635
+ } else {
636
+ reject(@"InstanceNotFound", @"No instance found", nil);
637
+ }
638
+ }
639
+
640
+ // You can add more methods here as needed, ensuring they use the instanceId
641
+
642
+ @end