react-native-voice-ts 1.0.0

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 (51) hide show
  1. package/.nvmrc +1 -0
  2. package/.prettierrc +5 -0
  3. package/.releaserc +15 -0
  4. package/CONTRIBUTING.md +293 -0
  5. package/LICENSE +21 -0
  6. package/MIGRATION_SUMMARY.md +510 -0
  7. package/README.md +576 -0
  8. package/android/build.gradle +126 -0
  9. package/android/gradle.properties +5 -0
  10. package/android/src/main/AndroidManifest.xml +8 -0
  11. package/android/src/main/VoiceSpec.kt +55 -0
  12. package/android/src/main/java/com/wenkesj/voice/Voice.kt +343 -0
  13. package/android/src/main/java/com/wenkesj/voice/VoiceModule.kt +63 -0
  14. package/android/src/main/java/com/wenkesj/voice/VoicePackage.kt +35 -0
  15. package/android/src/newarch/VoiceSpec.kt +55 -0
  16. package/android/src/oldarch/VoiceSpec.kt +30 -0
  17. package/app.plugin.js +1 -0
  18. package/dist/NativeVoiceAndroid.d.ts +22 -0
  19. package/dist/NativeVoiceAndroid.d.ts.map +1 -0
  20. package/dist/NativeVoiceAndroid.js +3 -0
  21. package/dist/NativeVoiceAndroid.js.map +1 -0
  22. package/dist/NativeVoiceIOS.d.ts +18 -0
  23. package/dist/NativeVoiceIOS.d.ts.map +1 -0
  24. package/dist/NativeVoiceIOS.js +3 -0
  25. package/dist/NativeVoiceIOS.js.map +1 -0
  26. package/dist/VoiceModuleTypes.d.ts +54 -0
  27. package/dist/VoiceModuleTypes.d.ts.map +1 -0
  28. package/dist/VoiceModuleTypes.js +2 -0
  29. package/dist/VoiceModuleTypes.js.map +1 -0
  30. package/dist/VoiceUtilTypes.d.ts +43 -0
  31. package/dist/VoiceUtilTypes.d.ts.map +1 -0
  32. package/dist/VoiceUtilTypes.js +8 -0
  33. package/dist/VoiceUtilTypes.js.map +1 -0
  34. package/dist/index.d.ts +72 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +395 -0
  37. package/dist/index.js.map +1 -0
  38. package/ios/Voice/Voice.h +14 -0
  39. package/ios/Voice/Voice.mm +672 -0
  40. package/ios/Voice.xcodeproj/project.pbxproj +272 -0
  41. package/ios/Voice.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  42. package/package.json +101 -0
  43. package/plugin/build/withVoice.d.ts +13 -0
  44. package/plugin/build/withVoice.js +47 -0
  45. package/plugin/tsconfig.tsbuildinfo +1 -0
  46. package/react-native-voice.podspec +46 -0
  47. package/src/NativeVoiceAndroid.ts +28 -0
  48. package/src/NativeVoiceIOS.ts +24 -0
  49. package/src/VoiceModuleTypes.ts +64 -0
  50. package/src/VoiceUtilTypes.ts +46 -0
  51. package/src/index.ts +500 -0
@@ -0,0 +1,672 @@
1
+ #import "Voice.h"
2
+ #import <Accelerate/Accelerate.h>
3
+ #import <React/RCTLog.h>
4
+ #import <React/RCTUtils.h>
5
+ #import <Speech/Speech.h>
6
+ #import <UIKit/UIKit.h>
7
+
8
+ @interface Voice () <SFSpeechRecognizerDelegate>
9
+
10
+ @property(nonatomic) SFSpeechRecognizer *speechRecognizer;
11
+ @property(nonatomic) SFSpeechURLRecognitionRequest *recognitionUrlRequest;
12
+ @property(nonatomic) SFSpeechAudioBufferRecognitionRequest *recognitionRequest;
13
+ @property(nonatomic) AVAudioEngine *audioEngine;
14
+ @property(nonatomic) SFSpeechRecognitionTask *recognitionTask;
15
+ @property(nonatomic) AVAudioSession *audioSession;
16
+ /** Whether speech recognition is finishing.. */
17
+ @property(nonatomic) BOOL isTearingDown;
18
+ @property(nonatomic) BOOL continuous;
19
+
20
+ @property(nonatomic) NSString *sessionId;
21
+ /** Previous category the user was on prior to starting speech recognition */
22
+ @property(nonatomic) NSString *priorAudioCategory;
23
+ /** Volume level Metering*/
24
+ @property float averagePowerForChannel0;
25
+ @property float averagePowerForChannel1;
26
+
27
+ @end
28
+
29
+ @implementation Voice {
30
+ }
31
+
32
+
33
+
34
+ ///** Returns "YES" if no errors had occurred */
35
+ - (BOOL)setupAudioSession {
36
+ if ([self isHeadsetPluggedIn] || [self isHeadSetBluetooth]) {
37
+ [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
38
+ withOptions:AVAudioSessionCategoryOptionAllowBluetooth
39
+ error:nil];
40
+ } else {
41
+ [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
42
+ withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
43
+ error:nil];
44
+ }
45
+
46
+ NSError *audioSessionError = nil;
47
+
48
+ // Activate the audio session
49
+ [self.audioSession
50
+ setActive:YES
51
+ withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
52
+ error:&audioSessionError];
53
+
54
+ if (audioSessionError != nil) {
55
+ [self sendResult:@{
56
+ @"code" : @"audio",
57
+ @"message" : [audioSessionError localizedDescription]
58
+ }:nil:nil:nil];
59
+ return NO;
60
+ }
61
+
62
+ [[NSNotificationCenter defaultCenter]
63
+ addObserver:self
64
+ selector:@selector(teardown)
65
+ name:RCTBridgeWillReloadNotification
66
+ object:nil];
67
+
68
+ return YES;
69
+ }
70
+
71
+ - (BOOL)isHeadsetPluggedIn {
72
+ AVAudioSessionRouteDescription *route =
73
+ [[AVAudioSession sharedInstance] currentRoute];
74
+ for (AVAudioSessionPortDescription *desc in [route outputs]) {
75
+ if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones] ||
76
+ [[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP])
77
+ return YES;
78
+ }
79
+ return NO;
80
+ }
81
+
82
+ - (BOOL)isHeadSetBluetooth {
83
+ NSArray *arrayInputs = [[AVAudioSession sharedInstance] availableInputs];
84
+ for (AVAudioSessionPortDescription *port in arrayInputs) {
85
+ if ([port.portType isEqualToString:AVAudioSessionPortBluetoothHFP]) {
86
+ return YES;
87
+ }
88
+ }
89
+ return NO;
90
+ }
91
+
92
+ - (void)teardown {
93
+ self.isTearingDown = YES;
94
+ [self.recognitionTask cancel];
95
+ self.recognitionTask = nil;
96
+
97
+ // Set back audio session category
98
+ [self resetAudioSession];
99
+
100
+ // End recognition request
101
+ [self.recognitionRequest endAudio];
102
+
103
+ // Remove tap on bus
104
+ [self.audioEngine.inputNode removeTapOnBus:0];
105
+ [self.audioEngine.inputNode reset];
106
+
107
+ // Stop audio engine and dereference it for re-allocation
108
+ if (self.audioEngine.isRunning) {
109
+ [self.audioEngine stop];
110
+ [self.audioEngine reset];
111
+ self.audioEngine = nil;
112
+ }
113
+
114
+ self.recognitionRequest = nil;
115
+ self.recognitionUrlRequest = nil;
116
+ self.sessionId = nil;
117
+ self.isTearingDown = NO;
118
+ }
119
+
120
+ - (void)resetAudioSession {
121
+ if (self.audioSession == nil) {
122
+ self.audioSession = [AVAudioSession sharedInstance];
123
+ }
124
+ // Set audio session to inactive and notify other sessions
125
+ // [self.audioSession setActive:NO
126
+ // withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:
127
+ // nil];
128
+ NSString *audioCategory = [self.audioSession category];
129
+ // Category hasn't changed -- do nothing
130
+ if ([self.priorAudioCategory isEqualToString:audioCategory])
131
+ return;
132
+ // Reset back to the previous category
133
+ if ([self isHeadsetPluggedIn] || [self isHeadSetBluetooth]) {
134
+ [self.audioSession setCategory:self.priorAudioCategory
135
+ withOptions:AVAudioSessionCategoryOptionAllowBluetooth
136
+ error:nil];
137
+ } else {
138
+ [self.audioSession setCategory:self.priorAudioCategory
139
+ withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
140
+ error:nil];
141
+ }
142
+ // Remove pointer reference
143
+ self.audioSession = nil;
144
+ }
145
+
146
+ - (void)setupAndTranscribeFile:(NSString *)filePath
147
+ withLocaleStr:(NSString *)localeStr {
148
+
149
+ // Tear down resources before starting speech recognition..
150
+ [self teardown];
151
+
152
+ self.sessionId = [[NSUUID UUID] UUIDString];
153
+
154
+ NSLocale *locale = nil;
155
+ if ([localeStr length] > 0) {
156
+ locale = [NSLocale localeWithLocaleIdentifier:localeStr];
157
+ }
158
+
159
+ if (locale) {
160
+ self.speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale];
161
+ } else {
162
+ self.speechRecognizer = [[SFSpeechRecognizer alloc] init];
163
+ }
164
+
165
+ self.speechRecognizer.delegate = self;
166
+
167
+ [self sendEventWithName:@"onTranscriptionError"
168
+ body:@{
169
+ @"error" :
170
+ @{@"code" : @"fake_error", @"message" : filePath}
171
+ }];
172
+ // Set up recognition request
173
+ self.recognitionUrlRequest = [[SFSpeechURLRecognitionRequest alloc]
174
+ initWithURL:[NSURL fileURLWithPath:filePath]];
175
+
176
+ if (self.recognitionUrlRequest == nil) {
177
+ [self sendEventWithName:@"onTranscriptionError"
178
+ body:@{@"error" : @{@"code" : @"recognition_url_init"}}];
179
+ [self teardown];
180
+ return;
181
+ }
182
+
183
+ @try {
184
+
185
+ [self sendEventWithName:@"onTranscriptionStart" body:nil];
186
+
187
+ // Set up recognition task
188
+ // A recognition task represents a speech recognition session.
189
+ // We keep a reference to the task so that it can be cancelled.
190
+ NSString *taskSessionId = self.sessionId;
191
+ self.recognitionTask = [self.speechRecognizer
192
+ recognitionTaskWithRequest:self.recognitionUrlRequest
193
+ resultHandler:^(
194
+ SFSpeechRecognitionResult *_Nullable result,
195
+ NSError *_Nullable error) {
196
+ if (![taskSessionId isEqualToString:self.sessionId]) {
197
+ // session ID has changed, so ignore any capture
198
+ // results and error
199
+ [self teardown];
200
+ return;
201
+ }
202
+ if (error != nil) {
203
+ NSString *errorMessage = [NSString
204
+ stringWithFormat:@"%ld/%@", error.code,
205
+ [error localizedDescription]];
206
+
207
+ [self sendEventWithName:@"onTranscriptionError"
208
+ body:@{
209
+ @"error" : @{
210
+ @"code" : @"recognition_fail_o",
211
+ @"message" : errorMessage,
212
+ @"filePath" : filePath
213
+ }
214
+ }];
215
+ [self teardown];
216
+ return;
217
+ }
218
+ // No result.
219
+ if (result == nil) {
220
+ [self sendEventWithName:@"onTranscriptionEnd"
221
+ body:nil];
222
+ [self teardown];
223
+ return;
224
+ }
225
+
226
+ BOOL isFinal = result.isFinal;
227
+
228
+ if (isFinal) {
229
+ NSMutableArray *transcriptionSegs =
230
+ [NSMutableArray new];
231
+ for (SFTranscriptionSegment *segment in result
232
+ .bestTranscription.segments) {
233
+ [transcriptionSegs addObject:@{
234
+ @"transcription" : segment.substring,
235
+ @"timestamp" : @(segment.timestamp),
236
+ @"duration" : @(segment.duration)
237
+ }];
238
+ }
239
+
240
+ [self sendEventWithName:@"onTranscriptionResults"
241
+ body:@{
242
+ @"segments" : transcriptionSegs,
243
+ @"transcription" :
244
+ result.bestTranscription
245
+ .formattedString,
246
+ @"isFinal" : @(isFinal)
247
+ }];
248
+ }
249
+
250
+ if (isFinal || self.recognitionTask.isCancelled ||
251
+ self.recognitionTask.isFinishing) {
252
+ [self sendEventWithName:@"onTranscriptionEnd"
253
+ body:nil];
254
+ [self teardown];
255
+ return;
256
+ }
257
+ }];
258
+ } @catch (NSException *exception) {
259
+ [self sendEventWithName:@"onTranscriptionError"
260
+ body:@{
261
+ @"error" : @{
262
+ @"code" : @"start_transcription_fail",
263
+ @"message" : [exception reason]
264
+ }
265
+ }];
266
+ [self teardown];
267
+
268
+ return;
269
+ } @finally {
270
+ }
271
+ }
272
+
273
+ - (void)setupAndStartRecognizing:(NSString *)localeStr {
274
+ self.audioSession = [AVAudioSession sharedInstance];
275
+ self.priorAudioCategory = [self.audioSession category];
276
+ // Tear down resources before starting speech recognition..
277
+ [self teardown];
278
+
279
+ self.sessionId = [[NSUUID UUID] UUIDString];
280
+
281
+ NSLocale *locale = nil;
282
+ if ([localeStr length] > 0) {
283
+ locale = [NSLocale localeWithLocaleIdentifier:localeStr];
284
+ }
285
+
286
+ if (locale) {
287
+ self.speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale];
288
+ } else {
289
+ self.speechRecognizer = [[SFSpeechRecognizer alloc] init];
290
+ }
291
+
292
+ self.speechRecognizer.delegate = self;
293
+
294
+ // Start audio session...
295
+ if (![self setupAudioSession]) {
296
+ [self teardown];
297
+ return;
298
+ }
299
+
300
+ self.recognitionRequest =
301
+ [[SFSpeechAudioBufferRecognitionRequest alloc] init];
302
+ // Configure request so that results are returned before audio
303
+ // recording is finished
304
+ self.recognitionRequest.shouldReportPartialResults = YES;
305
+
306
+ if (self.recognitionRequest == nil) {
307
+ [self sendResult:@{@"code" : @"recognition_init"}:nil:nil:nil];
308
+ [self teardown];
309
+ return;
310
+ }
311
+
312
+ if (self.audioEngine == nil) {
313
+ self.audioEngine = [[AVAudioEngine alloc] init];
314
+ }
315
+
316
+ @try {
317
+ AVAudioInputNode *inputNode = self.audioEngine.inputNode;
318
+ if (inputNode == nil) {
319
+ [self sendResult:@{@"code" : @"input"}:nil:nil:nil];
320
+ [self teardown];
321
+ return;
322
+ }
323
+
324
+ [self sendEventWithName:@"onSpeechStart" body:nil];
325
+
326
+ // A recognition task represents a speech recognition session.
327
+ // We keep a reference to the task so that it can be cancelled.
328
+ NSString *taskSessionId = self.sessionId;
329
+ self.recognitionTask = [self.speechRecognizer
330
+ recognitionTaskWithRequest:self.recognitionRequest
331
+ resultHandler:^(
332
+ SFSpeechRecognitionResult *_Nullable result,
333
+ NSError *_Nullable error) {
334
+ if (![taskSessionId isEqualToString:self.sessionId]) {
335
+ // session ID has changed, so ignore any
336
+ // capture results and error
337
+ [self teardown];
338
+ return;
339
+ }
340
+ if (error != nil) {
341
+ NSString *errorMessage = [NSString
342
+ stringWithFormat:@"%ld/%@", error.code,
343
+ [error localizedDescription]];
344
+ [self sendResult:@{
345
+ @"code" : @"recognition_fail_ooo",
346
+ @"message" : errorMessage
347
+ }:nil:nil:nil];
348
+ [self teardown];
349
+ return;
350
+ }
351
+
352
+ // No result.
353
+ if (result == nil) {
354
+ [self sendEventWithName:@"onSpeechEnd" body:nil];
355
+ [self teardown];
356
+ return;
357
+ }
358
+
359
+ BOOL isFinal = result.isFinal;
360
+
361
+ NSMutableArray *transcriptionDics = [NSMutableArray new];
362
+ for (SFTranscription *transcription in result
363
+ .transcriptions) {
364
+ [transcriptionDics
365
+ addObject:transcription.formattedString];
366
+ }
367
+
368
+ [self sendResult :nil :result.bestTranscription.formattedString :transcriptionDics :[NSNumber numberWithBool:isFinal]];
369
+
370
+ if (isFinal || self.recognitionTask.isCancelled ||
371
+ self.recognitionTask.isFinishing) {
372
+ [self sendEventWithName:@"onSpeechEnd" body:nil];
373
+ if (!self.continuous) {
374
+ [self teardown];
375
+ }
376
+ return;
377
+ }
378
+ }];
379
+
380
+ AVAudioFormat *recordingFormat = [inputNode outputFormatForBus:0];
381
+ AVAudioMixerNode *mixer = [[AVAudioMixerNode alloc] init];
382
+ [self.audioEngine attachNode:mixer];
383
+
384
+ // Start recording and append recording buffer to speech recognizer
385
+ @try {
386
+ [mixer
387
+ installTapOnBus:0
388
+ bufferSize:1024
389
+ format:recordingFormat
390
+ block:^(AVAudioPCMBuffer *_Nonnull buffer,
391
+ AVAudioTime *_Nonnull when) {
392
+ // Volume Level Metering
393
+ UInt32 inNumberFrames = buffer.frameLength;
394
+ float LEVEL_LOWPASS_TRIG = 0.5;
395
+ if (buffer.format.channelCount > 0) {
396
+ Float32 *samples =
397
+ (Float32 *)buffer.floatChannelData[0];
398
+ Float32 avgValue = 0;
399
+
400
+ vDSP_maxmgv((Float32 *)samples, 1, &avgValue,
401
+ inNumberFrames);
402
+ self.averagePowerForChannel0 =
403
+ (LEVEL_LOWPASS_TRIG *
404
+ ((avgValue == 0) ? -100
405
+ : 20.0 * log10f(avgValue))) +
406
+ ((1 - LEVEL_LOWPASS_TRIG) *
407
+ self.averagePowerForChannel0);
408
+ self.averagePowerForChannel1 =
409
+ self.averagePowerForChannel0;
410
+ }
411
+
412
+ if (buffer.format.channelCount > 1) {
413
+ Float32 *samples =
414
+ (Float32 *)buffer.floatChannelData[1];
415
+ Float32 avgValue = 0;
416
+
417
+ vDSP_maxmgv((Float32 *)samples, 1, &avgValue,
418
+ inNumberFrames);
419
+ self.averagePowerForChannel1 =
420
+ (LEVEL_LOWPASS_TRIG *
421
+ ((avgValue == 0) ? -100
422
+ : 20.0 * log10f(avgValue))) +
423
+ ((1 - LEVEL_LOWPASS_TRIG) *
424
+ self.averagePowerForChannel1);
425
+ }
426
+ // Normalizing the Volume Value on scale of (0-10)
427
+ self.averagePowerForChannel1 =
428
+ [self _normalizedPowerLevelFromDecibels:
429
+ self.averagePowerForChannel1] *
430
+ 10;
431
+ NSNumber *value = [NSNumber
432
+ numberWithFloat:self.averagePowerForChannel1];
433
+ [self sendEventWithName:@"onSpeechVolumeChanged"
434
+ body:@{@"value" : value}];
435
+
436
+ // Todo: write recording buffer to file (if user
437
+ // opts in)
438
+ if (self.recognitionRequest != nil) {
439
+ [self.recognitionRequest appendAudioPCMBuffer:buffer];
440
+ }
441
+ }];
442
+ } @catch (NSException *exception) {
443
+ NSLog(@"[Error] - %@ %@", exception.name, exception.reason);
444
+ [self sendResult:@{
445
+ @"code" : @"start_recording",
446
+ @"message" : [exception reason]
447
+ }:nil:nil:nil];
448
+ [self teardown];
449
+ return;
450
+ } @finally {
451
+ }
452
+
453
+ [self.audioEngine connect:inputNode to:mixer format:recordingFormat];
454
+ [self.audioEngine prepare];
455
+ NSError *audioSessionError = nil;
456
+ [self.audioEngine startAndReturnError:&audioSessionError];
457
+ if (audioSessionError != nil) {
458
+ [self sendResult:@{
459
+ @"code" : @"audio",
460
+ @"message" : [audioSessionError localizedDescription]
461
+ }:nil:nil:nil];
462
+ [self teardown];
463
+ return;
464
+ }
465
+ } @catch (NSException *exception) {
466
+ [self sendResult:@{
467
+ @"code" : @"start_recording",
468
+ @"message" : [exception reason]
469
+ }:nil:nil:nil];
470
+ return;
471
+ }
472
+ }
473
+
474
+ - (CGFloat)_normalizedPowerLevelFromDecibels:(CGFloat)decibels {
475
+ if (decibels < -80.0f || decibels == 0.0f) {
476
+ return 0.0f;
477
+ }
478
+ CGFloat power =
479
+ powf((powf(10.0f, 0.05f * decibels) - powf(10.0f, 0.05f * -80.0f)) *
480
+ (1.0f / (1.0f - powf(10.0f, 0.05f * -80.0f))),
481
+ 1.0f / 2.0f);
482
+ if (power < 1.0f) {
483
+ return power;
484
+ } else {
485
+ return 1.0f;
486
+ }
487
+ }
488
+
489
+ - (NSArray<NSString *> *)supportedEvents {
490
+ return @[
491
+ @"onSpeechResults", @"onSpeechStart", @"onSpeechPartialResults",
492
+ @"onSpeechError", @"onSpeechEnd", @"onSpeechRecognized",
493
+ @"onSpeechVolumeChanged", @"onTranscriptionStart", @"onTranscriptionEnd",
494
+ @"onTranscriptionError", @"onTranscriptionResults"
495
+ ];
496
+ }
497
+
498
+ - (void)sendResult:(NSDictionary *)
499
+ error:(NSString *)bestTranscription
500
+ :(NSArray *)transcriptions
501
+ :(NSNumber *)isFinal {
502
+ if (error != nil) {
503
+ [self sendEventWithName:@"onSpeechError" body:@{@"error" : error}];
504
+ }
505
+ if (bestTranscription != nil) {
506
+ [self sendEventWithName:@"onSpeechResults"
507
+ body:@{@"value" : @[ bestTranscription ]}];
508
+ }
509
+ if (transcriptions != nil) {
510
+ [self sendEventWithName:@"onSpeechPartialResults"
511
+ body:@{@"value" : transcriptions}];
512
+ }
513
+ if (isFinal != nil) {
514
+ [self sendEventWithName:@"onSpeechRecognized" body:@{@"isFinal" : isFinal}];
515
+ }
516
+ }
517
+
518
+ // Called when the availability of the given recognizer changes
519
+ - (void)speechRecognizer:(SFSpeechRecognizer *)speechRecognizer
520
+ availabilityDidChange:(BOOL)available {
521
+ if (available == false) {
522
+ [self sendResult:RCTMakeError(@"Speech recognition is not available now",
523
+ nil, nil):nil:nil:nil];
524
+ }
525
+ }
526
+
527
+ RCT_EXPORT_METHOD(stopSpeech : (RCTResponseSenderBlock)callback) {
528
+ [self.recognitionTask finish];
529
+ callback(@[ @false ]);
530
+ }
531
+
532
+ RCT_EXPORT_METHOD(stopTranscription : (RCTResponseSenderBlock)callback) {
533
+ [self.recognitionTask finish];
534
+ callback(@[ @false ]);
535
+ }
536
+
537
+ RCT_EXPORT_METHOD(cancelSpeech : (RCTResponseSenderBlock)callback) {
538
+ [self.recognitionTask cancel];
539
+ callback(@[ @false ]);
540
+ }
541
+
542
+ RCT_EXPORT_METHOD(cancelTranscription : (RCTResponseSenderBlock)callback) {
543
+ [self.recognitionTask cancel];
544
+ callback(@[ @false ]);
545
+ }
546
+
547
+ RCT_EXPORT_METHOD(destroySpeech : (RCTResponseSenderBlock)callback) {
548
+ [self teardown];
549
+ callback(@[ @false ]);
550
+ }
551
+
552
+ RCT_EXPORT_METHOD(destroyTranscription : (RCTResponseSenderBlock)callback) {
553
+ [self teardown];
554
+ callback(@[ @false ]);
555
+ }
556
+
557
+ RCT_EXPORT_METHOD(isSpeechAvailable : (RCTResponseSenderBlock)callback) {
558
+ [SFSpeechRecognizer
559
+ requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
560
+ switch (status) {
561
+ case SFSpeechRecognizerAuthorizationStatusAuthorized:
562
+ callback(@[ @true ]);
563
+ break;
564
+ default:
565
+ callback(@[ @false ]);
566
+ }
567
+ }];
568
+ }
569
+ RCT_EXPORT_METHOD(isRecognizing : (RCTResponseSenderBlock)callback) {
570
+ if (self.recognitionTask != nil) {
571
+ switch (self.recognitionTask.state) {
572
+ case SFSpeechRecognitionTaskStateRunning:
573
+ callback(@[ @true ]);
574
+ break;
575
+ default:
576
+ callback(@[ @false ]);
577
+ }
578
+ } else {
579
+ callback(@[ @false ]);
580
+ }
581
+ }
582
+
583
+ RCT_EXPORT_METHOD(startSpeech
584
+ : (NSString *)localeStr callback
585
+ : (RCTResponseSenderBlock)callback) {
586
+ if (self.recognitionTask != nil) {
587
+ [self sendResult:RCTMakeError(@"Speech recognition already started!", nil,
588
+ nil):nil:nil:nil];
589
+ return;
590
+ }
591
+
592
+ [SFSpeechRecognizer requestAuthorization:^(
593
+ SFSpeechRecognizerAuthorizationStatus status) {
594
+ switch (status) {
595
+ case SFSpeechRecognizerAuthorizationStatusNotDetermined:
596
+ [self sendResult:RCTMakeError(@"Speech recognition not yet authorized",
597
+ nil, nil):nil:nil:nil];
598
+ break;
599
+ case SFSpeechRecognizerAuthorizationStatusDenied:
600
+ [self sendResult:RCTMakeError(@"User denied access to speech recognition",
601
+ nil, nil):nil:nil:nil];
602
+ break;
603
+ case SFSpeechRecognizerAuthorizationStatusRestricted:
604
+ [self sendResult:RCTMakeError(
605
+ @"Speech recognition restricted on this device", nil,
606
+ nil):nil:nil:nil];
607
+ break;
608
+ case SFSpeechRecognizerAuthorizationStatusAuthorized:
609
+ [self setupAndStartRecognizing:localeStr];
610
+ break;
611
+ }
612
+ }];
613
+ callback(@[ @false ]);
614
+ }
615
+
616
+ RCT_EXPORT_METHOD(startTranscription
617
+ : (NSString *)filePath withLocaleStr
618
+ : (NSString *)localeStr callback
619
+ : (RCTResponseSenderBlock)callback) {
620
+ if (self.recognitionTask != nil) {
621
+ [self sendResult:RCTMakeError(@"Speech recognition already started!", nil,
622
+ nil):nil:nil:nil];
623
+ return;
624
+ }
625
+
626
+ [SFSpeechRecognizer requestAuthorization:^(
627
+ SFSpeechRecognizerAuthorizationStatus status) {
628
+ switch (status) {
629
+ case SFSpeechRecognizerAuthorizationStatusNotDetermined:
630
+ [self sendResult:RCTMakeError(@"Speech recognition not yet authorized",
631
+ nil, nil):nil:nil:nil];
632
+ break;
633
+ case SFSpeechRecognizerAuthorizationStatusDenied:
634
+ [self sendResult:RCTMakeError(@"User denied access to speech recognition",
635
+ nil, nil):nil:nil:nil];
636
+ break;
637
+ case SFSpeechRecognizerAuthorizationStatusRestricted:
638
+ [self sendResult:RCTMakeError(
639
+ @"Speech recognition restricted on this device", nil,
640
+ nil):nil:nil:nil];
641
+ break;
642
+ case SFSpeechRecognizerAuthorizationStatusAuthorized:
643
+ [self setupAndTranscribeFile:filePath withLocaleStr:localeStr];
644
+ break;
645
+ }
646
+ }];
647
+ callback(@[ @false ]);
648
+ }
649
+
650
+
651
+
652
+ + (BOOL)requiresMainQueueSetup {
653
+ return YES;
654
+ }
655
+
656
+ // Don't compile this code when we build for the old architecture.
657
+ #ifdef RCT_NEW_ARCH_ENABLED
658
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
659
+ (const facebook::react::ObjCTurboModule::InitParams &)params
660
+ {
661
+ return std::make_shared<facebook::react::NativeVoiceIOSSpecJSI>(params);
662
+ }
663
+ #endif
664
+
665
+ - (dispatch_queue_t)methodQueue {
666
+ return dispatch_get_main_queue();
667
+ }
668
+
669
+ RCT_EXPORT_MODULE()
670
+
671
+
672
+ @end