whisper.rn 0.4.3 → 0.5.0-rc.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/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
- By default, `whisper.rn` will use pre-built libraries for Android. If you want to build from source, please set `rnwhisperBuildFromSource` to `true` in `android/gradle.properties`.
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
 
@@ -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,46 @@ 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
- def rnwhisperBuildFromSource = project.properties["rnwhisperBuildFromSource"]
57
- if (rnwhisperBuildFromSource == "true") {
58
- externalNativeBuild {
59
- cmake {
60
- path = file('src/main/CMakeLists.txt')
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
+ ]
106
+ }
107
+
108
+ buildFeatures {
109
+ prefab = true
110
+ }
111
+ externalNativeBuild {
112
+ cmake {
113
+ path = file('src/main/CMakeLists.txt')
62
114
  }
63
- // Exclude jniLibs
64
- sourceSets {
65
- main {
66
- jniLibs.srcDirs = []
67
- }
115
+ }
116
+ // Exclude jniLibs when building from source
117
+ sourceSets {
118
+ main {
119
+ jniLibs.srcDirs = []
68
120
  }
69
121
  }
70
122
  buildTypes {
@@ -113,3 +165,10 @@ if (isNewArchitectureEnabled()) {
113
165
  codegenJavaPackageName = "com.rnwhisper"
114
166
  }
115
167
  }
168
+
169
+ def resolveBuildType() {
170
+ Gradle gradle = getGradle()
171
+ String tskReqStr = gradle.getStartParameter().getTaskRequests()["args"].toString()
172
+
173
+ return tskReqStr.contains("Release") ? "release" : "debug"
174
+ }
@@ -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
- target_link_libraries(${target_name} ${LOG_LIB} android)
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, Integer>() {
115
+ AsyncTask task = new AsyncTask<Void, Void, WhisperContext>() {
71
116
  private Exception exception;
72
117
 
73
118
  @Override
74
- protected Integer doInBackground(Void... voids) {
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 id;
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(Integer id) {
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", id);
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
  }
@@ -94,7 +94,7 @@ public class WhisperVadContext {
94
94
 
95
95
  public void release() {
96
96
  if (vadContext != 0) {
97
- WhisperContext.freeVadContext(vadContext);
97
+ WhisperContext.freeVadContext(id, vadContext);
98
98
  vadContext = 0;
99
99
  }
100
100
  }
@@ -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() {