react-native-wakeword 1.1.56 → 1.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/KeyWordRNBridge.podspec +1 -1
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
- package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar +0 -0
- package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.md5 +1 -1
- package/android/libs/com/davoice/keyworddetection/1.0.0/keyworddetection-1.0.0.aar.sha1 +1 -1
- package/android/src/main/java/com/davoice/keywordspotting/KeyWordRNBridge.java +764 -85
- package/android/src/main/java/com/davoice/speakeridrn/SpeakerIdRNBridge.java_not_used_yet +588 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +56 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Info.plist +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/KeyWordDetection +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.abi.json +8970 -2462
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.private.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +112 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Info.plist +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/KeyWordDetection +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.abi.json +8970 -2462
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json +8970 -2462
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +133 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/_CodeSignature/CodeResources +34 -34
- package/ios/KeyWordRNBridge/KeyWordRNBridge.m +704 -3
- package/package.json +1 -1
- package/wakewords/SpeakerVerificationRNBridge.d.ts +49 -0
- package/wakewords/SpeakerVerificationRNBridge.js +294 -0
- package/wakewords/index.d.ts +18 -10
- package/wakewords/index.js +28 -0
- package/android/src/main/assets/coca_cola_model_28_05052025.dm +0 -0
- package/wakewords/index.d.ts.chat_idiot +0 -2
|
@@ -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"
|
|
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(@"
|
|
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
|