react-native-wakeword-sid 1.1.55 → 1.1.56
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/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +45 -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 +8485 -2271
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios.private.swiftinterface +130 -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 +130 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Headers/KeyWordDetection-Swift.h +90 -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 +8485 -2271
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +130 -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 +130 -0
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.abi.json +8485 -2271
- package/ios/KeyWordRNBridge/KeyWordDetection.xcframework/ios-arm64_x86_64-simulator/KeyWordDetection.framework/Modules/KeyWordDetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +130 -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 +130 -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 +33 -33
- package/ios/KeyWordRNBridge/KeyWordRNBridge.m +332 -2
- package/package.json +1 -1
- package/wakewords/SpeakerVerificationRNBridge.d.ts +17 -0
- package/wakewords/SpeakerVerificationRNBridge.js +134 -19
- package/wakewords/index.d.ts +7 -1
- package/wakewords/index.js +16 -0
|
@@ -104,6 +104,46 @@
|
|
|
104
104
|
@implementation SVVerifierHolder
|
|
105
105
|
@end
|
|
106
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
|
+
|
|
107
147
|
// Resolve a file path from:
|
|
108
148
|
// 1) absolute path (exists)
|
|
109
149
|
// 2) main bundle resource "name.ext"
|
|
@@ -175,6 +215,44 @@ static Class SVFacadeClass(void) {
|
|
|
175
215
|
return NSClassFromString(@"SpeakerVerificationRNFacade");
|
|
176
216
|
}
|
|
177
217
|
|
|
218
|
+
|
|
219
|
+
// =========================
|
|
220
|
+
// MARK: - Mic controller dynamic bridge
|
|
221
|
+
// =========================
|
|
222
|
+
|
|
223
|
+
static id _Nullable SVCreateMicController(NSString *configJson, NSError **error) {
|
|
224
|
+
Class c = SVFacadeClass();
|
|
225
|
+
if (!c) {
|
|
226
|
+
if (error) *error = [NSError errorWithDomain:@"SV" code:10 userInfo:@{NSLocalizedDescriptionKey: @"Swift class SpeakerVerificationRNFacade not found"}];
|
|
227
|
+
return nil;
|
|
228
|
+
}
|
|
229
|
+
SEL sel = NSSelectorFromString(@"createMicControllerWithConfigJson:error:");
|
|
230
|
+
if (![c respondsToSelector:sel]) {
|
|
231
|
+
if (error) *error = [NSError errorWithDomain:@"SV" code:11 userInfo:@{NSLocalizedDescriptionKey: @"Missing selector createMicControllerWithConfigJson:error:"}];
|
|
232
|
+
return nil;
|
|
233
|
+
}
|
|
234
|
+
id (*msgSend)(id, SEL, NSString*, NSError**) = (void*)objc_msgSend;
|
|
235
|
+
return msgSend(c, sel, configJson, error);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static BOOL SVCallBool2(id obj, SEL sel, NSString *s1, NSInteger i1, BOOL b1, NSError **error) {
|
|
239
|
+
BOOL (*msgSend)(id, SEL, NSString*, NSInteger, BOOL, NSError**) = (void*)objc_msgSend;
|
|
240
|
+
return msgSend(obj, sel, s1, i1, b1, error);
|
|
241
|
+
}
|
|
242
|
+
static BOOL SVCallBool1(id obj, SEL sel, NSError **error) {
|
|
243
|
+
BOOL (*msgSend)(id, SEL, NSError**) = (void*)objc_msgSend;
|
|
244
|
+
return msgSend(obj, sel, error);
|
|
245
|
+
}
|
|
246
|
+
static BOOL SVCallBoolReset(id obj, SEL sel, BOOL b1, NSError **error) {
|
|
247
|
+
BOOL (*msgSend)(id, SEL, BOOL, NSError**) = (void*)objc_msgSend;
|
|
248
|
+
return msgSend(obj, sel, b1, error);
|
|
249
|
+
}
|
|
250
|
+
static void SVSetDelegate(id obj, id delegateObj) {
|
|
251
|
+
void (*msgSend)(id, SEL, id) = (void*)objc_msgSend;
|
|
252
|
+
msgSend(obj, NSSelectorFromString(@"setDelegate:"), delegateObj);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
178
256
|
static id _Nullable SVCreateEngine(NSString *modelPath, NSString *jsonPath, NSDictionary *options, NSError **error) {
|
|
179
257
|
Class c = SVFacadeClass();
|
|
180
258
|
if (!c) {
|
|
@@ -209,6 +287,7 @@ static NSDictionary * _Nullable SVVerifyWav(id engine, NSString *wavPath, BOOL r
|
|
|
209
287
|
|
|
210
288
|
@property (nonatomic, strong) NSMutableDictionary *instances;
|
|
211
289
|
@property (nonatomic, strong) NSMutableDictionary *speakerVerifiers; // { engineId: SVVerifierHolder }
|
|
290
|
+
@property (nonatomic, strong) NSMutableDictionary *speakerMicControllers; // { controllerId: SVMicHolder }
|
|
212
291
|
|
|
213
292
|
@end
|
|
214
293
|
|
|
@@ -220,6 +299,7 @@ RCT_EXPORT_MODULE();
|
|
|
220
299
|
if (self = [super init]) {
|
|
221
300
|
_instances = [NSMutableDictionary new];
|
|
222
301
|
_speakerVerifiers = [NSMutableDictionary new];
|
|
302
|
+
_speakerMicControllers = [NSMutableDictionary new];
|
|
223
303
|
}
|
|
224
304
|
return self;
|
|
225
305
|
}
|
|
@@ -229,11 +309,17 @@ RCT_EXPORT_MODULE();
|
|
|
229
309
|
return YES;
|
|
230
310
|
}
|
|
231
311
|
|
|
312
|
+
// NOTE: Extend supported events with Speaker Verification mic events
|
|
232
313
|
- (NSArray<NSString *> *)supportedEvents {
|
|
233
314
|
return @[@"onKeywordDetectionEvent",
|
|
234
|
-
@"onVADDetectionEvent"
|
|
315
|
+
@"onVADDetectionEvent",
|
|
316
|
+
@"onSpeakerVerificationOnboardingProgress",
|
|
317
|
+
@"onSpeakerVerificationOnboardingDone",
|
|
318
|
+
@"onSpeakerVerificationVerifyResult",
|
|
319
|
+
@"onSpeakerVerificationError"];
|
|
235
320
|
}
|
|
236
321
|
|
|
322
|
+
|
|
237
323
|
// ============================================================
|
|
238
324
|
// MARK: - Speaker Verification (Swift) - RN APIs
|
|
239
325
|
// ============================================================
|
|
@@ -340,6 +426,250 @@ RCT_EXPORT_METHOD(destroySpeakerVerifier:(NSString *)engineId
|
|
|
340
426
|
resolve(@{ @"ok": @YES, @"engineId": engineId });
|
|
341
427
|
}
|
|
342
428
|
|
|
429
|
+
|
|
430
|
+
// ============================================================
|
|
431
|
+
// MARK: - Speaker Verification Mic Controller (Swift) - RN APIs
|
|
432
|
+
// ============================================================
|
|
433
|
+
|
|
434
|
+
// Create mic controller from config JSON (SpeakerVerificationConfig).
|
|
435
|
+
// Returns controllerId.
|
|
436
|
+
RCT_EXPORT_METHOD(createSpeakerVerificationMicController:(NSString *)controllerId
|
|
437
|
+
configJson:(NSString *)configJson
|
|
438
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
439
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
440
|
+
{
|
|
441
|
+
if (self.speakerMicControllers[controllerId]) {
|
|
442
|
+
reject(@"SVMicExists", [NSString stringWithFormat:@"Speaker mic controller already exists with ID: %@", controllerId], nil);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
447
|
+
@autoreleasepool {
|
|
448
|
+
NSError *err = nil;
|
|
449
|
+
id ctrl = SVCreateMicController(configJson, &err);
|
|
450
|
+
if (err || !ctrl) {
|
|
451
|
+
reject(@"SVMicCreateError",
|
|
452
|
+
[NSString stringWithFormat:@"Failed to create mic controller: %@", err.localizedDescription ?: @"unknown"],
|
|
453
|
+
err);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Create delegate proxy that will forward Swift callbacks -> RN events
|
|
458
|
+
SVMicDelegateProxy *proxy = [SVMicDelegateProxy new];
|
|
459
|
+
proxy.bridge = self;
|
|
460
|
+
proxy.controllerId = controllerId;
|
|
461
|
+
|
|
462
|
+
// Set delegate dynamically (no header needed)
|
|
463
|
+
SVSetDelegate(ctrl, proxy);
|
|
464
|
+
|
|
465
|
+
SVMicHolder *h = [SVMicHolder new];
|
|
466
|
+
h.controllerId = controllerId;
|
|
467
|
+
h.controller = ctrl;
|
|
468
|
+
h.delegateProxy = proxy; // keep strong ref
|
|
469
|
+
self.speakerMicControllers[controllerId] = h;
|
|
470
|
+
|
|
471
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId });
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
RCT_EXPORT_METHOD(destroySpeakerVerificationMicController:(NSString *)controllerId
|
|
477
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
478
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
479
|
+
{
|
|
480
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
481
|
+
if (!h) {
|
|
482
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Best-effort stop (no throw)
|
|
487
|
+
if (h.controller && [h.controller respondsToSelector:NSSelectorFromString(@"stop")]) {
|
|
488
|
+
void (*msgSend)(id, SEL) = (void*)objc_msgSend;
|
|
489
|
+
msgSend(h.controller, NSSelectorFromString(@"stop"));
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
[self.speakerMicControllers removeObjectForKey:controllerId];
|
|
493
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// beginOnboarding(enrollmentId, targetEmbeddingCount, reset)
|
|
497
|
+
RCT_EXPORT_METHOD(svBeginOnboarding:(NSString *)controllerId
|
|
498
|
+
enrollmentId:(NSString *)enrollmentId
|
|
499
|
+
targetEmbeddingCount:(NSInteger)targetEmbeddingCount
|
|
500
|
+
reset:(BOOL)reset
|
|
501
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
502
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
503
|
+
{
|
|
504
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
505
|
+
if (!h || !h.controller) {
|
|
506
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
511
|
+
@autoreleasepool {
|
|
512
|
+
NSError *err = nil;
|
|
513
|
+
SEL sel = NSSelectorFromString(@"beginOnboardingWithEnrollmentId:targetEmbeddingCount:reset:error:");
|
|
514
|
+
if (![h.controller respondsToSelector:sel]) {
|
|
515
|
+
reject(@"SVMicMissingSelector", @"Mic controller missing beginOnboardingWithEnrollmentId:targetEmbeddingCount:reset:error:", nil);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
BOOL ok = SVCallBool2(h.controller, sel, enrollmentId, targetEmbeddingCount, reset, &err);
|
|
519
|
+
if (!ok || err) {
|
|
520
|
+
reject(@"SVMicBeginError",
|
|
521
|
+
[NSString stringWithFormat:@"beginOnboarding failed: %@", err.localizedDescription ?: @"unknown"],
|
|
522
|
+
err);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId, @"enrollmentId": enrollmentId, @"target": @(targetEmbeddingCount) });
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// getNextEmbeddingFromMic(): starts mic, collects ONE embedding, then stops mic.
|
|
531
|
+
// Result is delivered via event:
|
|
532
|
+
// onSpeakerVerificationOnboardingProgress / onSpeakerVerificationOnboardingDone
|
|
533
|
+
RCT_EXPORT_METHOD(svGetNextEmbeddingFromMic:(NSString *)controllerId
|
|
534
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
535
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
536
|
+
{
|
|
537
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
538
|
+
if (!h || !h.controller) {
|
|
539
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
544
|
+
@autoreleasepool {
|
|
545
|
+
NSError *err = nil;
|
|
546
|
+
SEL sel = NSSelectorFromString(@"getNextEmbeddingFromMicAndReturnError:");
|
|
547
|
+
if (![h.controller respondsToSelector:sel]) {
|
|
548
|
+
reject(@"SVMicMissingSelector", @"Mic controller missing getNextEmbeddingFromMicAndReturnError:", nil);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
BOOL ok = SVCallBool1(h.controller, sel, &err);
|
|
552
|
+
if (!ok || err) {
|
|
553
|
+
reject(@"SVMicGetNextError",
|
|
554
|
+
[NSString stringWithFormat:@"getNextEmbeddingFromMic failed: %@", err.localizedDescription ?: @"unknown"],
|
|
555
|
+
err);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId });
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// finalizeOnboardingNow(): forces finalize and emits onSpeakerVerificationOnboardingDone (from Swift delegate)
|
|
564
|
+
RCT_EXPORT_METHOD(svFinalizeOnboardingNow:(NSString *)controllerId
|
|
565
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
566
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
567
|
+
{
|
|
568
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
569
|
+
if (!h || !h.controller) {
|
|
570
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
574
|
+
@autoreleasepool {
|
|
575
|
+
NSError *err = nil;
|
|
576
|
+
SEL sel = NSSelectorFromString(@"finalizeOnboardingNowAndReturnError:");
|
|
577
|
+
if (![h.controller respondsToSelector:sel]) {
|
|
578
|
+
reject(@"SVMicMissingSelector", @"Mic controller missing finalizeOnboardingNowAndReturnError:", nil);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
BOOL ok = SVCallBool1(h.controller, sel, &err);
|
|
582
|
+
if (!ok || err) {
|
|
583
|
+
reject(@"SVMicFinalizeError",
|
|
584
|
+
[NSString stringWithFormat:@"finalizeOnboardingNow failed: %@", err.localizedDescription ?: @"unknown"],
|
|
585
|
+
err);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId });
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// setEnrollmentJson(enrollmentJson): sets enrollment for verification mode
|
|
594
|
+
RCT_EXPORT_METHOD(svSetEnrollmentJson:(NSString *)controllerId
|
|
595
|
+
enrollmentJson:(NSString *)enrollmentJson
|
|
596
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
597
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
598
|
+
{
|
|
599
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
600
|
+
if (!h || !h.controller) {
|
|
601
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
605
|
+
@autoreleasepool {
|
|
606
|
+
NSError *err = nil;
|
|
607
|
+
SEL sel = NSSelectorFromString(@"setEnrollmentJson:error:");
|
|
608
|
+
if (![h.controller respondsToSelector:sel]) {
|
|
609
|
+
reject(@"SVMicMissingSelector", @"Mic controller missing setEnrollmentJson:error:", nil);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
BOOL (*msgSend)(id, SEL, NSString*, NSError**) = (void*)objc_msgSend;
|
|
613
|
+
BOOL ok = msgSend(h.controller, sel, enrollmentJson, &err);
|
|
614
|
+
if (!ok || err) {
|
|
615
|
+
reject(@"SVMicSetEnrollError",
|
|
616
|
+
[NSString stringWithFormat:@"setEnrollmentJson failed: %@", err.localizedDescription ?: @"unknown"],
|
|
617
|
+
err);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId });
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// startVerifyFromMic(resetState): starts mic until a verification result is produced.
|
|
626
|
+
// Result delivered via event: onSpeakerVerificationVerifyResult
|
|
627
|
+
RCT_EXPORT_METHOD(svStartVerifyFromMic:(NSString *)controllerId
|
|
628
|
+
resetState:(BOOL)resetState
|
|
629
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
630
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
631
|
+
{
|
|
632
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
633
|
+
if (!h || !h.controller) {
|
|
634
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
638
|
+
@autoreleasepool {
|
|
639
|
+
NSError *err = nil;
|
|
640
|
+
SEL sel = NSSelectorFromString(@"startVerifyFromMicWithResetState:error:");
|
|
641
|
+
if (![h.controller respondsToSelector:sel]) {
|
|
642
|
+
reject(@"SVMicMissingSelector", @"Mic controller missing startVerifyFromMicWithResetState:error:", nil);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
BOOL ok = SVCallBoolReset(h.controller, sel, resetState, &err);
|
|
646
|
+
if (!ok || err) {
|
|
647
|
+
reject(@"SVMicStartVerifyError",
|
|
648
|
+
[NSString stringWithFormat:@"startVerifyFromMic failed: %@", err.localizedDescription ?: @"unknown"],
|
|
649
|
+
err);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId, @"resetState": @(resetState) });
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// stop(): stop mic immediately
|
|
658
|
+
RCT_EXPORT_METHOD(svStopMic:(NSString *)controllerId
|
|
659
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
660
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
661
|
+
{
|
|
662
|
+
SVMicHolder *h = self.speakerMicControllers[controllerId];
|
|
663
|
+
if (!h || !h.controller) {
|
|
664
|
+
reject(@"SVMicNotFound", [NSString stringWithFormat:@"No speaker mic controller with ID: %@", controllerId], nil);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if ([h.controller respondsToSelector:NSSelectorFromString(@"stop")]) {
|
|
668
|
+
void (*msgSend)(id, SEL) = (void*)objc_msgSend;
|
|
669
|
+
msgSend(h.controller, NSSelectorFromString(@"stop"));
|
|
670
|
+
}
|
|
671
|
+
resolve(@{ @"ok": @YES, @"controllerId": controllerId });
|
|
672
|
+
}
|
|
343
673
|
|
|
344
674
|
RCT_EXPORT_METHOD(createInstanceMulti:(NSString *)instanceId
|
|
345
675
|
modelPaths:(NSArray<NSString *> *)modelPaths
|
|
@@ -411,7 +741,7 @@ RCT_EXPORT_METHOD(setAudioRoutingConfig:(NSString *)jsonConfig
|
|
|
411
741
|
RCT_EXPORT_METHOD(disableDucking:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
412
742
|
{
|
|
413
743
|
[AudioSessionAndDuckingManager.shared disableDucking];
|
|
414
|
-
resolve(@"
|
|
744
|
+
resolve(@"disabled");
|
|
415
745
|
}
|
|
416
746
|
|
|
417
747
|
RCT_EXPORT_METHOD(initAudioSessAndDuckManage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
package/package.json
CHANGED
|
@@ -30,3 +30,20 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
30
30
|
destroy(): Promise<any>;
|
|
31
31
|
}
|
|
32
32
|
export function createSpeakerVerificationInstance(engineId: any): Promise<SpeakerVerificationRNBridgeInstance>;
|
|
33
|
+
export class SpeakerVerificationMicController {
|
|
34
|
+
constructor(controllerId: any);
|
|
35
|
+
controllerId: any;
|
|
36
|
+
create(configJson: any): Promise<any>;
|
|
37
|
+
beginOnboarding(enrollmentId: any, targetEmbeddingCount: any, reset?: boolean): Promise<any>;
|
|
38
|
+
getNextEmbeddingFromMic(): Promise<any>;
|
|
39
|
+
finalizeOnboardingNow(): Promise<any>;
|
|
40
|
+
setEnrollmentJson(enrollmentJson: any): Promise<any>;
|
|
41
|
+
startVerifyFromMic(resetState?: boolean): Promise<any>;
|
|
42
|
+
stop(): Promise<any>;
|
|
43
|
+
destroy(): Promise<any>;
|
|
44
|
+
}
|
|
45
|
+
export function createSpeakerVerificationMicController(controllerId: any): Promise<SpeakerVerificationMicController>;
|
|
46
|
+
export function onSpeakerVerificationOnboardingProgress(cb: any): () => void;
|
|
47
|
+
export function onSpeakerVerificationOnboardingDone(cb: any): () => void;
|
|
48
|
+
export function onSpeakerVerificationVerifyResult(cb: any): () => void;
|
|
49
|
+
export function onSpeakerVerificationError(cb: any): () => void;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// wakewords/SpeakerVerificationRNBridge.js
|
|
2
|
-
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
3
3
|
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
|
4
4
|
|
|
5
5
|
// Keep using the SAME native module (KeyWordRNBridge.m already exports these methods)
|
|
6
6
|
const { KeyWordRNBridge } = NativeModules;
|
|
7
|
+
const emitter = KeyWordRNBridge ? new NativeEventEmitter(KeyWordRNBridge) : null;
|
|
7
8
|
|
|
8
9
|
function stripFileScheme(uri) {
|
|
9
10
|
if (!uri) return uri;
|
|
@@ -35,6 +36,18 @@ function normalizePathOrName(input) {
|
|
|
35
36
|
return String(input);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
function assertIOS() {
|
|
40
|
+
if (Platform.OS !== 'ios') {
|
|
41
|
+
throw new Error('Speaker verification bridge is currently implemented for iOS only.');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function assertMethod(name) {
|
|
46
|
+
if (!KeyWordRNBridge?.[name]) {
|
|
47
|
+
throw new Error(`KeyWordRNBridge.${name} is not available (native not linked / iOS only?)`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
export class SpeakerVerificationRNBridgeInstance {
|
|
39
52
|
engineId;
|
|
40
53
|
|
|
@@ -57,13 +70,8 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
57
70
|
* { decisionThreshold, frameSize, tailSeconds, maxTailSeconds, cmn, expectedLayoutBDT, logLevel }
|
|
58
71
|
*/
|
|
59
72
|
async create(modelPathOrName, enrollmentJsonPathOrName, options = {}) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (Platform.OS !== 'ios') {
|
|
65
|
-
throw new Error('Speaker verification bridge is currently implemented for iOS only.');
|
|
66
|
-
}
|
|
73
|
+
assertIOS();
|
|
74
|
+
assertMethod('createSpeakerVerifier');
|
|
67
75
|
|
|
68
76
|
const modelArg = normalizePathOrName(modelPathOrName);
|
|
69
77
|
const jsonArg = normalizePathOrName(enrollmentJsonPathOrName);
|
|
@@ -87,13 +95,8 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
87
95
|
* - if true, clears internal streaming state before verification
|
|
88
96
|
*/
|
|
89
97
|
async verifyWavStreaming(wavPathOrName, resetState = true) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (Platform.OS !== 'ios') {
|
|
95
|
-
throw new Error('Speaker verification bridge is currently implemented for iOS only.');
|
|
96
|
-
}
|
|
98
|
+
assertIOS();
|
|
99
|
+
assertMethod('verifySpeakerWavStreaming');
|
|
97
100
|
|
|
98
101
|
const wavArg = normalizePathOrName(wavPathOrName);
|
|
99
102
|
|
|
@@ -105,15 +108,12 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
async destroy() {
|
|
108
|
-
if (!KeyWordRNBridge?.destroySpeakerVerifier) {
|
|
109
|
-
throw new Error('KeyWordRNBridge.destroySpeakerVerifier is not available (native not linked / iOS only?)');
|
|
110
|
-
}
|
|
111
111
|
|
|
112
112
|
if (Platform.OS !== 'ios') {
|
|
113
113
|
// no-op for now
|
|
114
114
|
return { ok: true, engineId: this.engineId, noop: true };
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
assertMethod('destroySpeakerVerifier');
|
|
117
117
|
return await KeyWordRNBridge.destroySpeakerVerifier(this.engineId);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -122,3 +122,118 @@ export class SpeakerVerificationRNBridgeInstance {
|
|
|
122
122
|
export const createSpeakerVerificationInstance = async (engineId) => {
|
|
123
123
|
return new SpeakerVerificationRNBridgeInstance(engineId);
|
|
124
124
|
};
|
|
125
|
+
|
|
126
|
+
// ============================================================
|
|
127
|
+
// MARK: - Speaker Verification Mic Controller (Swift) - RN APIs
|
|
128
|
+
// ============================================================
|
|
129
|
+
export class SpeakerVerificationMicController {
|
|
130
|
+
controllerId;
|
|
131
|
+
|
|
132
|
+
constructor(controllerId) {
|
|
133
|
+
this.controllerId = controllerId;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async create(configJson) {
|
|
137
|
+
assertIOS();
|
|
138
|
+
assertMethod('createSpeakerVerificationMicController');
|
|
139
|
+
const cfg =
|
|
140
|
+
typeof configJson === 'string'
|
|
141
|
+
? configJson
|
|
142
|
+
: JSON.stringify(configJson ?? {});
|
|
143
|
+
return await KeyWordRNBridge.createSpeakerVerificationMicController(
|
|
144
|
+
this.controllerId,
|
|
145
|
+
cfg
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async beginOnboarding(enrollmentId, targetEmbeddingCount, reset = true) {
|
|
150
|
+
assertIOS();
|
|
151
|
+
assertMethod('svBeginOnboarding');
|
|
152
|
+
return await KeyWordRNBridge.svBeginOnboarding(
|
|
153
|
+
this.controllerId,
|
|
154
|
+
String(enrollmentId ?? ''),
|
|
155
|
+
Number(targetEmbeddingCount ?? 0),
|
|
156
|
+
!!reset
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getNextEmbeddingFromMic() {
|
|
161
|
+
assertIOS();
|
|
162
|
+
assertMethod('svGetNextEmbeddingFromMic');
|
|
163
|
+
return await KeyWordRNBridge.svGetNextEmbeddingFromMic(this.controllerId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async finalizeOnboardingNow() {
|
|
167
|
+
assertIOS();
|
|
168
|
+
assertMethod('svFinalizeOnboardingNow');
|
|
169
|
+
return await KeyWordRNBridge.svFinalizeOnboardingNow(this.controllerId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async setEnrollmentJson(enrollmentJson) {
|
|
173
|
+
assertIOS();
|
|
174
|
+
assertMethod('svSetEnrollmentJson');
|
|
175
|
+
// if caller passes object, stringify it (native expects JSON string)
|
|
176
|
+
const s =
|
|
177
|
+
typeof enrollmentJson === 'string'
|
|
178
|
+
? enrollmentJson
|
|
179
|
+
: JSON.stringify(enrollmentJson ?? {});
|
|
180
|
+
|
|
181
|
+
return await KeyWordRNBridge.svSetEnrollmentJson(
|
|
182
|
+
this.controllerId,
|
|
183
|
+
s
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async startVerifyFromMic(resetState = true) {
|
|
188
|
+
assertIOS();
|
|
189
|
+
assertMethod('svStartVerifyFromMic');
|
|
190
|
+
return await KeyWordRNBridge.svStartVerifyFromMic(
|
|
191
|
+
this.controllerId,
|
|
192
|
+
!!resetState
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async stop() {
|
|
197
|
+
if (Platform.OS !== 'ios') {
|
|
198
|
+
return { ok: true, controllerId: this.controllerId, noop: true };
|
|
199
|
+
}
|
|
200
|
+
assertMethod('svStopMic');
|
|
201
|
+
return await KeyWordRNBridge.svStopMic(this.controllerId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async destroy() {
|
|
205
|
+
if (Platform.OS !== 'ios') {
|
|
206
|
+
return { ok: true, controllerId: this.controllerId, noop: true };
|
|
207
|
+
}
|
|
208
|
+
assertMethod('destroySpeakerVerificationMicController');
|
|
209
|
+
return await KeyWordRNBridge.destroySpeakerVerificationMicController(this.controllerId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const createSpeakerVerificationMicController = async (controllerId) => {
|
|
214
|
+
return new SpeakerVerificationMicController(controllerId);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// ============================================================
|
|
218
|
+
// MARK: - Event helpers
|
|
219
|
+
// ============================================================
|
|
220
|
+
|
|
221
|
+
function onEvent(eventName, cb) {
|
|
222
|
+
if (!emitter) {
|
|
223
|
+
throw new Error('NativeEventEmitter unavailable: KeyWordRNBridge is not linked.');
|
|
224
|
+
}
|
|
225
|
+
const sub = emitter.addListener(eventName, cb);
|
|
226
|
+
return () => sub.remove();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export const onSpeakerVerificationOnboardingProgress = (cb) =>
|
|
230
|
+
onEvent('onSpeakerVerificationOnboardingProgress', cb);
|
|
231
|
+
|
|
232
|
+
export const onSpeakerVerificationOnboardingDone = (cb) =>
|
|
233
|
+
onEvent('onSpeakerVerificationOnboardingDone', cb);
|
|
234
|
+
|
|
235
|
+
export const onSpeakerVerificationVerifyResult = (cb) =>
|
|
236
|
+
onEvent('onSpeakerVerificationVerifyResult', cb);
|
|
237
|
+
|
|
238
|
+
export const onSpeakerVerificationError = (cb) =>
|
|
239
|
+
onEvent('onSpeakerVerificationError', cb);
|
package/wakewords/index.d.ts
CHANGED
|
@@ -9,8 +9,14 @@ import { KeyWordRNBridgeInstance } from './KeyWordRNBridge';
|
|
|
9
9
|
import { setWakewordAudioRoutingConfig } from './KeyWordRNBridge';
|
|
10
10
|
import { createSpeakerVerificationInstance } from './SpeakerVerificationRNBridge';
|
|
11
11
|
import { SpeakerVerificationRNBridgeInstance } from './SpeakerVerificationRNBridge';
|
|
12
|
+
import { createSpeakerVerificationMicController } from './SpeakerVerificationRNBridge';
|
|
13
|
+
import { SpeakerVerificationMicController } from './SpeakerVerificationRNBridge';
|
|
14
|
+
import { onSpeakerVerificationOnboardingProgress } from './SpeakerVerificationRNBridge';
|
|
15
|
+
import { onSpeakerVerificationOnboardingDone } from './SpeakerVerificationRNBridge';
|
|
16
|
+
import { onSpeakerVerificationVerifyResult } from './SpeakerVerificationRNBridge';
|
|
17
|
+
import { onSpeakerVerificationError } from './SpeakerVerificationRNBridge';
|
|
12
18
|
import useModel from './useModel';
|
|
13
|
-
export { enableDucking, disableDucking, initAudioSessAndDuckManage, restartListeningAfterDucking, removeAllRNBridgeListeners, createKeyWordRNBridgeInstance, KeyWordRNBridgeInstance, setWakewordAudioRoutingConfig, createSpeakerVerificationInstance, SpeakerVerificationRNBridgeInstance, useModel };
|
|
19
|
+
export { enableDucking, disableDucking, initAudioSessAndDuckManage, restartListeningAfterDucking, removeAllRNBridgeListeners, createKeyWordRNBridgeInstance, KeyWordRNBridgeInstance, setWakewordAudioRoutingConfig, createSpeakerVerificationInstance, SpeakerVerificationRNBridgeInstance, createSpeakerVerificationMicController, SpeakerVerificationMicController, onSpeakerVerificationOnboardingProgress, onSpeakerVerificationOnboardingDone, onSpeakerVerificationVerifyResult, onSpeakerVerificationError, useModel };
|
|
14
20
|
|
|
15
21
|
// Re-export routing types for consumers:
|
|
16
22
|
export type { AudioRoutingConfig, RouteConfigEntry } from './audioRoutingConfig';
|
package/wakewords/index.js
CHANGED
|
@@ -16,6 +16,12 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
createSpeakerVerificationInstance,
|
|
18
18
|
SpeakerVerificationRNBridgeInstance,
|
|
19
|
+
createSpeakerVerificationMicController,
|
|
20
|
+
SpeakerVerificationMicController,
|
|
21
|
+
onSpeakerVerificationOnboardingProgress,
|
|
22
|
+
onSpeakerVerificationOnboardingDone,
|
|
23
|
+
onSpeakerVerificationVerifyResult,
|
|
24
|
+
onSpeakerVerificationError,
|
|
19
25
|
} from './SpeakerVerificationRNBridge';
|
|
20
26
|
|
|
21
27
|
export { enableDucking }
|
|
@@ -30,5 +36,15 @@ export { setWakewordAudioRoutingConfig } // ← NEW
|
|
|
30
36
|
export { createSpeakerVerificationInstance }; // ✅ NEW
|
|
31
37
|
export { SpeakerVerificationRNBridgeInstance }; // ✅ NEW
|
|
32
38
|
|
|
39
|
+
// ✅ NEW speaker verification mic controller exports
|
|
40
|
+
export { createSpeakerVerificationMicController };
|
|
41
|
+
export { SpeakerVerificationMicController };
|
|
42
|
+
export {
|
|
43
|
+
onSpeakerVerificationOnboardingProgress,
|
|
44
|
+
onSpeakerVerificationOnboardingDone,
|
|
45
|
+
onSpeakerVerificationVerifyResult,
|
|
46
|
+
onSpeakerVerificationError,
|
|
47
|
+
};
|
|
48
|
+
|
|
33
49
|
export { useModel }; // Export only useModel
|
|
34
50
|
export default useModel; // Allow default import
|