feat: Make Reanimated optional (disable Frame Processors if REA v2 is not installed) (#412)
* Fix building iOS without Reanimated * Conditionally compile Frame Processors (gradle) * Conditionally use externalNativeBuild * Remove Reanimated import * fix: Conditionally load REA/VisionCamera libraries * fix: Add disable FP to docs * fix: Fix dummy placeholder for Scheduler.mm * fix: Fix dummy `Scheduler` declaration * fix: Only init `CameraView` C++ side if frame processors are enabled * fix: Install JSI Bindings on Frame Processor Manager ctor * fix: Wrong conditional * whoops
This commit is contained in:
		@@ -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,6 +96,7 @@ android {
 | 
			
		||||
    minSdkVersion 21
 | 
			
		||||
    targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
 | 
			
		||||
 | 
			
		||||
    if (ENABLE_FRAME_PROCESSORS) {
 | 
			
		||||
      externalNativeBuild {
 | 
			
		||||
        cmake {
 | 
			
		||||
          cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID"
 | 
			
		||||
@@ -104,16 +108,19 @@ android {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dexOptions {
 | 
			
		||||
    javaMaxHeapSize "4g"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (ENABLE_FRAME_PROCESSORS) {
 | 
			
		||||
    externalNativeBuild {
 | 
			
		||||
      cmake {
 | 
			
		||||
        path "CMakeLists.txt"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  packagingOptions {
 | 
			
		||||
    // Exclude all Libraries that are already present in the user's app (through React Native or by him installing REA)
 | 
			
		||||
@@ -217,6 +224,8 @@ def kotlin_version = getExtOrDefault('kotlinVersion')
 | 
			
		||||
dependencies {
 | 
			
		||||
  // noinspection GradleDynamicVersion
 | 
			
		||||
  implementation 'com.facebook.react:react-native:+'
 | 
			
		||||
 | 
			
		||||
  if (ENABLE_FRAME_PROCESSORS) {
 | 
			
		||||
    implementation project(':react-native-reanimated')
 | 
			
		||||
 | 
			
		||||
    //noinspection GradleDynamicVersion
 | 
			
		||||
@@ -236,6 +245,7 @@ dependencies {
 | 
			
		||||
    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,6 +262,8 @@ dependencies {
 | 
			
		||||
  implementation "androidx.exifinterface:exifinterface:1.3.3"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@@ -485,3 +497,4 @@ tasks.whenTaskAdded { task ->
 | 
			
		||||
      task.dependsOn(prepareThirdPartyNdkHeaders)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
    if (FrameProcessorRuntimeManager.enableFrameProcessors) {
 | 
			
		||||
      mHybridData = initHybrid()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    previewView = PreviewView(context)
 | 
			
		||||
    previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
 | 
			
		||||
 
 | 
			
		||||
@@ -57,10 +57,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
 | 
			
		||||
    if (frameProcessorManager == null) {
 | 
			
		||||
      frameProcessorThread.execute {
 | 
			
		||||
        frameProcessorManager = FrameProcessorRuntimeManager(reactApplicationContext, frameProcessorThread)
 | 
			
		||||
 | 
			
		||||
        reactApplicationContext.runOnJSQueueThread {
 | 
			
		||||
          frameProcessorManager!!.installJSIBindings()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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,23 +16,30 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces
 | 
			
		||||
  companion object {
 | 
			
		||||
    const val TAG = "FrameProcessorRuntime"
 | 
			
		||||
    val Plugins: ArrayList<FrameProcessorPlugin> = ArrayList()
 | 
			
		||||
    var enableFrameProcessors = true
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
      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<ReactApplicationContext>
 | 
			
		||||
  private var mScheduler: VisionCameraScheduler
 | 
			
		||||
  private var mHybridData: HybridData? = null
 | 
			
		||||
  private var mContext: WeakReference<ReactApplicationContext>? = null
 | 
			
		||||
  private var mScheduler: VisionCameraScheduler? = null
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    if (enableFrameProcessors) {
 | 
			
		||||
      val holder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl
 | 
			
		||||
      mScheduler = VisionCameraScheduler(frameProcessorThread)
 | 
			
		||||
      mContext = WeakReference(context)
 | 
			
		||||
    mHybridData = initHybrid(context.javaScriptContextHolder.get(), holder, mScheduler)
 | 
			
		||||
      mHybridData = initHybrid(context.javaScriptContextHolder.get(), holder, mScheduler!!)
 | 
			
		||||
      initializeRuntime()
 | 
			
		||||
 | 
			
		||||
      Log.i(TAG, "Installing Frame Processor Plugins...")
 | 
			
		||||
@@ -41,15 +47,21 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces
 | 
			
		||||
        registerPlugin(plugin)
 | 
			
		||||
      }
 | 
			
		||||
      Log.i(TAG, "Successfully installed ${Plugins.count()} Frame Processor Plugins!")
 | 
			
		||||
 | 
			
		||||
      Log.i(TAG, "Installing JSI Bindings on JS Thread...")
 | 
			
		||||
      context.runOnJSQueueThread {
 | 
			
		||||
        installJSIBindings()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Suppress("unused")
 | 
			
		||||
  @DoNotStrip
 | 
			
		||||
  @Keep
 | 
			
		||||
  fun findCameraViewById(viewId: Int): CameraView {
 | 
			
		||||
    Log.d(TAG, "finding view $viewId...")
 | 
			
		||||
    val view = mContext.get()?.currentActivity?.findViewById<CameraView>(viewId)
 | 
			
		||||
    Log.d(TAG, "found view $viewId! is null: ${view == null}")
 | 
			
		||||
    Log.d(TAG, "Finding view $viewId...")
 | 
			
		||||
    val view = mContext?.get()?.currentActivity?.findViewById<CameraView>(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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = (
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
      #import <RNReanimated/RuntimeManager.h>
 | 
			
		||||
      #import <RNReanimated/RuntimeDecorator.h>
 | 
			
		||||
      #import <RNReanimated/REAIOSErrorHandler.h>
 | 
			
		||||
      #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"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,16 +8,28 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#import <RNReanimated/Scheduler.h>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#import <React-callinvoker/ReactCommon/CallInvoker.h>
 | 
			
		||||
 | 
			
		||||
#if __has_include(<RNReanimated/RuntimeManager.h>)
 | 
			
		||||
  #import <RNReanimated/Scheduler.h>
 | 
			
		||||
#else
 | 
			
		||||
  // dummy placeholder
 | 
			
		||||
  namespace reanimated {
 | 
			
		||||
    class Scheduler {
 | 
			
		||||
    public:
 | 
			
		||||
      virtual void scheduleOnUI(std::function<void()> job);
 | 
			
		||||
    protected:
 | 
			
		||||
      std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker_;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace vision {
 | 
			
		||||
 | 
			
		||||
using namespace facebook;
 | 
			
		||||
 | 
			
		||||
class VisionCameraScheduler : public reanimated::Scheduler {
 | 
			
		||||
public:
 | 
			
		||||
  VisionCameraScheduler(std::shared_ptr<react::CallInvoker> jsInvoker);
 | 
			
		||||
  VisionCameraScheduler(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
 | 
			
		||||
  virtual ~VisionCameraScheduler();
 | 
			
		||||
 | 
			
		||||
  void scheduleOnUI(std::function<void()> job) override;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user