Skip to content

Commit

Permalink
feat: support react-native new architecture (#65)
Browse files Browse the repository at this point in the history
* feat(android): support new architecture

* feat(ios): support new architecture

* feat(android): improve type conversation in releaseContext

* fix: constants type defs

* chore: docgen

* fix(ios): requiresMainQueueSetup
  • Loading branch information
jhen0409 committed Jun 30, 2023
1 parent f3df332 commit db75c27
Show file tree
Hide file tree
Showing 16 changed files with 779 additions and 104 deletions.
12 changes: 11 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,17 @@
"@typescript-eslint/no-shadow": 1,
"no-undef": "off",
"func-call-spacing": "off",
"@typescript-eslint/func-call-spacing": 1
"@typescript-eslint/func-call-spacing": 1,
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
]
}
}
],
Expand Down
9 changes: 9 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
if (isNewArchitectureEnabled()) {
java.srcDirs += ['src/newarch']
} else {
java.srcDirs += ['src/oldarch']
}
}
}
}

repositories {
Expand Down
46 changes: 33 additions & 13 deletions android/src/main/java/com/rnwhisper/RNWhisperPackage.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
package com.rnwhisper;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.TurboReactPackage;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class RNWhisperPackage implements ReactPackage {
@NonNull
public class RNWhisperPackage extends TurboReactPackage {

@Nullable
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNWhisperModule(reactContext));
return modules;
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(RNWhisperModule.NAME)) {
return new com.rnwhisper.RNWhisperModule(reactContext);
} else {
return null;
}
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return () -> {
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
moduleInfos.put(
RNWhisperModule.NAME,
new ReactModuleInfo(
RNWhisperModule.NAME,
RNWhisperModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
)
);
return moduleInfos;
};
}
}
232 changes: 232 additions & 0 deletions android/src/newarch/java/com/rnwhisper/RNWhisperModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package com.rnwhisper;

import androidx.annotation.NonNull;
import android.util.Log;
import android.os.Build;
import android.os.Handler;
import android.os.AsyncTask;
import android.media.AudioRecord;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;

import java.util.HashMap;
import java.util.Random;

@ReactModule(name = RNWhisperModule.NAME)
public class RNWhisperModule extends NativeRNWhisperSpec implements LifecycleEventListener {
public static final String NAME = "RNWhisper";

private ReactApplicationContext reactContext;

public RNWhisperModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addLifecycleEventListener(this);
this.reactContext = reactContext;
}

@Override
@NonNull
public String getName() {
return NAME;
}

@Override
public HashMap<String, Object> getTypedExportedConstants() {
HashMap<String, Object> constants = new HashMap<>();

// iOS only constants, put for passing type checks
constants.put("useCoreML", false);
constants.put("coreMLAllowFallback", false);

return constants;
}

private HashMap<Integer, WhisperContext> contexts = new HashMap<>();

@ReactMethod
public void initContext(final String modelPath, final boolean isBundleAsset, final Promise promise) {
new AsyncTask<Void, Void, Integer>() {
private Exception exception;

@Override
protected Integer doInBackground(Void... voids) {
try {
long context;
if (isBundleAsset) {
context = WhisperContext.initContextWithAsset(reactContext.getAssets(), modelPath);
} else {
context = WhisperContext.initContext(modelPath);
}
if (context == 0) {
throw new Exception("Failed to initialize context");
}
int id = Math.abs(new Random().nextInt());
WhisperContext whisperContext = new WhisperContext(id, reactContext, context);
contexts.put(id, whisperContext);
return id;
} catch (Exception e) {
exception = e;
return null;
}
}

@Override
protected void onPostExecute(Integer id) {
if (exception != null) {
promise.reject(exception);
return;
}
promise.resolve(id);
}
}.execute();
}

@ReactMethod
public void transcribeFile(double id, double jobId, String filePath, ReadableMap options, Promise promise) {
final WhisperContext context = contexts.get((int) id);
if (context == null) {
promise.reject("Context not found");
return;
}
if (context.isCapturing()) {
promise.reject("The context is in realtime transcribe mode");
return;
}
if (context.isTranscribing()) {
promise.reject("Context is already transcribing");
return;
}
new AsyncTask<Void, Void, WritableMap>() {
private Exception exception;

@Override
protected WritableMap doInBackground(Void... voids) {
try {
return context.transcribeFile((int) jobId, filePath, options);
} catch (Exception e) {
exception = e;
return null;
}
}

@Override
protected void onPostExecute(WritableMap data) {
if (exception != null) {
promise.reject(exception);
return;
}
promise.resolve(data);
}
}.execute();
}

@ReactMethod
public void startRealtimeTranscribe(double id, double jobId, ReadableMap options, Promise promise) {
final WhisperContext context = contexts.get((int) id);
if (context == null) {
promise.reject("Context not found");
return;
}
if (context.isCapturing()) {
promise.reject("Context is already in capturing");
return;
}
int state = context.startRealtimeTranscribe((int) jobId, options);
if (state == AudioRecord.STATE_INITIALIZED) {
promise.resolve(null);
return;
}
promise.reject("Failed to start realtime transcribe. State: " + state);
}

@ReactMethod
public void abortTranscribe(double contextId, double jobId, Promise promise) {
WhisperContext context = contexts.get((int) contextId);
if (context == null) {
promise.reject("Context not found");
return;
}
context.stopTranscribe((int) jobId);
}

@ReactMethod
public void releaseContext(double id, Promise promise) {
final int contextId = (int) id;
new AsyncTask<Void, Void, Void>() {
private Exception exception;

@Override
protected Void doInBackground(Void... voids) {
try {
WhisperContext context = contexts.get(contextId);
if (context == null) {
throw new Exception("Context " + id + " not found");
}
context.release();
contexts.remove(contextId);
} catch (Exception e) {
exception = e;
}
return null;
}

@Override
protected void onPostExecute(Void result) {
if (exception != null) {
promise.reject(exception);
return;
}
promise.resolve(null);
}
}.execute();
}

@ReactMethod
public void releaseAllContexts(Promise promise) {
new AsyncTask<Void, Void, Void>() {
private Exception exception;

@Override
protected Void doInBackground(Void... voids) {
try {
onHostDestroy();
} catch (Exception e) {
exception = e;
}
return null;
}

@Override
protected void onPostExecute(Void result) {
if (exception != null) {
promise.reject(exception);
return;
}
promise.resolve(null);
}
}.execute();
}

@Override
public void onHostResume() {
}

@Override
public void onHostPause() {
}

@Override
public void onHostDestroy() {
WhisperContext.abortAllTranscribe();
for (WhisperContext context : contexts.values()) {
context.release();
}
contexts.clear();
}
}
Loading

0 comments on commit db75c27

Please sign in to comment.