whisper.rn 0.4.3 → 0.5.0-rc.1
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/README.md +1 -3
- package/android/build.gradle +71 -11
- package/android/src/main/CMakeLists.txt +28 -1
- package/android/src/main/java/com/rnwhisper/JSCallInvokerResolver.java +40 -0
- package/android/src/main/java/com/rnwhisper/RNWhisper.java +59 -11
- package/android/src/main/java/com/rnwhisper/WhisperContext.java +21 -9
- package/android/src/main/java/com/rnwhisper/WhisperVadContext.java +1 -1
- package/android/src/main/jni.cpp +79 -2
- package/android/src/newarch/java/com/rnwhisper/RNWhisperModule.java +5 -0
- package/android/src/oldarch/java/com/rnwhisper/RNWhisperModule.java +5 -0
- package/cpp/jsi/RNWhisperJSI.cpp +681 -0
- package/cpp/jsi/RNWhisperJSI.h +44 -0
- package/cpp/jsi/ThreadPool.h +100 -0
- package/ios/RNWhisper.h +3 -0
- package/ios/RNWhisper.mm +38 -0
- package/jest/mock.js +1 -0
- package/lib/commonjs/NativeRNWhisper.js.map +1 -1
- package/lib/commonjs/index.js +83 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeRNWhisper.js.map +1 -1
- package/lib/module/index.js +83 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/NativeRNWhisper.d.ts +4 -0
- package/lib/typescript/NativeRNWhisper.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +18 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +7 -8
- package/src/NativeRNWhisper.ts +2 -0
- package/src/index.ts +162 -33
- package/whisper-rn.podspec +6 -3
package/README.md
CHANGED
|
@@ -38,9 +38,7 @@ Add proguard rule if it's enabled in project (android/app/proguard-rules.pro):
|
|
|
38
38
|
-keep class com.rnwhisper.** { *; }
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
For build from source, it's recommended to use `ndkVersion = "24.0.8215888"` (or above) in your root project build configuration for Apple Silicon Macs. Otherwise please follow this trobleshooting [issue](./TROUBLESHOOTING.md#android-got-build-error-unknown-host-cpu-architecture-arm64-on-apple-silicon-macs).
|
|
41
|
+
It's recommended to use `ndkVersion = "24.0.8215888"` (or above) in your root project build configuration for Apple Silicon Macs. Otherwise please follow this trobleshooting [issue](./TROUBLESHOOTING.md#android-got-build-error-unknown-host-cpu-architecture-arm64-on-apple-silicon-macs).
|
|
44
42
|
|
|
45
43
|
#### Expo
|
|
46
44
|
|
package/android/build.gradle
CHANGED
|
@@ -35,6 +35,33 @@ def reactNativeArchitectures() {
|
|
|
35
35
|
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Detect RN Version as done here:
|
|
39
|
+
// https://github.com/software-mansion/react-native-reanimated/blob/66a6bd0e3a819ca7ae46751d36e405fe32b68b71/packages/react-native-reanimated/android/build.gradle#L73
|
|
40
|
+
def resolveReactNativeDirectory() {
|
|
41
|
+
def reactNativeLocation = rootProject.ext.has("REACT_NATIVE_NODE_MODULES_DIR") ? rootProject.ext.get("REACT_NATIVE_NODE_MODULES_DIR") : null
|
|
42
|
+
if (reactNativeLocation != null) {
|
|
43
|
+
return file(reactNativeLocation)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fallback to node resolver for custom directory structures like monorepos.
|
|
47
|
+
def reactNativePackage = file(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim())
|
|
48
|
+
if(reactNativePackage.exists()) {
|
|
49
|
+
return reactNativePackage.parentFile
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new GradleException(
|
|
53
|
+
"[Reanimated] Unable to resolve react-native location in node_modules. You should project extension property (in `app/build.gradle`) `REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def reactNativeRootDir = resolveReactNativeDirectory()
|
|
58
|
+
|
|
59
|
+
def reactProperties = new Properties()
|
|
60
|
+
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
|
|
61
|
+
|
|
62
|
+
def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
|
|
63
|
+
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
|
|
64
|
+
|
|
38
65
|
android {
|
|
39
66
|
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
|
40
67
|
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
|
|
@@ -50,21 +77,47 @@ android {
|
|
|
50
77
|
externalNativeBuild {
|
|
51
78
|
cmake {
|
|
52
79
|
abiFilters (*reactNativeArchitectures())
|
|
80
|
+
// Configure STL to be compatible with ReactAndroid JSI libraries
|
|
81
|
+
arguments "-DANDROID_STL=c++_shared",
|
|
82
|
+
"-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}"
|
|
53
83
|
}
|
|
54
84
|
}
|
|
85
|
+
ndk {
|
|
86
|
+
// Ensure shared STL is used for JSI compatibility
|
|
87
|
+
abiFilters (*reactNativeArchitectures())
|
|
88
|
+
}
|
|
55
89
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
90
|
+
|
|
91
|
+
packagingOptions {
|
|
92
|
+
doNotStrip resolveBuildType() == "debug" ? "**/**/*.so" : ""
|
|
93
|
+
excludes = [
|
|
94
|
+
"META-INF",
|
|
95
|
+
"META-INF/**",
|
|
96
|
+
"**/libc++_shared.so",
|
|
97
|
+
"**/libfbjni.so",
|
|
98
|
+
"**/libjsi.so",
|
|
99
|
+
"**/libfolly_json.so",
|
|
100
|
+
"**/libfolly_runtime.so",
|
|
101
|
+
"**/libglog.so",
|
|
102
|
+
"**/libreactnative.so",
|
|
103
|
+
"**/libreactnativejni.so",
|
|
104
|
+
"**/libturbomodulejsijni.so",
|
|
105
|
+
"**/libreact_nativemodule_core.so",
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
buildFeatures {
|
|
110
|
+
prefab = true
|
|
111
|
+
}
|
|
112
|
+
externalNativeBuild {
|
|
113
|
+
cmake {
|
|
114
|
+
path = file('src/main/CMakeLists.txt')
|
|
62
115
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
116
|
+
}
|
|
117
|
+
// Exclude jniLibs when building from source
|
|
118
|
+
sourceSets {
|
|
119
|
+
main {
|
|
120
|
+
jniLibs.srcDirs = []
|
|
68
121
|
}
|
|
69
122
|
}
|
|
70
123
|
buildTypes {
|
|
@@ -113,3 +166,10 @@ if (isNewArchitectureEnabled()) {
|
|
|
113
166
|
codegenJavaPackageName = "com.rnwhisper"
|
|
114
167
|
}
|
|
115
168
|
}
|
|
169
|
+
|
|
170
|
+
def resolveBuildType() {
|
|
171
|
+
Gradle gradle = getGradle()
|
|
172
|
+
String tskReqStr = gradle.getStartParameter().getTaskRequests()["args"].toString()
|
|
173
|
+
|
|
174
|
+
return tskReqStr.contains("Release") ? "release" : "debug"
|
|
175
|
+
}
|
|
@@ -5,9 +5,17 @@ project(whisper.rn)
|
|
|
5
5
|
set(CMAKE_CXX_STANDARD 17)
|
|
6
6
|
set(RNWHISPER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../../cpp)
|
|
7
7
|
|
|
8
|
+
# Configure STL to be compatible with ReactAndroid JSI libraries
|
|
9
|
+
set(CMAKE_ANDROID_STL_TYPE c++_shared)
|
|
10
|
+
|
|
11
|
+
# Find ReactAndroid package for JSI
|
|
12
|
+
find_package(ReactAndroid REQUIRED CONFIG)
|
|
13
|
+
find_package(fbjni REQUIRED CONFIG)
|
|
14
|
+
|
|
8
15
|
include_directories(
|
|
9
16
|
${RNWHISPER_LIB_DIR}
|
|
10
17
|
${RNWHISPER_LIB_DIR}/ggml-cpu
|
|
18
|
+
${RNWHISPER_LIB_DIR}/jsi
|
|
11
19
|
)
|
|
12
20
|
|
|
13
21
|
set(
|
|
@@ -34,6 +42,7 @@ set(
|
|
|
34
42
|
${RNWHISPER_LIB_DIR}/whisper.cpp
|
|
35
43
|
${RNWHISPER_LIB_DIR}/rn-audioutils.cpp
|
|
36
44
|
${RNWHISPER_LIB_DIR}/rn-whisper.cpp
|
|
45
|
+
${RNWHISPER_LIB_DIR}/jsi/RNWhisperJSI.cpp
|
|
37
46
|
${CMAKE_SOURCE_DIR}/jni.cpp
|
|
38
47
|
)
|
|
39
48
|
|
|
@@ -54,7 +63,25 @@ function(build_library target_name arch cpu_flags)
|
|
|
54
63
|
${SOURCE_FILES_ARCH}
|
|
55
64
|
)
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
# Link JSI libraries
|
|
67
|
+
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 76)
|
|
68
|
+
target_link_libraries(${target_name}
|
|
69
|
+
${LOG_LIB}
|
|
70
|
+
android
|
|
71
|
+
fbjni::fbjni
|
|
72
|
+
ReactAndroid::jsi
|
|
73
|
+
ReactAndroid::reactnative
|
|
74
|
+
)
|
|
75
|
+
else ()
|
|
76
|
+
target_link_libraries(${target_name}
|
|
77
|
+
${LOG_LIB}
|
|
78
|
+
android
|
|
79
|
+
fbjni::fbjni
|
|
80
|
+
ReactAndroid::jsi
|
|
81
|
+
ReactAndroid::turbomodulejsijni
|
|
82
|
+
ReactAndroid::react_nativemodule_core
|
|
83
|
+
)
|
|
84
|
+
endif ()
|
|
58
85
|
|
|
59
86
|
if (${arch} STREQUAL "generic")
|
|
60
87
|
target_compile_options(${target_name} PRIVATE -DWSP_GGML_CPU_GENERIC)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.rnwhisper;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.OptIn;
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
5
|
+
import com.facebook.react.common.annotations.FrameworkAPI;
|
|
6
|
+
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
|
|
7
|
+
|
|
8
|
+
public class JSCallInvokerResolver {
|
|
9
|
+
|
|
10
|
+
@OptIn(markerClass = FrameworkAPI.class)
|
|
11
|
+
public static CallInvokerHolderImpl getJSCallInvokerHolder(ReactApplicationContext context) {
|
|
12
|
+
try {
|
|
13
|
+
var method = context.getClass().getMethod("getJSCallInvokerHolder");
|
|
14
|
+
return (CallInvokerHolderImpl) method.invoke(context);
|
|
15
|
+
} catch (Exception ignored) {
|
|
16
|
+
// In newer implementations, the method is in CatalystInstance, continue.
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
var catalystInstance = context.getClass().getMethod("getCatalystInstance").invoke(context);
|
|
20
|
+
assert catalystInstance != null;
|
|
21
|
+
var method = catalystInstance.getClass().getMethod("getJSCallInvokerHolder");
|
|
22
|
+
return (CallInvokerHolderImpl) method.invoke(catalystInstance);
|
|
23
|
+
} catch (Exception e) {
|
|
24
|
+
throw new RuntimeException("Failed to get JSCallInvokerHolder", e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@OptIn(markerClass = FrameworkAPI.class)
|
|
29
|
+
public static long getJavaScriptContextHolder(ReactApplicationContext context) {
|
|
30
|
+
try {
|
|
31
|
+
var method = context.getClass().getMethod("getJavaScriptContextHolder");
|
|
32
|
+
var holder = method.invoke(context);
|
|
33
|
+
var getMethod = holder.getClass().getMethod("get");
|
|
34
|
+
return (Long) getMethod.invoke(holder);
|
|
35
|
+
} catch (Exception e) {
|
|
36
|
+
throw new RuntimeException("Failed to get JavaScriptContextHolder", e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -15,6 +15,9 @@ import com.facebook.react.bridge.ReadableMap;
|
|
|
15
15
|
import com.facebook.react.bridge.WritableMap;
|
|
16
16
|
import com.facebook.react.bridge.WritableArray;
|
|
17
17
|
import com.facebook.react.bridge.Arguments;
|
|
18
|
+
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
|
|
19
|
+
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder;
|
|
20
|
+
import com.facebook.react.bridge.Arguments;
|
|
18
21
|
|
|
19
22
|
import java.util.HashMap;
|
|
20
23
|
import java.util.Random;
|
|
@@ -23,6 +26,11 @@ import java.io.FileInputStream;
|
|
|
23
26
|
import java.io.InputStream;
|
|
24
27
|
import java.io.PushbackInputStream;
|
|
25
28
|
|
|
29
|
+
import com.facebook.react.common.LifecycleState;
|
|
30
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
31
|
+
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
|
|
32
|
+
import com.facebook.react.bridge.ReactContext;
|
|
33
|
+
|
|
26
34
|
public class RNWhisper implements LifecycleEventListener {
|
|
27
35
|
public static final String NAME = "RNWhisper";
|
|
28
36
|
|
|
@@ -50,6 +58,43 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
50
58
|
private HashMap<Integer, WhisperContext> contexts = new HashMap<>();
|
|
51
59
|
private HashMap<Integer, WhisperVadContext> vadContexts = new HashMap<>();
|
|
52
60
|
|
|
61
|
+
// JSI helper method to check if context exists
|
|
62
|
+
public boolean hasContext(int contextId) {
|
|
63
|
+
return contexts.containsKey(contextId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public void installJSIBindings(Promise promise) {
|
|
67
|
+
|
|
68
|
+
AsyncTask task = new AsyncTask<Void, Void, Void>() {
|
|
69
|
+
private Exception exception;
|
|
70
|
+
|
|
71
|
+
@Override
|
|
72
|
+
protected Void doInBackground(Void... voids) {
|
|
73
|
+
try {
|
|
74
|
+
CallInvokerHolderImpl callInvokerHolder = JSCallInvokerResolver.getJSCallInvokerHolder(reactContext);
|
|
75
|
+
long runtimePtr = JSCallInvokerResolver.getJavaScriptContextHolder(reactContext);
|
|
76
|
+
|
|
77
|
+
WhisperContext.installJSIBindings(runtimePtr, callInvokerHolder);
|
|
78
|
+
android.util.Log.i("RNWhisperModule", "JSI bindings installed successfully");
|
|
79
|
+
} catch (Exception e) {
|
|
80
|
+
exception = e;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Override
|
|
86
|
+
protected void onPostExecute(Void result) {
|
|
87
|
+
if (exception != null) {
|
|
88
|
+
promise.reject(exception);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
promise.resolve(null);
|
|
92
|
+
tasks.remove(this);
|
|
93
|
+
}
|
|
94
|
+
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
95
|
+
tasks.put(task, "installJSIBindings");
|
|
96
|
+
}
|
|
97
|
+
|
|
53
98
|
private int getResourceIdentifier(String filePath) {
|
|
54
99
|
int identifier = reactContext.getResources().getIdentifier(
|
|
55
100
|
filePath,
|
|
@@ -67,11 +112,11 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
67
112
|
}
|
|
68
113
|
|
|
69
114
|
public void initContext(final ReadableMap options, final Promise promise) {
|
|
70
|
-
AsyncTask task = new AsyncTask<Void, Void,
|
|
115
|
+
AsyncTask task = new AsyncTask<Void, Void, WhisperContext>() {
|
|
71
116
|
private Exception exception;
|
|
72
117
|
|
|
73
118
|
@Override
|
|
74
|
-
protected
|
|
119
|
+
protected WhisperContext doInBackground(Void... voids) {
|
|
75
120
|
try {
|
|
76
121
|
String modelPath = options.getString("filePath");
|
|
77
122
|
boolean isBundleAsset = options.getBoolean("isBundleAsset");
|
|
@@ -81,24 +126,25 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
81
126
|
modelFilePath = downloader.downloadFile(modelPath);
|
|
82
127
|
}
|
|
83
128
|
|
|
129
|
+
int id = Math.abs(new Random().nextInt());
|
|
84
130
|
long context;
|
|
85
131
|
int resId = getResourceIdentifier(modelFilePath);
|
|
86
132
|
if (resId > 0) {
|
|
87
133
|
context = WhisperContext.initContextWithInputStream(
|
|
134
|
+
id,
|
|
88
135
|
new PushbackInputStream(reactContext.getResources().openRawResource(resId))
|
|
89
136
|
);
|
|
90
137
|
} else if (isBundleAsset) {
|
|
91
|
-
context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelFilePath);
|
|
138
|
+
context = WhisperContext.initContextWithAsset(id, reactContext.getAssets(), modelFilePath);
|
|
92
139
|
} else {
|
|
93
|
-
context = WhisperContext.initContext(modelFilePath);
|
|
140
|
+
context = WhisperContext.initContext(id, modelFilePath);
|
|
94
141
|
}
|
|
95
142
|
if (context == 0) {
|
|
96
143
|
throw new Exception("Failed to initialize context");
|
|
97
144
|
}
|
|
98
|
-
int id = Math.abs(new Random().nextInt());
|
|
99
145
|
WhisperContext whisperContext = new WhisperContext(id, reactContext, context);
|
|
100
146
|
contexts.put(id, whisperContext);
|
|
101
|
-
return
|
|
147
|
+
return whisperContext;
|
|
102
148
|
} catch (Exception e) {
|
|
103
149
|
exception = e;
|
|
104
150
|
return null;
|
|
@@ -106,13 +152,14 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
106
152
|
}
|
|
107
153
|
|
|
108
154
|
@Override
|
|
109
|
-
protected void onPostExecute(
|
|
155
|
+
protected void onPostExecute(WhisperContext context) {
|
|
110
156
|
if (exception != null) {
|
|
111
157
|
promise.reject(exception);
|
|
112
158
|
return;
|
|
113
159
|
}
|
|
114
160
|
WritableMap result = Arguments.createMap();
|
|
115
|
-
result.putInt("contextId",
|
|
161
|
+
result.putInt("contextId", context.getId());
|
|
162
|
+
result.putDouble("contextPtr", (double) context.getContextPtr());
|
|
116
163
|
result.putBoolean("gpu", false);
|
|
117
164
|
result.putString("reasonNoGPU", "Currently not supported");
|
|
118
165
|
promise.resolve(result);
|
|
@@ -361,21 +408,22 @@ public class RNWhisper implements LifecycleEventListener {
|
|
|
361
408
|
modelFilePath = downloader.downloadFile(modelPath);
|
|
362
409
|
}
|
|
363
410
|
|
|
411
|
+
int id = Math.abs(new Random().nextInt());
|
|
364
412
|
long vadContext;
|
|
365
413
|
int resId = getResourceIdentifier(modelFilePath);
|
|
366
414
|
if (resId > 0) {
|
|
367
415
|
vadContext = WhisperContext.initVadContextWithInputStream(
|
|
416
|
+
id,
|
|
368
417
|
new PushbackInputStream(reactContext.getResources().openRawResource(resId))
|
|
369
418
|
);
|
|
370
419
|
} else if (isBundleAsset) {
|
|
371
|
-
vadContext = WhisperContext.initVadContextWithAsset(reactContext.getAssets(), modelFilePath);
|
|
420
|
+
vadContext = WhisperContext.initVadContextWithAsset(id, reactContext.getAssets(), modelFilePath);
|
|
372
421
|
} else {
|
|
373
|
-
vadContext = WhisperContext.initVadContext(modelFilePath);
|
|
422
|
+
vadContext = WhisperContext.initVadContext(id, modelFilePath);
|
|
374
423
|
}
|
|
375
424
|
if (vadContext == 0) {
|
|
376
425
|
throw new Exception("Failed to initialize VAD context");
|
|
377
426
|
}
|
|
378
|
-
int id = Math.abs(new Random().nextInt());
|
|
379
427
|
WhisperVadContext whisperVadContext = new WhisperVadContext(id, reactContext, vadContext);
|
|
380
428
|
vadContexts.put(id, whisperVadContext);
|
|
381
429
|
return id;
|
|
@@ -81,6 +81,14 @@ public class WhisperContext {
|
|
|
81
81
|
fullHandler = null;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
public int getId() {
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public long getContextPtr() {
|
|
89
|
+
return context;
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
private boolean vad(int sliceIndex, int nSamples, int n) {
|
|
85
93
|
if (isTranscribing) return true;
|
|
86
94
|
return vadSimple(jobId, sliceIndex, nSamples, n);
|
|
@@ -430,7 +438,7 @@ public class WhisperContext {
|
|
|
430
438
|
|
|
431
439
|
public void release() {
|
|
432
440
|
stopCurrentTranscribe();
|
|
433
|
-
freeContext(context);
|
|
441
|
+
freeContext(id, context);
|
|
434
442
|
}
|
|
435
443
|
|
|
436
444
|
static {
|
|
@@ -497,10 +505,10 @@ public class WhisperContext {
|
|
|
497
505
|
}
|
|
498
506
|
|
|
499
507
|
// JNI methods
|
|
500
|
-
protected static native long initContext(String modelPath);
|
|
501
|
-
protected static native long initContextWithAsset(AssetManager assetManager, String modelPath);
|
|
502
|
-
protected static native long initContextWithInputStream(PushbackInputStream inputStream);
|
|
503
|
-
protected static native void freeContext(long contextPtr);
|
|
508
|
+
protected static native long initContext(int contextId, String modelPath);
|
|
509
|
+
protected static native long initContextWithAsset(int contextId, AssetManager assetManager, String modelPath);
|
|
510
|
+
protected static native long initContextWithInputStream(int contextId, PushbackInputStream inputStream);
|
|
511
|
+
protected static native void freeContext(int contextId, long contextPtr);
|
|
504
512
|
|
|
505
513
|
protected static native int fullWithNewJob(
|
|
506
514
|
int job_id,
|
|
@@ -535,10 +543,10 @@ public class WhisperContext {
|
|
|
535
543
|
protected static native String bench(long context, int n_threads);
|
|
536
544
|
|
|
537
545
|
// VAD JNI methods
|
|
538
|
-
protected static native long initVadContext(String modelPath);
|
|
539
|
-
protected static native long initVadContextWithAsset(AssetManager assetManager, String modelPath);
|
|
540
|
-
protected static native long initVadContextWithInputStream(PushbackInputStream inputStream);
|
|
541
|
-
protected static native void freeVadContext(long vadContextPtr);
|
|
546
|
+
protected static native long initVadContext(int contextId, String modelPath);
|
|
547
|
+
protected static native long initVadContextWithAsset(int contextId, AssetManager assetManager, String modelPath);
|
|
548
|
+
protected static native long initVadContextWithInputStream(int contextId, PushbackInputStream inputStream);
|
|
549
|
+
protected static native void freeVadContext(int contextId, long vadContextPtr);
|
|
542
550
|
protected static native boolean vadDetectSpeech(long vadContextPtr, float[] audioData, int nSamples);
|
|
543
551
|
protected static native long vadGetSegmentsFromProbs(long vadContextPtr, float threshold,
|
|
544
552
|
int minSpeechDurationMs, int minSilenceDurationMs,
|
|
@@ -559,4 +567,8 @@ public class WhisperContext {
|
|
|
559
567
|
return null;
|
|
560
568
|
}
|
|
561
569
|
}
|
|
570
|
+
|
|
571
|
+
// JSI Installation
|
|
572
|
+
protected static native void installJSIBindings(long runtimePtr, Object callInvokerHolder);
|
|
573
|
+
protected static native void cleanupJSIBindings();
|
|
562
574
|
}
|
package/android/src/main/jni.cpp
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
#include "rn-whisper.h"
|
|
12
12
|
#include "ggml.h"
|
|
13
13
|
#include "jni-utils.h"
|
|
14
|
+
#include "RNWhisperJSI.h"
|
|
15
|
+
|
|
16
|
+
// Include fbjni headers for type-safe JNI
|
|
17
|
+
#include <fbjni/fbjni.h>
|
|
18
|
+
#include <ReactCommon/CallInvokerHolder.h>
|
|
14
19
|
|
|
15
20
|
#define UNUSED(x) (void)(x)
|
|
16
21
|
#define TAG "JNI"
|
|
@@ -193,7 +198,7 @@ extern "C" {
|
|
|
193
198
|
|
|
194
199
|
JNIEXPORT jlong JNICALL
|
|
195
200
|
Java_com_rnwhisper_WhisperContext_initContext(
|
|
196
|
-
JNIEnv *env, jobject thiz, jstring model_path_str) {
|
|
201
|
+
JNIEnv *env, jobject thiz, jint context_id, jstring model_path_str) {
|
|
197
202
|
UNUSED(thiz);
|
|
198
203
|
struct whisper_context_params cparams;
|
|
199
204
|
|
|
@@ -205,6 +210,7 @@ Java_com_rnwhisper_WhisperContext_initContext(
|
|
|
205
210
|
const char *model_path_chars = env->GetStringUTFChars(model_path_str, nullptr);
|
|
206
211
|
context = whisper_init_from_file_with_params(model_path_chars, cparams);
|
|
207
212
|
env->ReleaseStringUTFChars(model_path_str, model_path_chars);
|
|
213
|
+
rnwhisper_jsi::addContext(context_id, reinterpret_cast<jlong>(context));
|
|
208
214
|
return reinterpret_cast<jlong>(context);
|
|
209
215
|
}
|
|
210
216
|
|
|
@@ -542,11 +548,12 @@ Java_com_rnwhisper_WhisperContext_getTextSegmentT1(
|
|
|
542
548
|
|
|
543
549
|
JNIEXPORT void JNICALL
|
|
544
550
|
Java_com_rnwhisper_WhisperContext_freeContext(
|
|
545
|
-
JNIEnv *env, jobject thiz, jlong context_ptr) {
|
|
551
|
+
JNIEnv *env, jobject thiz, jint context_id, jlong context_ptr) {
|
|
546
552
|
UNUSED(env);
|
|
547
553
|
UNUSED(thiz);
|
|
548
554
|
struct whisper_context *context = reinterpret_cast<struct whisper_context *>(context_ptr);
|
|
549
555
|
whisper_free(context);
|
|
556
|
+
rnwhisper_jsi::removeContext(context_id);
|
|
550
557
|
}
|
|
551
558
|
|
|
552
559
|
JNIEXPORT jboolean JNICALL
|
|
@@ -576,6 +583,7 @@ JNIEXPORT jlong JNICALL
|
|
|
576
583
|
Java_com_rnwhisper_WhisperContext_initVadContext(
|
|
577
584
|
JNIEnv *env,
|
|
578
585
|
jobject thiz,
|
|
586
|
+
jint context_id,
|
|
579
587
|
jstring model_path_str
|
|
580
588
|
) {
|
|
581
589
|
UNUSED(thiz);
|
|
@@ -585,6 +593,7 @@ Java_com_rnwhisper_WhisperContext_initVadContext(
|
|
|
585
593
|
const char *model_path_chars = env->GetStringUTFChars(model_path_str, nullptr);
|
|
586
594
|
vad_context = whisper_vad_init_from_file_with_params(model_path_chars, vad_params);
|
|
587
595
|
env->ReleaseStringUTFChars(model_path_str, model_path_chars);
|
|
596
|
+
rnwhisper_jsi::addVadContext(context_id, reinterpret_cast<jlong>(vad_context));
|
|
588
597
|
return reinterpret_cast<jlong>(vad_context);
|
|
589
598
|
}
|
|
590
599
|
|
|
@@ -592,6 +601,7 @@ JNIEXPORT jlong JNICALL
|
|
|
592
601
|
Java_com_rnwhisper_WhisperContext_initVadContextWithAsset(
|
|
593
602
|
JNIEnv *env,
|
|
594
603
|
jobject thiz,
|
|
604
|
+
jint context_id,
|
|
595
605
|
jobject asset_manager,
|
|
596
606
|
jstring model_path_str
|
|
597
607
|
) {
|
|
@@ -602,6 +612,7 @@ Java_com_rnwhisper_WhisperContext_initVadContextWithAsset(
|
|
|
602
612
|
const char *model_path_chars = env->GetStringUTFChars(model_path_str, nullptr);
|
|
603
613
|
vad_context = whisper_vad_init_from_asset(env, asset_manager, model_path_chars, vad_params);
|
|
604
614
|
env->ReleaseStringUTFChars(model_path_str, model_path_chars);
|
|
615
|
+
rnwhisper_jsi::addVadContext(context_id, reinterpret_cast<jlong>(vad_context));
|
|
605
616
|
return reinterpret_cast<jlong>(vad_context);
|
|
606
617
|
}
|
|
607
618
|
|
|
@@ -609,6 +620,7 @@ JNIEXPORT jlong JNICALL
|
|
|
609
620
|
Java_com_rnwhisper_WhisperContext_initVadContextWithInputStream(
|
|
610
621
|
JNIEnv *env,
|
|
611
622
|
jobject thiz,
|
|
623
|
+
jint context_id,
|
|
612
624
|
jobject input_stream
|
|
613
625
|
) {
|
|
614
626
|
UNUSED(thiz);
|
|
@@ -616,6 +628,7 @@ Java_com_rnwhisper_WhisperContext_initVadContextWithInputStream(
|
|
|
616
628
|
|
|
617
629
|
struct whisper_vad_context *vad_context = nullptr;
|
|
618
630
|
vad_context = whisper_vad_init_from_input_stream(env, input_stream, vad_params);
|
|
631
|
+
rnwhisper_jsi::addVadContext(context_id, reinterpret_cast<jlong>(vad_context));
|
|
619
632
|
return reinterpret_cast<jlong>(vad_context);
|
|
620
633
|
}
|
|
621
634
|
|
|
@@ -623,12 +636,14 @@ JNIEXPORT void JNICALL
|
|
|
623
636
|
Java_com_rnwhisper_WhisperContext_freeVadContext(
|
|
624
637
|
JNIEnv *env,
|
|
625
638
|
jobject thiz,
|
|
639
|
+
jint context_id,
|
|
626
640
|
jlong vad_context_ptr
|
|
627
641
|
) {
|
|
628
642
|
UNUSED(env);
|
|
629
643
|
UNUSED(thiz);
|
|
630
644
|
struct whisper_vad_context *vad_context = reinterpret_cast<struct whisper_vad_context *>(vad_context_ptr);
|
|
631
645
|
whisper_vad_free(vad_context);
|
|
646
|
+
rnwhisper_jsi::removeVadContext(context_id);
|
|
632
647
|
}
|
|
633
648
|
|
|
634
649
|
JNIEXPORT jboolean JNICALL
|
|
@@ -726,4 +741,66 @@ Java_com_rnwhisper_WhisperContext_vadFreeSegments(
|
|
|
726
741
|
whisper_vad_free_segments(segments);
|
|
727
742
|
}
|
|
728
743
|
|
|
744
|
+
// JSI Installation function using fbjni
|
|
745
|
+
JNIEXPORT void JNICALL
|
|
746
|
+
Java_com_rnwhisper_WhisperContext_installJSIBindings(
|
|
747
|
+
JNIEnv *env,
|
|
748
|
+
jclass clazz,
|
|
749
|
+
jlong runtimePtr,
|
|
750
|
+
jobject callInvokerHolder
|
|
751
|
+
) {
|
|
752
|
+
auto runtime = reinterpret_cast<facebook::jsi::Runtime*>(runtimePtr);
|
|
753
|
+
|
|
754
|
+
if (runtime == nullptr) {
|
|
755
|
+
LOGW("Runtime is null, cannot install JSI bindings");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
std::shared_ptr<facebook::react::CallInvoker> callInvoker = nullptr;
|
|
760
|
+
|
|
761
|
+
if (callInvokerHolder != nullptr) {
|
|
762
|
+
try {
|
|
763
|
+
// Use fbjni for type-safe access to CallInvoker
|
|
764
|
+
auto holder = facebook::jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>{
|
|
765
|
+
reinterpret_cast<facebook::react::CallInvokerHolder::javaobject>(callInvokerHolder)
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
if (holder) {
|
|
769
|
+
callInvoker = holder->cthis()->getCallInvoker();
|
|
770
|
+
LOGI("Successfully obtained CallInvoker using fbjni");
|
|
771
|
+
}
|
|
772
|
+
} catch (const std::exception& e) {
|
|
773
|
+
LOGW("Failed to obtain CallInvoker: %s", e.what());
|
|
774
|
+
} catch (...) {
|
|
775
|
+
LOGW("Failed to obtain CallInvoker: unknown error");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (callInvoker == nullptr) {
|
|
780
|
+
LOGW("CallInvoker is null, cannot install JSI bindings");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
callInvoker->invokeAsync([runtime, callInvoker]() {
|
|
785
|
+
try {
|
|
786
|
+
rnwhisper_jsi::installJSIBindings(*runtime, callInvoker);
|
|
787
|
+
LOGI("JSI bindings installed successfully on JS thread");
|
|
788
|
+
} catch (const facebook::jsi::JSError& e) {
|
|
789
|
+
LOGW("JSError installing JSI bindings: %s", e.getMessage().c_str());
|
|
790
|
+
} catch (const std::exception& e) {
|
|
791
|
+
LOGW("Exception installing JSI bindings: %s", e.what());
|
|
792
|
+
} catch (...) {
|
|
793
|
+
LOGW("Unknown error installing JSI bindings");
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
JNIEXPORT void JNICALL
|
|
799
|
+
Java_com_rnwhisper_WhisperContext_cleanupJSIBindings(
|
|
800
|
+
JNIEnv *env,
|
|
801
|
+
jclass clazz
|
|
802
|
+
) {
|
|
803
|
+
rnwhisper_jsi::cleanupJSIBindings();
|
|
804
|
+
}
|
|
805
|
+
|
|
729
806
|
} // extern "C"
|
|
@@ -26,6 +26,11 @@ public class RNWhisperModule extends NativeRNWhisperSpec {
|
|
|
26
26
|
rnwhisper = new RNWhisper(reactContext);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
@ReactMethod
|
|
30
|
+
public void installJSIBindings(Promise promise) {
|
|
31
|
+
rnwhisper.installJSIBindings(promise);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
@Override
|
|
30
35
|
@NonNull
|
|
31
36
|
public String getName() {
|
|
@@ -26,6 +26,11 @@ public class RNWhisperModule extends ReactContextBaseJavaModule {
|
|
|
26
26
|
rnwhisper = new RNWhisper(reactContext);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
@ReactMethod
|
|
30
|
+
public void installJSIBindings(Promise promise) {
|
|
31
|
+
rnwhisper.installJSIBindings(promise);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
@Override
|
|
30
35
|
@NonNull
|
|
31
36
|
public String getName() {
|