whisper.rn 0.3.5 → 0.3.6
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.
|
@@ -21,6 +21,8 @@ import java.io.FileInputStream;
|
|
|
21
21
|
import java.io.PushbackInputStream;
|
|
22
22
|
|
|
23
23
|
public class RNWhisper implements LifecycleEventListener {
|
|
24
|
+
public static final String NAME = "RNWhisper";
|
|
25
|
+
|
|
24
26
|
private ReactApplicationContext reactContext;
|
|
25
27
|
private Downloader downloader;
|
|
26
28
|
|
|
@@ -40,6 +42,8 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
40
42
|
return constants;
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
private HashMap<AsyncTask, String> tasks = new HashMap<>();
|
|
46
|
+
|
|
43
47
|
private HashMap<Integer, WhisperContext> contexts = new HashMap<>();
|
|
44
48
|
|
|
45
49
|
private int getResourceIdentifier(String filePath) {
|
|
@@ -59,7 +63,7 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
public void initContext(final ReadableMap options, final Promise promise) {
|
|
62
|
-
new AsyncTask<Void, Void, Integer>() {
|
|
66
|
+
AsyncTask task = new AsyncTask<Void, Void, Integer>() {
|
|
63
67
|
private Exception exception;
|
|
64
68
|
|
|
65
69
|
@Override
|
|
@@ -104,8 +108,10 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
104
108
|
return;
|
|
105
109
|
}
|
|
106
110
|
promise.resolve(id);
|
|
111
|
+
tasks.remove(this);
|
|
107
112
|
}
|
|
108
113
|
}.execute();
|
|
114
|
+
tasks.put(task, "initContext");
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
public void transcribeFile(double id, double jobId, String filePath, ReadableMap options, Promise promise) {
|
|
@@ -122,7 +128,7 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
122
128
|
promise.reject("Context is already transcribing");
|
|
123
129
|
return;
|
|
124
130
|
}
|
|
125
|
-
new AsyncTask<Void, Void, WritableMap>() {
|
|
131
|
+
AsyncTask task = new AsyncTask<Void, Void, WritableMap>() {
|
|
126
132
|
private Exception exception;
|
|
127
133
|
|
|
128
134
|
@Override
|
|
@@ -161,8 +167,10 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
161
167
|
return;
|
|
162
168
|
}
|
|
163
169
|
promise.resolve(data);
|
|
170
|
+
tasks.remove(this);
|
|
164
171
|
}
|
|
165
172
|
}.execute();
|
|
173
|
+
tasks.put(task, "transcribeFile-" + id);
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
public void startRealtimeTranscribe(double id, double jobId, ReadableMap options, Promise promise) {
|
|
@@ -183,18 +191,48 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
183
191
|
promise.reject("Failed to start realtime transcribe. State: " + state);
|
|
184
192
|
}
|
|
185
193
|
|
|
186
|
-
public void abortTranscribe(double
|
|
187
|
-
WhisperContext context = contexts.get((int)
|
|
194
|
+
public void abortTranscribe(double id, double jobId, Promise promise) {
|
|
195
|
+
WhisperContext context = contexts.get((int) id);
|
|
188
196
|
if (context == null) {
|
|
189
197
|
promise.reject("Context not found");
|
|
190
198
|
return;
|
|
191
199
|
}
|
|
192
|
-
|
|
200
|
+
AsyncTask task = new AsyncTask<Void, Void, Void>() {
|
|
201
|
+
private Exception exception;
|
|
202
|
+
|
|
203
|
+
@Override
|
|
204
|
+
protected Void doInBackground(Void... voids) {
|
|
205
|
+
try {
|
|
206
|
+
context.stopTranscribe((int) jobId);
|
|
207
|
+
AsyncTask completionTask = null;
|
|
208
|
+
for (AsyncTask task : tasks.keySet()) {
|
|
209
|
+
if (tasks.get(task).equals("transcribeFile-" + id)) {
|
|
210
|
+
task.get();
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch (Exception e) {
|
|
215
|
+
exception = e;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@Override
|
|
221
|
+
protected void onPostExecute(Void result) {
|
|
222
|
+
if (exception != null) {
|
|
223
|
+
promise.reject(exception);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
promise.resolve(null);
|
|
227
|
+
tasks.remove(this);
|
|
228
|
+
}
|
|
229
|
+
}.execute();
|
|
230
|
+
tasks.put(task, "abortTranscribe-" + id);
|
|
193
231
|
}
|
|
194
232
|
|
|
195
233
|
public void releaseContext(double id, Promise promise) {
|
|
196
234
|
final int contextId = (int) id;
|
|
197
|
-
new AsyncTask<Void, Void, Void>() {
|
|
235
|
+
AsyncTask task = new AsyncTask<Void, Void, Void>() {
|
|
198
236
|
private Exception exception;
|
|
199
237
|
|
|
200
238
|
@Override
|
|
@@ -204,6 +242,14 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
204
242
|
if (context == null) {
|
|
205
243
|
throw new Exception("Context " + id + " not found");
|
|
206
244
|
}
|
|
245
|
+
context.stopCurrentTranscribe();
|
|
246
|
+
AsyncTask completionTask = null;
|
|
247
|
+
for (AsyncTask task : tasks.keySet()) {
|
|
248
|
+
if (tasks.get(task).equals("transcribeFile-" + contextId)) {
|
|
249
|
+
task.get();
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
207
253
|
context.release();
|
|
208
254
|
contexts.remove(contextId);
|
|
209
255
|
} catch (Exception e) {
|
|
@@ -219,12 +265,14 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
219
265
|
return;
|
|
220
266
|
}
|
|
221
267
|
promise.resolve(null);
|
|
268
|
+
tasks.remove(this);
|
|
222
269
|
}
|
|
223
270
|
}.execute();
|
|
271
|
+
tasks.put(task, "releaseContext-" + id);
|
|
224
272
|
}
|
|
225
273
|
|
|
226
274
|
public void releaseAllContexts(Promise promise) {
|
|
227
|
-
new AsyncTask<Void, Void, Void>() {
|
|
275
|
+
AsyncTask task = new AsyncTask<Void, Void, Void>() {
|
|
228
276
|
private Exception exception;
|
|
229
277
|
|
|
230
278
|
@Override
|
|
@@ -244,8 +292,10 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
244
292
|
return;
|
|
245
293
|
}
|
|
246
294
|
promise.resolve(null);
|
|
295
|
+
tasks.remove(this);
|
|
247
296
|
}
|
|
248
297
|
}.execute();
|
|
298
|
+
tasks.put(task, "releaseAllContexts");
|
|
249
299
|
}
|
|
250
300
|
|
|
251
301
|
@Override
|
|
@@ -258,10 +308,20 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
258
308
|
|
|
259
309
|
@Override
|
|
260
310
|
public void onHostDestroy() {
|
|
261
|
-
WhisperContext.
|
|
311
|
+
for (WhisperContext context : contexts.values()) {
|
|
312
|
+
context.stopCurrentTranscribe();
|
|
313
|
+
}
|
|
314
|
+
for (AsyncTask task : tasks.keySet()) {
|
|
315
|
+
try {
|
|
316
|
+
task.get();
|
|
317
|
+
} catch (Exception e) {
|
|
318
|
+
Log.e(NAME, "Failed to wait for task", e);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
262
321
|
for (WhisperContext context : contexts.values()) {
|
|
263
322
|
context.release();
|
|
264
323
|
}
|
|
324
|
+
WhisperContext.abortAllTranscribe(); // graceful abort
|
|
265
325
|
contexts.clear();
|
|
266
326
|
downloader.clearCache();
|
|
267
327
|
}
|
|
@@ -61,6 +61,7 @@ public class WhisperContext {
|
|
|
61
61
|
private boolean isCapturing = false;
|
|
62
62
|
private boolean isStoppedByAction = false;
|
|
63
63
|
private boolean isTranscribing = false;
|
|
64
|
+
private Thread rootFullHandler = null;
|
|
64
65
|
private Thread fullHandler = null;
|
|
65
66
|
|
|
66
67
|
public WhisperContext(int id, ReactApplicationContext reactContext, long context) {
|
|
@@ -81,6 +82,7 @@ public class WhisperContext {
|
|
|
81
82
|
isCapturing = false;
|
|
82
83
|
isStoppedByAction = false;
|
|
83
84
|
isTranscribing = false;
|
|
85
|
+
rootFullHandler = null;
|
|
84
86
|
fullHandler = null;
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -117,7 +119,7 @@ public class WhisperContext {
|
|
|
117
119
|
isCapturing = true;
|
|
118
120
|
recorder.startRecording();
|
|
119
121
|
|
|
120
|
-
new Thread(new Runnable() {
|
|
122
|
+
rootFullHandler = new Thread(new Runnable() {
|
|
121
123
|
@Override
|
|
122
124
|
public void run() {
|
|
123
125
|
try {
|
|
@@ -195,7 +197,8 @@ public class WhisperContext {
|
|
|
195
197
|
recorder = null;
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
|
-
})
|
|
200
|
+
});
|
|
201
|
+
rootFullHandler.start();
|
|
199
202
|
return state;
|
|
200
203
|
}
|
|
201
204
|
|
|
@@ -402,6 +405,14 @@ public class WhisperContext {
|
|
|
402
405
|
abortTranscribe(jobId);
|
|
403
406
|
isCapturing = false;
|
|
404
407
|
isStoppedByAction = true;
|
|
408
|
+
if (rootFullHandler != null) {
|
|
409
|
+
try {
|
|
410
|
+
rootFullHandler.join();
|
|
411
|
+
} catch (Exception e) {
|
|
412
|
+
Log.e(NAME, "Error joining rootFullHandler: " + e.getMessage());
|
|
413
|
+
}
|
|
414
|
+
rootFullHandler = null;
|
|
415
|
+
}
|
|
405
416
|
}
|
|
406
417
|
|
|
407
418
|
public void stopCurrentTranscribe() {
|
package/ios/RNWhisper.mm
CHANGED
|
@@ -68,13 +68,17 @@ RCT_REMAP_METHOD(initContext,
|
|
|
68
68
|
path = [[NSBundle mainBundle] pathForResource:modelPath ofType:nil];
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
int contextId = arc4random_uniform(1000000);
|
|
72
|
+
|
|
73
|
+
RNWhisperContext *context = [RNWhisperContext
|
|
74
|
+
initWithModelPath:path
|
|
75
|
+
contextId:contextId
|
|
76
|
+
];
|
|
72
77
|
if ([context getContext] == NULL) {
|
|
73
78
|
reject(@"whisper_cpp_error", @"Failed to load the model", nil);
|
|
74
79
|
return;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
int contextId = arc4random_uniform(1000000);
|
|
78
82
|
[contexts setObject:context forKey:[NSNumber numberWithInt:contextId]];
|
|
79
83
|
|
|
80
84
|
resolve([NSNumber numberWithInt:contextId]);
|
|
@@ -122,36 +126,36 @@ RCT_REMAP_METHOD(transcribeFile,
|
|
|
122
126
|
reject(@"whisper_error", @"Invalid file", nil);
|
|
123
127
|
return;
|
|
124
128
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
129
|
+
[context transcribeFile:jobId
|
|
130
|
+
audioData:waveFile
|
|
131
|
+
audioDataCount:count
|
|
132
|
+
options:options
|
|
133
|
+
onProgress: ^(int progress) {
|
|
134
|
+
if (rn_whisper_transcribe_is_aborted(jobId)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
138
|
+
[self sendEventWithName:@"@RNWhisper_onTranscribeProgress"
|
|
139
|
+
body:@{
|
|
140
|
+
@"contextId": [NSNumber numberWithInt:contextId],
|
|
141
|
+
@"jobId": [NSNumber numberWithInt:jobId],
|
|
142
|
+
@"progress": [NSNumber numberWithInt:progress]
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
onEnd: ^(int code) {
|
|
148
|
+
if (code != 0) {
|
|
149
|
+
free(waveFile);
|
|
150
|
+
reject(@"whisper_cpp_error", [NSString stringWithFormat:@"Failed to transcribe the file. Code: %d", code], nil);
|
|
151
|
+
return;
|
|
143
152
|
}
|
|
144
|
-
];
|
|
145
|
-
if (code != 0) {
|
|
146
153
|
free(waveFile);
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
NSMutableDictionary *result = [context getTextSegments];
|
|
155
|
+
result[@"isAborted"] = @([context isStoppedByAction]);
|
|
156
|
+
resolve(result);
|
|
149
157
|
}
|
|
150
|
-
|
|
151
|
-
NSMutableDictionary *result = [context getTextSegments];
|
|
152
|
-
result[@"isAborted"] = @([context isStoppedByAction]);
|
|
153
|
-
resolve(result);
|
|
154
|
-
});
|
|
158
|
+
];
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
RCT_REMAP_METHOD(startRealtimeTranscribe,
|
|
@@ -260,7 +264,7 @@ RCT_REMAP_METHOD(releaseAllContexts,
|
|
|
260
264
|
}
|
|
261
265
|
|
|
262
266
|
- (void)invalidate {
|
|
263
|
-
|
|
267
|
+
[super invalidate];
|
|
264
268
|
|
|
265
269
|
if (contexts == nil) {
|
|
266
270
|
return;
|
|
@@ -271,6 +275,8 @@ RCT_REMAP_METHOD(releaseAllContexts,
|
|
|
271
275
|
[context invalidate];
|
|
272
276
|
}
|
|
273
277
|
|
|
278
|
+
rn_whisper_abort_all_transcribe(); // graceful abort
|
|
279
|
+
|
|
274
280
|
[contexts removeAllObjects];
|
|
275
281
|
contexts = nil;
|
|
276
282
|
|
package/ios/RNWhisperContext.h
CHANGED
|
@@ -36,21 +36,26 @@ typedef struct {
|
|
|
36
36
|
} RNWhisperContextRecordState;
|
|
37
37
|
|
|
38
38
|
@interface RNWhisperContext : NSObject {
|
|
39
|
+
int contextId;
|
|
40
|
+
dispatch_queue_t dQueue;
|
|
39
41
|
struct whisper_context * ctx;
|
|
40
42
|
RNWhisperContextRecordState recordState;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
+ (instancetype)initWithModelPath:(NSString *)modelPath;
|
|
45
|
+
+ (instancetype)initWithModelPath:(NSString *)modelPath contextId:(int)contextId;
|
|
44
46
|
- (struct whisper_context *)getContext;
|
|
47
|
+
- (dispatch_queue_t)getDispatchQueue;
|
|
45
48
|
- (OSStatus)transcribeRealtime:(int)jobId
|
|
46
49
|
options:(NSDictionary *)options
|
|
47
50
|
onTranscribe:(void (^)(int, NSString *, NSDictionary *))onTranscribe;
|
|
48
|
-
- (
|
|
51
|
+
- (void)transcribeFile:(int)jobId
|
|
49
52
|
audioData:(float *)audioData
|
|
50
53
|
audioDataCount:(int)audioDataCount
|
|
51
54
|
options:(NSDictionary *)options
|
|
52
|
-
onProgress:(void (^)(int))onProgress
|
|
55
|
+
onProgress:(void (^)(int))onProgress
|
|
56
|
+
onEnd:(void (^)(int))onEnd;
|
|
53
57
|
- (void)stopTranscribe:(int)jobId;
|
|
58
|
+
- (void)stopCurrentTranscribe;
|
|
54
59
|
- (bool)isCapturing;
|
|
55
60
|
- (bool)isTranscribing;
|
|
56
61
|
- (bool)isStoppedByAction;
|
package/ios/RNWhisperContext.mm
CHANGED
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
@implementation RNWhisperContext
|
|
6
6
|
|
|
7
|
-
+ (instancetype)initWithModelPath:(NSString *)modelPath {
|
|
7
|
+
+ (instancetype)initWithModelPath:(NSString *)modelPath contextId:(int)contextId {
|
|
8
8
|
RNWhisperContext *context = [[RNWhisperContext alloc] init];
|
|
9
|
+
context->contextId = contextId;
|
|
9
10
|
context->ctx = whisper_init_from_file([modelPath UTF8String]);
|
|
11
|
+
context->dQueue = dispatch_queue_create(
|
|
12
|
+
[[NSString stringWithFormat:@"RNWhisperContext-%d", contextId] UTF8String],
|
|
13
|
+
DISPATCH_QUEUE_SERIAL
|
|
14
|
+
);
|
|
10
15
|
return context;
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -14,6 +19,10 @@
|
|
|
14
19
|
return self->ctx;
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
- (dispatch_queue_t)getDispatchQueue {
|
|
23
|
+
return self->dQueue;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
- (void)prepareRealtime:(NSDictionary *)options {
|
|
18
27
|
self->recordState.options = options;
|
|
19
28
|
|
|
@@ -109,7 +118,7 @@ void AudioInputCallback(void * inUserData,
|
|
|
109
118
|
nSamples != state->nSamplesTranscribing
|
|
110
119
|
) {
|
|
111
120
|
state->isTranscribing = true;
|
|
112
|
-
dispatch_async(
|
|
121
|
+
dispatch_async([state->mSelf getDispatchQueue], ^{
|
|
113
122
|
[state->mSelf fullTranscribeSamples:state];
|
|
114
123
|
});
|
|
115
124
|
}
|
|
@@ -140,7 +149,7 @@ void AudioInputCallback(void * inUserData,
|
|
|
140
149
|
|
|
141
150
|
if (!state->isTranscribing) {
|
|
142
151
|
state->isTranscribing = true;
|
|
143
|
-
dispatch_async(
|
|
152
|
+
dispatch_async([state->mSelf getDispatchQueue], ^{
|
|
144
153
|
[state->mSelf fullTranscribeSamples:state];
|
|
145
154
|
});
|
|
146
155
|
}
|
|
@@ -263,19 +272,22 @@ void AudioInputCallback(void * inUserData,
|
|
|
263
272
|
return status;
|
|
264
273
|
}
|
|
265
274
|
|
|
266
|
-
- (
|
|
275
|
+
- (void)transcribeFile:(int)jobId
|
|
267
276
|
audioData:(float *)audioData
|
|
268
277
|
audioDataCount:(int)audioDataCount
|
|
269
278
|
options:(NSDictionary *)options
|
|
270
279
|
onProgress:(void (^)(int))onProgress
|
|
280
|
+
onEnd:(void (^)(int))onEnd
|
|
271
281
|
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
282
|
+
dispatch_async(dQueue, ^{
|
|
283
|
+
self->recordState.isStoppedByAction = false;
|
|
284
|
+
self->recordState.isTranscribing = true;
|
|
285
|
+
self->recordState.jobId = jobId;
|
|
286
|
+
int code = [self fullTranscribeWithProgress:onProgress jobId:jobId audioData:audioData audioDataCount:audioDataCount options:options];
|
|
287
|
+
self->recordState.jobId = -1;
|
|
288
|
+
self->recordState.isTranscribing = false;
|
|
289
|
+
onEnd(code);
|
|
290
|
+
});
|
|
279
291
|
}
|
|
280
292
|
|
|
281
293
|
- (void)stopAudio {
|
|
@@ -293,6 +305,7 @@ void AudioInputCallback(void * inUserData,
|
|
|
293
305
|
}
|
|
294
306
|
self->recordState.isCapturing = false;
|
|
295
307
|
self->recordState.isStoppedByAction = true;
|
|
308
|
+
dispatch_barrier_sync(dQueue, ^{});
|
|
296
309
|
}
|
|
297
310
|
|
|
298
311
|
- (void)stopCurrentTranscribe {
|