diff --git a/android/build.gradle b/android/build.gradle index f82579e..0216124 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -84,6 +84,9 @@ def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['VisionCamera_' + name]).toInteger() } +def reanimated = rootProject.subprojects.find { it.name == 'react-native-reanimated' } +def ENABLE_FRAME_PROCESSORS = !getExtOrDefault("disableFrameProcessors") && reanimated != null + android { compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') buildToolsVersion getExtOrDefault('buildToolsVersion') @@ -93,14 +96,16 @@ android { minSdkVersion 21 targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') - externalNativeBuild { - cmake { - cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" - abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' - arguments '-DANDROID_STL=c++_shared', - "-DREACT_NATIVE_VERSION=${REACT_NATIVE_VERSION}", - "-DNODE_MODULES_DIR=${rootDir}/../node_modules", - "-DFOR_HERMES=${FOR_HERMES}" + if (ENABLE_FRAME_PROCESSORS) { + externalNativeBuild { + cmake { + cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + arguments '-DANDROID_STL=c++_shared', + "-DREACT_NATIVE_VERSION=${REACT_NATIVE_VERSION}", + "-DNODE_MODULES_DIR=${rootDir}/../node_modules", + "-DFOR_HERMES=${FOR_HERMES}" + } } } } @@ -109,9 +114,11 @@ android { javaMaxHeapSize "4g" } - externalNativeBuild { - cmake { - path "CMakeLists.txt" + if (ENABLE_FRAME_PROCESSORS) { + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } } } @@ -217,25 +224,28 @@ def kotlin_version = getExtOrDefault('kotlinVersion') dependencies { // noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' - implementation project(':react-native-reanimated') - //noinspection GradleDynamicVersion - extractHeaders("com.facebook.fbjni:fbjni:+:headers") - //noinspection GradleDynamicVersion - extractJNI("com.facebook.fbjni:fbjni:+") + if (ENABLE_FRAME_PROCESSORS) { + implementation project(':react-native-reanimated') - def rnAAR = fileTree("${rootDir}/../node_modules/react-native/android").matching({ it.include "**/**/*.aar" }).singleFile - def jscAAR = fileTree("${rootDir}/../node_modules/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile + //noinspection GradleDynamicVersion + extractHeaders("com.facebook.fbjni:fbjni:+:headers") + //noinspection GradleDynamicVersion + extractJNI("com.facebook.fbjni:fbjni:+") - def inputFile = new File(rootDir, '../node_modules/react-native/package.json') - def json = new JsonSlurper().parseText(inputFile.text) - def reactNativeVersion = json.version as String - def (major, minor, patch) = reactNativeVersion.tokenize('.') + def rnAAR = fileTree("${rootDir}/../node_modules/react-native/android").matching({ it.include "**/**/*.aar" }).singleFile + def jscAAR = fileTree("${rootDir}/../node_modules/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile - def jsEngine = FOR_HERMES ? "hermes" : "jsc" - def reaAAR = "${rootDir}/../node_modules/react-native-reanimated/android/react-native-reanimated-${minor}-${jsEngine}.aar" + def inputFile = new File(rootDir, '../node_modules/react-native/package.json') + def json = new JsonSlurper().parseText(inputFile.text) + def reactNativeVersion = json.version as String + def (major, minor, patch) = reactNativeVersion.tokenize('.') - extractJNI(files(rnAAR, jscAAR, reaAAR)) + def jsEngine = FOR_HERMES ? "hermes" : "jsc" + def reaAAR = "${rootDir}/../node_modules/react-native-reanimated/android/react-native-reanimated-${minor}-${jsEngine}.aar" + + extractJNI(files(rnAAR, jscAAR, reaAAR)) + } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.5.2" @@ -252,236 +262,239 @@ dependencies { implementation "androidx.exifinterface:exifinterface:1.3.3" } -// third-party-ndk deps headers -// mostly a copy of https://github.com/software-mansion/react-native-reanimated/blob/master/android/build.gradle#L115 -def downloadsDir = new File("$buildDir/downloads") -def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") -def thirdPartyVersionsFile = new File("${rootDir}/../node_modules/react-native/ReactAndroid/gradle.properties") -def thirdPartyVersions = new Properties() -thirdPartyVersions.load(new FileInputStream(thirdPartyVersionsFile)) +if (ENABLE_FRAME_PROCESSORS) { + // third-party-ndk deps headers + // mostly a copy of https://github.com/software-mansion/react-native-reanimated/blob/master/android/build.gradle#L115 -def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] -def boost_file = new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz") -def DOUBLE_CONVERSION_VERSION = thirdPartyVersions["DOUBLE_CONVERSION_VERSION"] -def double_conversion_file = new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz") -def FOLLY_VERSION = thirdPartyVersions["FOLLY_VERSION"] -def folly_file = new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz") -def GLOG_VERSION = thirdPartyVersions["GLOG_VERSION"] -def glog_file = new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz") + def downloadsDir = new File("$buildDir/downloads") + def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") + def thirdPartyVersionsFile = new File("${rootDir}/../node_modules/react-native/ReactAndroid/gradle.properties") + def thirdPartyVersions = new Properties() + thirdPartyVersions.load(new FileInputStream(thirdPartyVersionsFile)) -task createNativeDepsDirectories { - doLast { - downloadsDir.mkdirs() - thirdPartyNdkDir.mkdirs() - } -} + def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] + def boost_file = new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz") + def DOUBLE_CONVERSION_VERSION = thirdPartyVersions["DOUBLE_CONVERSION_VERSION"] + def double_conversion_file = new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz") + def FOLLY_VERSION = thirdPartyVersions["FOLLY_VERSION"] + def folly_file = new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz") + def GLOG_VERSION = thirdPartyVersions["GLOG_VERSION"] + def glog_file = new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz") -task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/react-native-community/boost-for-react-native/releases/download/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(boost_file) -} - -task prepareBoost(dependsOn: downloadBoost, type: Copy) { - from(tarTree(resources.gzip(downloadBoost.dest))) - from("src/main/jni/third-party/boost/Android.mk") - include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") - includeEmptyDirs = false - into("$thirdPartyNdkDir") // /boost_X_XX_X - doLast { - file("$thirdPartyNdkDir/boost_${BOOST_VERSION}").renameTo("$thirdPartyNdkDir/boost") - } -} - -task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(double_conversion_file) -} - -task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) { - from(tarTree(downloadDoubleConversion.dest)) - from("src/main/jni/third-party/double-conversion/Android.mk") - include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") - filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) - includeEmptyDirs = false - into("$thirdPartyNdkDir/double-conversion") -} - -task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(folly_file) -} - -task prepareFolly(dependsOn: downloadFolly, type: Copy) { - from(tarTree(downloadFolly.dest)) - from("src/main/jni/third-party/folly/Android.mk") - include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") - eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } - includeEmptyDirs = false - into("$thirdPartyNdkDir/folly") -} - -task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(glog_file) -} - -task prepareGlog(dependsOn: downloadGlog, type: Copy) { - from(tarTree(downloadGlog.dest)) - from("src/main/jni/third-party/glog/") - include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h") - includeEmptyDirs = false - filesMatching("**/*.h.in") { - filter(ReplaceTokens, tokens: [ - ac_cv_have_unistd_h : "1", - ac_cv_have_stdint_h : "1", - ac_cv_have_systypes_h : "1", - ac_cv_have_inttypes_h : "1", - ac_cv_have_libgflags : "0", - ac_google_start_namespace : "namespace google {", - ac_cv_have_uint16_t : "1", - ac_cv_have_u_int16_t : "1", - ac_cv_have___uint16 : "0", - ac_google_end_namespace : "}", - ac_cv_have___builtin_expect : "1", - ac_google_namespace : "google", - ac_cv___attribute___noinline : "__attribute__ ((noinline))", - ac_cv___attribute___noreturn : "__attribute__ ((noreturn))", - ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))" - ]) - it.path = (it.name - ".in") - } - into("$thirdPartyNdkDir/glog") - - doLast { - copy { - from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files) - includeEmptyDirs = false - into("$thirdPartyNdkDir/glog/exported/glog") + task createNativeDepsDirectories { + doLast { + downloadsDir.mkdirs() + thirdPartyNdkDir.mkdirs() } } -} -task prepareThirdPartyNdkHeaders { - if (!boost_file.exists()) { - dependsOn(prepareBoost) + task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { + src("https://github.com/react-native-community/boost-for-react-native/releases/download/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(boost_file) } - if (!double_conversion_file.exists()) { - dependsOn(prepareDoubleConversion) - } - if (!folly_file.exists()) { - dependsOn(prepareFolly) - } - if (!glog_file.exists()) { - dependsOn(prepareGlog) - } -} -prepareThirdPartyNdkHeaders.mustRunAfter createNativeDepsDirectories - -task prepareHermes() { - doLast { - def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine") - if (!hermesPackagePath) { - throw new GradleScriptException("Could not find the hermes-engine npm package", null) - } - - def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar") - if (!hermesAAR.exists()) { - throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"", null) - } - - def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" }) - - copy { - from soFiles - from "$reactNative/ReactAndroid/src/main/jni/first-party/hermes/Android.mk" - into "$thirdPartyNdkDir/hermes" + task prepareBoost(dependsOn: downloadBoost, type: Copy) { + from(tarTree(resources.gzip(downloadBoost.dest))) + from("src/main/jni/third-party/boost/Android.mk") + include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") + includeEmptyDirs = false + into("$thirdPartyNdkDir") // /boost_X_XX_X + doLast { + file("$thirdPartyNdkDir/boost_${BOOST_VERSION}").renameTo("$thirdPartyNdkDir/boost") } } -} -prepareHermes.mustRunAfter prepareThirdPartyNdkHeaders - -task prepareJSC { - doLast { - def jscPackagePath = findNodeModulePath(projectDir, "jsc-android") - if (!jscPackagePath) { - throw new GradleScriptException("Could not find the jsc-android npm package", null) - } - - def jscDist = file("$jscPackagePath/dist") - if (!jscDist.exists()) { - throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory", null) - } - - def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile - def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" }) - - def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" }) - - copy { - from(soFiles) - from(headerFiles) - from("$reactNative/ReactAndroid/src/main/jni/third-party/jsc/Android.mk") - - filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" }) - - includeEmptyDirs(false) - into("$thirdPartyNdkDir/jsc") - } + task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { + src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(double_conversion_file) } -} -prepareJSC.mustRunAfter prepareHermes + task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) { + from(tarTree(downloadDoubleConversion.dest)) + from("src/main/jni/third-party/double-conversion/Android.mk") + include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") + filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) + includeEmptyDirs = false + into("$thirdPartyNdkDir/double-conversion") + } -task extractAARHeaders { - doLast { - configurations.extractHeaders.files.each { - def file = it.absoluteFile + task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { + src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(folly_file) + } + + task prepareFolly(dependsOn: downloadFolly, type: Copy) { + from(tarTree(downloadFolly.dest)) + from("src/main/jni/third-party/folly/Android.mk") + include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") + eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } + includeEmptyDirs = false + into("$thirdPartyNdkDir/folly") + } + + task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { + src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(glog_file) + } + + task prepareGlog(dependsOn: downloadGlog, type: Copy) { + from(tarTree(downloadGlog.dest)) + from("src/main/jni/third-party/glog/") + include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h") + includeEmptyDirs = false + filesMatching("**/*.h.in") { + filter(ReplaceTokens, tokens: [ + ac_cv_have_unistd_h : "1", + ac_cv_have_stdint_h : "1", + ac_cv_have_systypes_h : "1", + ac_cv_have_inttypes_h : "1", + ac_cv_have_libgflags : "0", + ac_google_start_namespace : "namespace google {", + ac_cv_have_uint16_t : "1", + ac_cv_have_u_int16_t : "1", + ac_cv_have___uint16 : "0", + ac_google_end_namespace : "}", + ac_cv_have___builtin_expect : "1", + ac_google_namespace : "google", + ac_cv___attribute___noinline : "__attribute__ ((noinline))", + ac_cv___attribute___noreturn : "__attribute__ ((noreturn))", + ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))" + ]) + it.path = (it.name - ".in") + } + into("$thirdPartyNdkDir/glog") + + doLast { copy { - from zipTree(file) - into "$buildDir/$file.name" - include "**/*.h" + from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files) + includeEmptyDirs = false + into("$thirdPartyNdkDir/glog/exported/glog") } } } -} -extractAARHeaders.mustRunAfter prepareJSC + task prepareThirdPartyNdkHeaders { + if (!boost_file.exists()) { + dependsOn(prepareBoost) + } + if (!double_conversion_file.exists()) { + dependsOn(prepareDoubleConversion) + } + if (!folly_file.exists()) { + dependsOn(prepareFolly) + } + if (!glog_file.exists()) { + dependsOn(prepareGlog) + } + } -task extractJNIFiles { - doLast { - configurations.extractJNI.files.each { - def file = it.absoluteFile + prepareThirdPartyNdkHeaders.mustRunAfter createNativeDepsDirectories + + task prepareHermes() { + doLast { + def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine") + if (!hermesPackagePath) { + throw new GradleScriptException("Could not find the hermes-engine npm package", null) + } + + def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar") + if (!hermesAAR.exists()) { + throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"", null) + } + + def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" }) copy { - from zipTree(file) - into "$buildDir/$file.name" - include "jni/**/*" + from soFiles + from "$reactNative/ReactAndroid/src/main/jni/first-party/hermes/Android.mk" + into "$thirdPartyNdkDir/hermes" } } } -} -extractJNIFiles.mustRunAfter extractAARHeaders + prepareHermes.mustRunAfter prepareThirdPartyNdkHeaders -// pre-native build pipeline + task prepareJSC { + doLast { + def jscPackagePath = findNodeModulePath(projectDir, "jsc-android") + if (!jscPackagePath) { + throw new GradleScriptException("Could not find the jsc-android npm package", null) + } -tasks.whenTaskAdded { task -> - if (!task.name.contains('Clean') && (task.name.contains('externalNative') || task.name.contains('CMake'))) { - task.dependsOn(extractAARHeaders) - task.dependsOn(extractJNIFiles) - task.dependsOn(prepareJSC) - task.dependsOn(prepareHermes) - task.dependsOn(prepareThirdPartyNdkHeaders) + def jscDist = file("$jscPackagePath/dist") + if (!jscDist.exists()) { + throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory", null) + } + + def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile + def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" }) + + def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" }) + + copy { + from(soFiles) + from(headerFiles) + from("$reactNative/ReactAndroid/src/main/jni/third-party/jsc/Android.mk") + + filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" }) + + includeEmptyDirs(false) + into("$thirdPartyNdkDir/jsc") + } + } + } + + prepareJSC.mustRunAfter prepareHermes + + task extractAARHeaders { + doLast { + configurations.extractHeaders.files.each { + def file = it.absoluteFile + copy { + from zipTree(file) + into "$buildDir/$file.name" + include "**/*.h" + } + } + } + } + + extractAARHeaders.mustRunAfter prepareJSC + + task extractJNIFiles { + doLast { + configurations.extractJNI.files.each { + def file = it.absoluteFile + + copy { + from zipTree(file) + into "$buildDir/$file.name" + include "jni/**/*" + } + } + } + } + + extractJNIFiles.mustRunAfter extractAARHeaders + + // pre-native build pipeline + + tasks.whenTaskAdded { task -> + if (!task.name.contains('Clean') && (task.name.contains('externalNative') || task.name.contains('CMake'))) { + task.dependsOn(extractAARHeaders) + task.dependsOn(extractJNIFiles) + task.dependsOn(prepareJSC) + task.dependsOn(prepareHermes) + task.dependsOn(prepareThirdPartyNdkHeaders) + } } } diff --git a/android/gradle.properties b/android/gradle.properties index d171611..3751dfe 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -16,5 +16,6 @@ VisionCamera_compileSdkVersion=31 VisionCamera_kotlinVersion=1.5.30 VisionCamera_targetSdkVersion=31 VisionCamera_ndkVersion=21.4.7075529 +VisionCamera_disableFrameProcessors=false android.enableJetifier=true android.useAndroidX=true diff --git a/android/src/main/java/com/mrousavy/camera/CameraView.kt b/android/src/main/java/com/mrousavy/camera/CameraView.kt index 171c67d..1b1f6f8 100644 --- a/android/src/main/java/com/mrousavy/camera/CameraView.kt +++ b/android/src/main/java/com/mrousavy/camera/CameraView.kt @@ -26,6 +26,7 @@ import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter import com.mrousavy.camera.frameprocessor.FrameProcessorPerformanceDataCollector +import com.mrousavy.camera.frameprocessor.FrameProcessorRuntimeManager import com.mrousavy.camera.utils.* import kotlinx.coroutines.* import kotlinx.coroutines.guava.await @@ -159,7 +160,7 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer } @DoNotStrip - private var mHybridData: HybridData + private var mHybridData: HybridData? = null @Suppress("LiftReturnOrAssignment", "RedundantIf") internal val fallbackToSnapshot: Boolean @@ -192,7 +193,9 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer } init { - mHybridData = initHybrid() + if (FrameProcessorRuntimeManager.enableFrameProcessors) { + mHybridData = initHybrid() + } previewView = PreviewView(context) previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) diff --git a/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt index d900f30..92dcc93 100644 --- a/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt +++ b/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt @@ -57,10 +57,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase if (frameProcessorManager == null) { frameProcessorThread.execute { frameProcessorManager = FrameProcessorRuntimeManager(reactApplicationContext, frameProcessorThread) - - reactApplicationContext.runOnJSQueueThread { - frameProcessorManager!!.installJSIBindings() - } } } } diff --git a/android/src/main/java/com/mrousavy/camera/frameprocessor/FrameProcessorRuntimeManager.kt b/android/src/main/java/com/mrousavy/camera/frameprocessor/FrameProcessorRuntimeManager.kt index 990af78..bb1781b 100644 --- a/android/src/main/java/com/mrousavy/camera/frameprocessor/FrameProcessorRuntimeManager.kt +++ b/android/src/main/java/com/mrousavy/camera/frameprocessor/FrameProcessorRuntimeManager.kt @@ -8,7 +8,6 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.turbomodule.core.CallInvokerHolderImpl import com.mrousavy.camera.CameraView import com.mrousavy.camera.ViewNotFoundError -import com.swmansion.reanimated.Scheduler import java.lang.ref.WeakReference import java.util.concurrent.ExecutorService @@ -17,39 +16,52 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces companion object { const val TAG = "FrameProcessorRuntime" val Plugins: ArrayList = ArrayList() + var enableFrameProcessors = true init { - System.loadLibrary("reanimated") - System.loadLibrary("VisionCamera") + try { + System.loadLibrary("reanimated") + System.loadLibrary("VisionCamera") + } catch (e: UnsatisfiedLinkError) { + Log.w(TAG, "Failed to load Reanimated/VisionCamera C++ library. Frame Processors are disabled!") + enableFrameProcessors = false + } } } @DoNotStrip - private var mHybridData: HybridData - private var mContext: WeakReference - private var mScheduler: VisionCameraScheduler + private var mHybridData: HybridData? = null + private var mContext: WeakReference? = null + private var mScheduler: VisionCameraScheduler? = null init { - val holder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl - mScheduler = VisionCameraScheduler(frameProcessorThread) - mContext = WeakReference(context) - mHybridData = initHybrid(context.javaScriptContextHolder.get(), holder, mScheduler) - initializeRuntime() + if (enableFrameProcessors) { + val holder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl + mScheduler = VisionCameraScheduler(frameProcessorThread) + mContext = WeakReference(context) + mHybridData = initHybrid(context.javaScriptContextHolder.get(), holder, mScheduler!!) + initializeRuntime() - Log.i(TAG, "Installing Frame Processor Plugins...") - Plugins.forEach { plugin -> - registerPlugin(plugin) + Log.i(TAG, "Installing Frame Processor Plugins...") + Plugins.forEach { plugin -> + registerPlugin(plugin) + } + Log.i(TAG, "Successfully installed ${Plugins.count()} Frame Processor Plugins!") + + Log.i(TAG, "Installing JSI Bindings on JS Thread...") + context.runOnJSQueueThread { + installJSIBindings() + } } - Log.i(TAG, "Successfully installed ${Plugins.count()} Frame Processor Plugins!") } @Suppress("unused") @DoNotStrip @Keep fun findCameraViewById(viewId: Int): CameraView { - Log.d(TAG, "finding view $viewId...") - val view = mContext.get()?.currentActivity?.findViewById(viewId) - Log.d(TAG, "found view $viewId! is null: ${view == null}") + Log.d(TAG, "Finding view $viewId...") + val view = mContext?.get()?.currentActivity?.findViewById(viewId) + Log.d(TAG, if (view != null) "Found view $viewId!" else "Couldn't find view $viewId!") return view ?: throw ViewNotFoundError(viewId) } @@ -61,7 +73,5 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces ): HybridData private external fun initializeRuntime() private external fun registerPlugin(plugin: FrameProcessorPlugin) - - // public C++ funcs - external fun installJSIBindings() + private external fun installJSIBindings() } diff --git a/docs/docs/guides/FRAME_PROCESSORS.mdx b/docs/docs/guides/FRAME_PROCESSORS.mdx index 1e04e08..7c5d4a6 100644 --- a/docs/docs/guides/FRAME_PROCESSORS.mdx +++ b/docs/docs/guides/FRAME_PROCESSORS.mdx @@ -208,7 +208,21 @@ The Frame Processor gets called with a `Frame` object, which is a **JSI HostObje ### Disabling Frame Processors -The Frame Processor API spawns a secondary JavaScript Runtime which consumes a small amount of extra CPU and RAM. If you're not using Frame Processors at all, you can disable them by setting the `VISION_CAMERA_DISABLE_FRAME_PROCESSORS` flag. Inside your `project.pbxproj`, find the `GCC_PREPROCESSOR_DEFINITIONS` parameter and add the flag: +The Frame Processor API spawns a secondary JavaScript Runtime which consumes a small amount of extra CPU and RAM. Additionally, compile time increases since Frame Processors are written in native C++. If you're not using Frame Processors at all, you can disable them: + +#### Android + +Inside your `gradle.properties` file, add the `disableFrameProcessors` flag: + +``` +disableFrameProcessors=true +``` + +Then, clean and rebuild your project. + +#### iOS + +Inside your `project.pbxproj`, find the `GCC_PREPROCESSOR_DEFINITIONS` group and add the flag: ```txt {3} GCC_PREPROCESSOR_DEFINITIONS = ( diff --git a/ios/Frame Processor/FrameHostObject.h b/ios/Frame Processor/FrameHostObject.h index 091ded0..7cdadab 100644 --- a/ios/Frame Processor/FrameHostObject.h +++ b/ios/Frame Processor/FrameHostObject.h @@ -25,7 +25,7 @@ public: public: Frame* frame; - + private: void assertIsFrameStrong(jsi::Runtime& runtime, const std::string& accessedPropName); }; diff --git a/ios/Frame Processor/FrameProcessorRuntimeManager.mm b/ios/Frame Processor/FrameProcessorRuntimeManager.mm index 8ad6ea6..3841b20 100644 --- a/ios/Frame Processor/FrameProcessorRuntimeManager.mm +++ b/ios/Frame Processor/FrameProcessorRuntimeManager.mm @@ -25,6 +25,7 @@ #import #import #import + #import "VisionCameraScheduler.h" #define ENABLE_FRAME_PROCESSORS #else #warning Your react-native-reanimated version is not compatible with VisionCamera, Frame Processors are disabled. Make sure you're using reanimated 2.2.0 or above! @@ -36,7 +37,6 @@ #import "FrameProcessorUtils.h" #import "FrameProcessorCallback.h" -#import "VisionCameraScheduler.h" #import "../React Utils/MakeJSIRuntime.h" #import "../React Utils/JSIUtils.h" @@ -153,7 +153,7 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView"))) dispatch_async(CameraQueues.frameProcessorQueue, [=]() { NSLog(@"FrameProcessorBindings: Converting worklet to Objective-C callback..."); - + auto& rt = *runtimeManager->runtime; auto function = worklet->getValue(rt).asObject(rt).asFunction(rt); diff --git a/ios/Frame Processor/VisionCameraScheduler.h b/ios/Frame Processor/VisionCameraScheduler.h index c3b5336..07dfb70 100644 --- a/ios/Frame Processor/VisionCameraScheduler.h +++ b/ios/Frame Processor/VisionCameraScheduler.h @@ -8,18 +8,30 @@ #pragma once -#import +#include #import +#if __has_include() + #import +#else + // dummy placeholder + namespace reanimated { + class Scheduler { + public: + virtual void scheduleOnUI(std::function job); + protected: + std::shared_ptr jsCallInvoker_; + }; + } +#endif + namespace vision { -using namespace facebook; - class VisionCameraScheduler : public reanimated::Scheduler { public: - VisionCameraScheduler(std::shared_ptr jsInvoker); + VisionCameraScheduler(std::shared_ptr jsInvoker); virtual ~VisionCameraScheduler(); - + void scheduleOnUI(std::function job) override; }; diff --git a/ios/Frame Processor/VisionCameraScheduler.mm b/ios/Frame Processor/VisionCameraScheduler.mm index c005477..5067d64 100644 --- a/ios/Frame Processor/VisionCameraScheduler.mm +++ b/ios/Frame Processor/VisionCameraScheduler.mm @@ -18,9 +18,9 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera12CameraQueues"))) @end namespace vision { - + using namespace facebook; - + VisionCameraScheduler::VisionCameraScheduler(std::shared_ptr jsInvoker) { this->jsCallInvoker_ = jsInvoker; } @@ -34,6 +34,6 @@ void VisionCameraScheduler::scheduleOnUI(std::function job) { VisionCameraScheduler::~VisionCameraScheduler(){ } - - + + } // namespace vision