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.
- package/.nvmrc +1 -0
- package/.prettierrc +5 -0
- package/.releaserc +15 -0
- package/CONTRIBUTING.md +293 -0
- package/LICENSE +21 -0
- package/MIGRATION_SUMMARY.md +510 -0
- package/README.md +576 -0
- package/android/build.gradle +126 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/VoiceSpec.kt +55 -0
- package/android/src/main/java/com/wenkesj/voice/Voice.kt +343 -0
- package/android/src/main/java/com/wenkesj/voice/VoiceModule.kt +63 -0
- package/android/src/main/java/com/wenkesj/voice/VoicePackage.kt +35 -0
- package/android/src/newarch/VoiceSpec.kt +55 -0
- package/android/src/oldarch/VoiceSpec.kt +30 -0
- package/app.plugin.js +1 -0
- package/dist/NativeVoiceAndroid.d.ts +22 -0
- package/dist/NativeVoiceAndroid.d.ts.map +1 -0
- package/dist/NativeVoiceAndroid.js +3 -0
- package/dist/NativeVoiceAndroid.js.map +1 -0
- package/dist/NativeVoiceIOS.d.ts +18 -0
- package/dist/NativeVoiceIOS.d.ts.map +1 -0
- package/dist/NativeVoiceIOS.js +3 -0
- package/dist/NativeVoiceIOS.js.map +1 -0
- package/dist/VoiceModuleTypes.d.ts +54 -0
- package/dist/VoiceModuleTypes.d.ts.map +1 -0
- package/dist/VoiceModuleTypes.js +2 -0
- package/dist/VoiceModuleTypes.js.map +1 -0
- package/dist/VoiceUtilTypes.d.ts +43 -0
- package/dist/VoiceUtilTypes.d.ts.map +1 -0
- package/dist/VoiceUtilTypes.js +8 -0
- package/dist/VoiceUtilTypes.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +395 -0
- package/dist/index.js.map +1 -0
- package/ios/Voice/Voice.h +14 -0
- package/ios/Voice/Voice.mm +672 -0
- package/ios/Voice.xcodeproj/project.pbxproj +272 -0
- package/ios/Voice.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/package.json +101 -0
- package/plugin/build/withVoice.d.ts +13 -0
- package/plugin/build/withVoice.js +47 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/react-native-voice.podspec +46 -0
- package/src/NativeVoiceAndroid.ts +28 -0
- package/src/NativeVoiceIOS.ts +24 -0
- package/src/VoiceModuleTypes.ts +64 -0
- package/src/VoiceUtilTypes.ts +46 -0
- 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
|