feat: Replace Reanimated with RN Worklets (#1468)

* Setup RN Worklets

* Use RN Worklets on iOS

* Fix console

* Add `installFrameProcessorBindings()` function

* Add `FrameProcessorPlugins` proxy (BREAKING CHANGE)

* Clean up docs

* Update FRAME_PROCESSORS.mdx

* Use RN Worklets 0.2.5

* feat: Android build setup

* Rewrite Android Frame Processor Part

* Update CMakeLists.txt

* fix: Add react-native-worklets Gradle dependency

* Update Podfile.lock

* fix build

* gradle:7.4.1

* Init JSI Bindings in method on Android

* Fix Folly flags

* fix: Init `FrameProcessorRuntimeManager` later

* fix: Wrap in `<GestureHandlerRootView>`

* Refactor plugins

* fix: Remove enableFrameProcessors

* Install RN Worklets from current GH master

* Update babel.config.js

* Update CameraViewModule.kt

* Update ImageProxyUtils.java

* feat: Upgrade to Reanimated v3

* fix: Fix crash on Worklet init

* Update RN Worklets to latest master

* fix: Simplify FP Plugins Proxy
This commit is contained in:
Marc Rousavy 2023-02-13 15:22:45 +01:00 committed by GitHub
parent 11d1e7178d
commit a0590dccb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 469 additions and 861 deletions

View File

@ -24,7 +24,7 @@ jobs:
with: with:
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}
reporter: github-pr-review reporter: github-pr-review
flags: --linelength=230 --exclude "android/src/main/cpp/reanimated-headers" flags: --linelength=230
targets: --recursive cpp android/src/main/cpp targets: --recursive cpp android/src/main/cpp
filter: "-legal/copyright\ filter: "-legal/copyright\
,-readability/todo\ ,-readability/todo\

View File

@ -64,4 +64,5 @@ Pod::Spec.new do |s|
s.dependency "React-callinvoker" s.dependency "React-callinvoker"
s.dependency "React" s.dependency "React"
s.dependency "React-Core" s.dependency "React-Core"
s.dependency "react-native-worklets"
end end

View File

@ -1,20 +1,23 @@
project(VisionCamera) project(VisionCamera)
cmake_minimum_required(VERSION 3.4.1) cmake_minimum_required(VERSION 3.9.0)
set (CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 14) set(PACKAGE_NAME "VisionCamera")
set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 17)
# Folly
include("${NODE_MODULES_DIR}/react-native/ReactAndroid/cmake-utils/folly-flags.cmake") include("${NODE_MODULES_DIR}/react-native/ReactAndroid/cmake-utils/folly-flags.cmake")
add_compile_options(${folly_FLAGS}) add_compile_options(${folly_FLAGS})
# Third party libraries (Prefabs)
set (PACKAGE_NAME "VisionCamera")
set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
# Consume shared libraries and headers from prefabs
find_package(fbjni REQUIRED CONFIG)
find_package(ReactAndroid REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG)
# VisionCamera shared find_package(fbjni REQUIRED CONFIG)
find_package(react-native-worklets REQUIRED CONFIG)
find_library(LOG_LIB log)
# Add react-native-vision-camera sources
add_library( add_library(
${PACKAGE_NAME} ${PACKAGE_NAME}
SHARED SHARED
@ -29,87 +32,24 @@ add_library(
src/main/cpp/java-bindings/JHashMap.cpp src/main/cpp/java-bindings/JHashMap.cpp
) )
# includes # Header Search Paths (includes)
target_include_directories( target_include_directories(
${PACKAGE_NAME} ${PACKAGE_NAME}
PRIVATE PRIVATE
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" "src/main/cpp"
"${NODE_MODULES_DIR}/react-native/ReactCommon" "${NODE_MODULES_DIR}/react-native/ReactCommon"
"${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker" "${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker"
"${NODE_MODULES_DIR}/react-native/ReactCommon/jsi" "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper
"${NODE_MODULES_DIR}/react-native/ReactCommon/react/renderer/graphics/platform/cxx"
"${NODE_MODULES_DIR}/react-native/ReactCommon/runtimeexecutor"
"${NODE_MODULES_DIR}/react-native/ReactCommon/yoga"
# --- Reanimated ---
# New
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/AnimatedSensor"
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/Tools"
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/SpecTools"
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/SharedItems"
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/Registries"
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/LayoutAnimations"
"${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/hidden_headers"
"src/main/cpp"
) )
# Link everything together
# find libraries
file (GLOB LIBRN_DIR "${BUILD_DIR}/react-native-0*/jni/${ANDROID_ABI}")
if(${FOR_HERMES})
string(APPEND CMAKE_CXX_FLAGS " -DFOR_HERMES=1")
find_package(hermes-engine REQUIRED CONFIG)
target_link_libraries(
${PACKAGE_NAME}
"hermes-engine::libhermes"
)
file (GLOB LIBREANIMATED_DIR "${BUILD_DIR}/react-native-reanimated-*-hermes.aar/jni/${ANDROID_ABI}")
else()
file (GLOB LIBJSC_DIR "${BUILD_DIR}/android-jsc*.aar/jni/${ANDROID_ABI}")
set(JS_ENGINE_LIB ReactAndroid::jscexecutor)
target_link_libraries(
${PACKAGE_NAME}
${JS_ENGINE_LIB}
)
# Use Reanimated JSC
file (GLOB LIBREANIMATED_DIR "${BUILD_DIR}/react-native-reanimated-*-jsc.aar/jni/${ANDROID_ABI}")
endif()
target_link_libraries( target_link_libraries(
${PACKAGE_NAME} ${PACKAGE_NAME}
ReactAndroid::folly_runtime ${LOG_LIB} # <-- Logcat logger
ReactAndroid::glog android # <-- Android JNI core
ReactAndroid::jsi ReactAndroid::jsi # <-- RN: JSI
ReactAndroid::reactnativejni ReactAndroid::reactnativejni # <-- RN: React Native JNI bindings
fbjni::fbjni ReactAndroid::folly_runtime # <-- RN: For casting JSI <> Java objects
) fbjni::fbjni # <-- fbjni
react-native-worklets::rnworklets # <-- RN Worklets
find_library(
REANIMATED_LIB
reanimated
PATHS ${LIBREANIMATED_DIR}
NO_CMAKE_FIND_ROOT_PATH
)
find_library(
LOG_LIB
log
)
# linking
message(WARNING "VisionCamera linking: FOR_HERMES=${FOR_HERMES}")
target_link_libraries(
${PACKAGE_NAME}
${LOG_LIB}
${JSI_LIB}
${REANIMATED_LIB}
${REACT_NATIVE_JNI_LIB}
${FBJNI_LIB}
${FOLLY_LIB}
android
) )

View File

@ -1,3 +1,5 @@
import java.nio.file.Paths
buildscript { buildscript {
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['VisionCamera_kotlinVersion'] def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['VisionCamera_kotlinVersion']
@ -16,7 +18,6 @@ buildscript {
} }
} }
def ENABLE_FRAME_PROCESSORS = false // TODO: fix
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['VisionCamera_kotlinVersion'] def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['VisionCamera_kotlinVersion']
def resolveBuildType() { def resolveBuildType() {
@ -49,6 +50,23 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
} }
static def findNodeModules(baseDir) {
def basePath = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def nodeModulesPath = Paths.get(basePath.toString(), "node_modules")
def reactNativePath = Paths.get(nodeModulesPath.toString(), "react-native")
if (nodeModulesPath.toFile().exists() && reactNativePath.toFile().exists()) {
return nodeModulesPath.toString()
}
basePath = basePath.getParent()
}
throw new GradleException("react-native-worklets: Failed to find node_modules/ path!")
}
def nodeModules = findNodeModules(projectDir)
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
@ -77,13 +95,12 @@ android {
versionName "1.0" versionName "1.0"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (ENABLE_FRAME_PROCESSORS) { externalNativeBuild {
externalNativeBuild { cmake {
cmake { cppFlags "-O2 -frtti -fexceptions -Wall -Wno-unused-variable -fstack-protector-all"
cppFlags "-O2 -frtti -fexceptions -Wall -Wno-unused-variable -fstack-protector-all" arguments "-DANDROID_STL=c++_shared",
arguments "-DANDROID_STL=c++_shared" "-DNODE_MODULES_DIR=${nodeModules}"
abiFilters (*reactNativeArchitectures()) abiFilters (*reactNativeArchitectures())
}
} }
} }
} }
@ -93,11 +110,9 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
if (ENABLE_FRAME_PROCESSORS) { externalNativeBuild {
externalNativeBuild { cmake {
cmake { path "CMakeLists.txt"
path "CMakeLists.txt"
}
} }
} }
packagingOptions { packagingOptions {
@ -128,6 +143,8 @@ dependencies {
implementation "androidx.camera:camera-extensions:1.1.0-beta02" implementation "androidx.camera:camera-extensions:1.1.0-beta02"
implementation "androidx.exifinterface:exifinterface:1.3.3" implementation "androidx.exifinterface:exifinterface:1.3.3"
implementation project(":react-native-worklets")
} }
// Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct". // Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct".

View File

@ -1,6 +1,3 @@
rootProject.name = 'VisionCamera' rootProject.name = 'VisionCamera'
include ':react-native-reanimated'
project(':react-native-reanimated').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-reanimated/android/')
include ':VisionCamera' include ':VisionCamera'

View File

@ -7,17 +7,11 @@
#include <jni.h> #include <jni.h>
#include <utility> #include <utility>
#include <string> #include <string>
#include <JsiWorklet.h>
#include "RuntimeDecorator.h"
#include "RuntimeManager.h"
#include "reanimated-headers/AndroidScheduler.h"
#include "reanimated-headers/AndroidErrorHandler.h"
#include "MakeJSIRuntime.h"
#include "CameraView.h" #include "CameraView.h"
#include "FrameHostObject.h" #include "FrameHostObject.h"
#include "JSIJNIConversion.h" #include "JSIJNIConversion.h"
#include "VisionCameraScheduler.h"
#include "java-bindings/JImageProxy.h" #include "java-bindings/JImageProxy.h"
#include "java-bindings/JFrameProcessorPlugin.h" #include "java-bindings/JFrameProcessorPlugin.h"
@ -28,6 +22,28 @@ using TSelf = local_ref<HybridClass<vision::FrameProcessorRuntimeManager>::jhybr
using TJSCallInvokerHolder = jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>; using TJSCallInvokerHolder = jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>;
using TAndroidScheduler = jni::alias_ref<VisionCameraScheduler::javaobject>; using TAndroidScheduler = jni::alias_ref<VisionCameraScheduler::javaobject>;
FrameProcessorRuntimeManager::FrameProcessorRuntimeManager(jni::alias_ref<FrameProcessorRuntimeManager::jhybridobject> jThis,
jsi::Runtime* jsRuntime,
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker,
std::shared_ptr<vision::VisionCameraScheduler> scheduler) :
javaPart_(jni::make_global(jThis)),
_jsRuntime(jsRuntime) {
auto runOnJS = [jsCallInvoker](std::function<void()>&& f) {
// Run on React JS Runtime
jsCallInvoker->invokeAsync(std::move(f));
};
auto runOnWorklet = [scheduler](std::function<void()>&& f) {
// Run on Frame Processor Worklet Runtime
scheduler->dispatchAsync(std::move(f));
};
_workletContext = std::make_shared<RNWorklet::JsiWorkletContext>("VisionCamera");
_workletContext->initialize("VisionCamera",
jsRuntime,
runOnJS,
runOnWorklet);
}
// JNI binding // JNI binding
void vision::FrameProcessorRuntimeManager::registerNatives() { void vision::FrameProcessorRuntimeManager::registerNatives() {
registerHybrid({ registerHybrid({
@ -35,8 +51,6 @@ void vision::FrameProcessorRuntimeManager::registerNatives() {
FrameProcessorRuntimeManager::initHybrid), FrameProcessorRuntimeManager::initHybrid),
makeNativeMethod("installJSIBindings", makeNativeMethod("installJSIBindings",
FrameProcessorRuntimeManager::installJSIBindings), FrameProcessorRuntimeManager::installJSIBindings),
makeNativeMethod("initializeRuntime",
FrameProcessorRuntimeManager::initializeRuntime),
makeNativeMethod("registerPlugin", makeNativeMethod("registerPlugin",
FrameProcessorRuntimeManager::registerPlugin), FrameProcessorRuntimeManager::registerPlugin),
}); });
@ -52,32 +66,11 @@ TSelf vision::FrameProcessorRuntimeManager::initHybrid(
"Initializing FrameProcessorRuntimeManager..."); "Initializing FrameProcessorRuntimeManager...");
// cast from JNI hybrid objects to C++ instances // cast from JNI hybrid objects to C++ instances
auto runtime = reinterpret_cast<jsi::Runtime*>(jsRuntimePointer); auto jsRuntime = reinterpret_cast<jsi::Runtime*>(jsRuntimePointer);
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();
auto scheduler = std::shared_ptr<VisionCameraScheduler>(androidScheduler->cthis()); auto scheduler = std::shared_ptr<VisionCameraScheduler>(androidScheduler->cthis());
scheduler->setJSCallInvoker(jsCallInvoker);
return makeCxxInstance(jThis, runtime, jsCallInvoker, scheduler); return makeCxxInstance(jThis, jsRuntime, jsCallInvoker, scheduler);
}
void vision::FrameProcessorRuntimeManager::initializeRuntime() {
__android_log_write(ANDROID_LOG_INFO, TAG,
"Initializing Vision JS-Runtime...");
// create JSI runtime and decorate it
auto runtime = makeJSIRuntime();
reanimated::RuntimeDecorator::decorateRuntime(*runtime, "FRAME_PROCESSOR");
runtime->global().setProperty(*runtime, "_FRAME_PROCESSOR",
jsi::Value(true));
// create REA runtime manager
auto errorHandler = std::make_shared<reanimated::AndroidErrorHandler>(scheduler_);
_runtimeManager = std::make_unique<reanimated::RuntimeManager>(std::move(runtime),
errorHandler,
scheduler_);
__android_log_write(ANDROID_LOG_INFO, TAG,
"Initialized Vision JS-Runtime!");
} }
global_ref<CameraView::javaobject> FrameProcessorRuntimeManager::findCameraViewById(int viewId) { global_ref<CameraView::javaobject> FrameProcessorRuntimeManager::findCameraViewById(int viewId) {
@ -87,21 +80,16 @@ global_ref<CameraView::javaobject> FrameProcessorRuntimeManager::findCameraViewB
} }
void FrameProcessorRuntimeManager::logErrorToJS(const std::string& message) { void FrameProcessorRuntimeManager::logErrorToJS(const std::string& message) {
if (!this->jsCallInvoker_) { if (!_workletContext) {
return; return;
} }
// Call console.error() on JS Thread
this->jsCallInvoker_->invokeAsync([this, message]() { _workletContext->invokeOnJsThread([message](jsi::Runtime& runtime) {
if (this->runtime_ == nullptr) { auto consoleError = runtime
return; .global()
} .getPropertyAsObject(runtime, "console")
.getPropertyAsFunction(runtime, "error");
auto& runtime = *this->runtime_; consoleError.call(runtime, jsi::String::createFromUtf8(runtime, message));
auto consoleError = runtime
.global()
.getPropertyAsObject(runtime, "console")
.getPropertyAsFunction(runtime, "error");
consoleError.call(runtime, jsi::String::createFromUtf8(runtime, message));
}); });
} }
@ -111,42 +99,38 @@ void FrameProcessorRuntimeManager::setFrameProcessor(jsi::Runtime& runtime,
__android_log_write(ANDROID_LOG_INFO, TAG, __android_log_write(ANDROID_LOG_INFO, TAG,
"Setting new Frame Processor..."); "Setting new Frame Processor...");
if (!_runtimeManager || !_runtimeManager->runtime) { if (!_workletContext) {
throw jsi::JSError(runtime, throw jsi::JSError(runtime,
"setFrameProcessor(..): VisionCamera's RuntimeManager is not yet initialized!"); "setFrameProcessor(..): VisionCamera's Worklet Context is not yet initialized!");
} }
// find camera view // find camera view
auto cameraView = findCameraViewById(viewTag); auto cameraView = findCameraViewById(viewTag);
__android_log_write(ANDROID_LOG_INFO, TAG, "Found CameraView!"); __android_log_write(ANDROID_LOG_INFO, TAG, "Found CameraView!");
// convert jsi::Function to a ShareableValue (can be shared across runtimes) // convert jsi::Function to a Worklet (can be shared across runtimes)
__android_log_write(ANDROID_LOG_INFO, TAG, __android_log_write(ANDROID_LOG_INFO, TAG, "Creating Worklet...");
"Adapting Shareable value from function (conversion to worklet)..."); auto worklet = std::make_shared<RNWorklet::JsiWorklet>(runtime, frameProcessor);
auto worklet = reanimated::ShareableValue::adapt(runtime, auto workletInvoker = std::make_shared<RNWorklet::WorkletInvoker>(worklet);
frameProcessor,
_runtimeManager.get());
__android_log_write(ANDROID_LOG_INFO, TAG, "Successfully created worklet!"); __android_log_write(ANDROID_LOG_INFO, TAG, "Successfully created worklet!");
scheduler_->scheduleOnUI([=]() { _workletContext->invokeOnWorkletThread([=](RNWorklet::JsiWorkletContext*, jsi::Runtime& rt) {
// cast worklet to a jsi::Function for the new runtime // Set Frame Processor as callable C++ lambda - this will then call the Worklet
auto& rt = *_runtimeManager->runtime; cameraView->cthis()->setFrameProcessor([this, workletInvoker, &rt](jni::alias_ref<JImageProxy::javaobject> frame) {
auto function = std::make_shared<jsi::Function>(worklet->getValue(rt).asObject(rt).asFunction(rt)); try {
// create HostObject which holds the Frame (JImageProxy)
// assign lambda to frame processor auto frameHostObject = std::make_shared<FrameHostObject>(frame);
cameraView->cthis()->setFrameProcessor([this, &rt, function](jni::alias_ref<JImageProxy::javaobject> frame) { auto argument = jsi::Object::createFromHostObject(rt, frameHostObject);
try { jsi::Value jsValue(std::move(argument));
// create HostObject which holds the Frame (JImageProxy) // Call the Worklet on the Worklet Runtime
auto hostObject = std::make_shared<FrameHostObject>(frame); workletInvoker->call(rt, jsi::Value::undefined(), &jsValue, 1);
function->callWithThis(rt, *function, jsi::Object::createFromHostObject(rt, hostObject)); } catch (jsi::JSError& jsError) {
} catch (jsi::JSError& jsError) { // Worklet threw a JS Error, catch it and log it to JS.
auto message = "Frame Processor threw an error: " + jsError.getMessage(); auto message = "Frame Processor threw an error: " + jsError.getMessage();
__android_log_write(ANDROID_LOG_ERROR, TAG, message.c_str()); __android_log_write(ANDROID_LOG_ERROR, TAG, message.c_str());
this->logErrorToJS(message); this->logErrorToJS(message);
} }
}); });
__android_log_write(ANDROID_LOG_INFO, TAG, "Frame Processor set!");
}); });
} }
@ -166,13 +150,13 @@ void FrameProcessorRuntimeManager::unsetFrameProcessor(int viewTag) {
void FrameProcessorRuntimeManager::installJSIBindings() { void FrameProcessorRuntimeManager::installJSIBindings() {
__android_log_write(ANDROID_LOG_INFO, TAG, "Installing JSI bindings..."); __android_log_write(ANDROID_LOG_INFO, TAG, "Installing JSI bindings...");
if (runtime_ == nullptr) { if (_jsRuntime == nullptr) {
__android_log_write(ANDROID_LOG_ERROR, TAG, __android_log_write(ANDROID_LOG_ERROR, TAG,
"JS-Runtime was null, Frame Processor JSI bindings could not be installed!"); "JS-Runtime was null, Frame Processor JSI bindings could not be installed!");
return; return;
} }
auto& jsiRuntime = *runtime_; auto& jsiRuntime = *_jsRuntime;
auto setFrameProcessor = [this](jsi::Runtime &runtime, auto setFrameProcessor = [this](jsi::Runtime &runtime,
const jsi::Value &thisValue, const jsi::Value &thisValue,
@ -234,27 +218,26 @@ void FrameProcessorRuntimeManager::installJSIBindings() {
} }
void FrameProcessorRuntimeManager::registerPlugin(alias_ref<JFrameProcessorPlugin::javaobject> plugin) { void FrameProcessorRuntimeManager::registerPlugin(alias_ref<JFrameProcessorPlugin::javaobject> plugin) {
// _runtimeManager might never be null, but we can never be too sure. auto& runtime = *_jsRuntime;
if (!_runtimeManager || !_runtimeManager->runtime) {
throw std::runtime_error("Tried to register plugin before initializing JS runtime! Call `initializeRuntime()` first.");
}
auto& runtime = *_runtimeManager->runtime;
// we need a strong reference on the plugin, make_global does that. // we need a strong reference on the plugin, make_global does that.
auto pluginGlobal = make_global(plugin); auto pluginGlobal = make_global(plugin);
// name is always prefixed with two underscores (__) auto pluginName = pluginGlobal->getName();
auto name = "__" + pluginGlobal->getName();
__android_log_print(ANDROID_LOG_INFO, TAG, "Installing Frame Processor Plugin \"%s\"...", name.c_str()); __android_log_print(ANDROID_LOG_INFO, TAG, "Installing Frame Processor Plugin \"%s\"...", pluginName.c_str());
auto callback = [pluginGlobal](jsi::Runtime& runtime, if (!runtime.global().hasProperty(runtime, "FrameProcessorPlugins")) {
runtime.global().setProperty(runtime, "FrameProcessorPlugins", jsi::Object(runtime));
}
jsi::Object frameProcessorPlugins = runtime.global().getPropertyAsObject(runtime, "FrameProcessorPlugins");
auto function = [pluginGlobal](jsi::Runtime& runtime,
const jsi::Value& thisValue, const jsi::Value& thisValue,
const jsi::Value* arguments, const jsi::Value* arguments,
size_t count) -> jsi::Value { size_t count) -> jsi::Value {
// Unbox object and get typed HostObject // Unbox object and get typed HostObject
auto boxedHostObject = arguments[0].asObject(runtime).asHostObject(runtime); auto boxedHostObject = arguments[0].asObject(runtime).asHostObject(runtime);
auto frameHostObject = static_cast<FrameHostObject*>(boxedHostObject.get()); auto frameHostObject = dynamic_cast<FrameHostObject*>(boxedHostObject.get());
// parse params - we are offset by `1` because the frame is the first parameter. // parse params - we are offset by `1` because the frame is the first parameter.
auto params = JArrayClass<jobject>::newArray(count - 1); auto params = JArrayClass<jobject>::newArray(count - 1);
@ -269,10 +252,14 @@ void FrameProcessorRuntimeManager::registerPlugin(alias_ref<JFrameProcessorPlugi
return JSIJNIConversion::convertJNIObjectToJSIValue(runtime, result); return JSIJNIConversion::convertJNIObjectToJSIValue(runtime, result);
}; };
runtime.global().setProperty(runtime, name.c_str(), jsi::Function::createFromHostFunction(runtime, // Assign it to the Proxy.
jsi::PropNameID::forAscii(runtime, name), // A FP Plugin called "example_plugin" can be now called from JS using "FrameProcessorPlugins.example_plugin(frame)"
1, // frame frameProcessorPlugins.setProperty(runtime,
callback)); pluginName.c_str(),
jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forAscii(runtime, pluginName),
1, // frame
function));
} }
} // namespace vision } // namespace vision

View File

@ -9,9 +9,7 @@
#include <ReactCommon/CallInvokerHolder.h> #include <ReactCommon/CallInvokerHolder.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <JsiWorkletContext.h>
#include "RuntimeManager.h"
#include "reanimated-headers/AndroidScheduler.h"
#include "CameraView.h" #include "CameraView.h"
#include "VisionCameraScheduler.h" #include "VisionCameraScheduler.h"
@ -32,25 +30,17 @@ class FrameProcessorRuntimeManager : public jni::HybridClass<FrameProcessorRunti
static void registerNatives(); static void registerNatives();
explicit FrameProcessorRuntimeManager(jni::alias_ref<FrameProcessorRuntimeManager::jhybridobject> jThis, explicit FrameProcessorRuntimeManager(jni::alias_ref<FrameProcessorRuntimeManager::jhybridobject> jThis,
jsi::Runtime* runtime, jsi::Runtime* jsRuntime,
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker, std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker,
std::shared_ptr<vision::VisionCameraScheduler> scheduler) : std::shared_ptr<vision::VisionCameraScheduler> scheduler);
javaPart_(jni::make_global(jThis)),
runtime_(runtime),
jsCallInvoker_(jsCallInvoker),
scheduler_(scheduler)
{}
private: private:
friend HybridBase; friend HybridBase;
jni::global_ref<FrameProcessorRuntimeManager::javaobject> javaPart_; jni::global_ref<FrameProcessorRuntimeManager::javaobject> javaPart_;
jsi::Runtime* runtime_; jsi::Runtime* _jsRuntime;
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker_; std::shared_ptr<RNWorklet::JsiWorkletContext> _workletContext;
std::shared_ptr<reanimated::RuntimeManager> _runtimeManager;
std::shared_ptr<vision::VisionCameraScheduler> scheduler_;
jni::global_ref<CameraView::javaobject> findCameraViewById(int viewId); jni::global_ref<CameraView::javaobject> findCameraViewById(int viewId);
void initializeRuntime();
void installJSIBindings(); void installJSIBindings();
void registerPlugin(alias_ref<JFrameProcessorPlugin::javaobject> plugin); void registerPlugin(alias_ref<JFrameProcessorPlugin::javaobject> plugin);
void logErrorToJS(const std::string& message); void logErrorToJS(const std::string& message);

View File

@ -1,30 +0,0 @@
//
// Created by Marc Rousavy on 06.07.21.
//
#pragma once
#include <jsi/jsi.h>
#include <memory>
#if FOR_HERMES
// Hermes
#include <hermes/hermes.h>
#else
// JSC
#include <jsi/JSCRuntime.h>
#endif
namespace vision {
using namespace facebook;
static std::unique_ptr<jsi::Runtime> makeJSIRuntime() {
#if FOR_HERMES
return facebook::hermes::makeHermesRuntime();
#else
return facebook::jsc::makeJSCRuntime();
#endif
}
} // namespace vision

View File

@ -14,9 +14,9 @@ TSelf VisionCameraScheduler::initHybrid(jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance(jThis); return makeCxxInstance(jThis);
} }
void VisionCameraScheduler::scheduleOnUI(std::function<void()> job) { void VisionCameraScheduler::dispatchAsync(std::function<void()> job) {
// 1. add job to queue // 1. add job to queue
uiJobs.push(job); _jobs.push(job);
scheduleTrigger(); scheduleTrigger();
} }
@ -26,16 +26,18 @@ void VisionCameraScheduler::scheduleTrigger() {
method(javaPart_.get()); method(javaPart_.get());
} }
void VisionCameraScheduler::triggerUI() { void VisionCameraScheduler::trigger() {
std::unique_lock<std::mutex> lock(_mutex);
// 3. call job we enqueued in step 1. // 3. call job we enqueued in step 1.
auto job = uiJobs.pop(); auto job = _jobs.front();
job(); job();
_jobs.pop();
} }
void VisionCameraScheduler::registerNatives() { void VisionCameraScheduler::registerNatives() {
registerHybrid({ registerHybrid({
makeNativeMethod("initHybrid", VisionCameraScheduler::initHybrid), makeNativeMethod("initHybrid", VisionCameraScheduler::initHybrid),
makeNativeMethod("triggerUI", VisionCameraScheduler::triggerUI), makeNativeMethod("trigger", VisionCameraScheduler::trigger),
}); });
} }

View File

@ -4,35 +4,47 @@
#pragma once #pragma once
#include "Scheduler.h"
#include <ReactCommon/CallInvokerHolder.h> #include <ReactCommon/CallInvokerHolder.h>
#include <jni.h> #include <jni.h>
#include <fbjni/fbjni.h> #include <fbjni/fbjni.h>
#include <queue>
#include <mutex>
namespace vision { namespace vision {
using namespace facebook; using namespace facebook;
class VisionCameraScheduler : public reanimated::Scheduler, public jni::HybridClass<VisionCameraScheduler> { /**
* A Scheduler that runs methods on the Frame Processor Thread (which is a Java Thread).
* In order to call something on the Java Frame Processor Thread, you have to:
*
* 1. Call `dispatchAsync(..)` with the given C++ Method.
* 2. Internally, `scheduleTrigger()` will get called, which is a Java Method.
* 3. The `scheduleTrigger()` Java Method will switch to the Frame Processor Java Thread and call `trigger()` on there
* 4. `trigger()` is a C++ function here that just calls the passed C++ Method from step 1.
*/
class VisionCameraScheduler : public jni::HybridClass<VisionCameraScheduler> {
public: public:
static auto constexpr kJavaDescriptor = "Lcom/mrousavy/camera/frameprocessor/VisionCameraScheduler;"; static auto constexpr kJavaDescriptor = "Lcom/mrousavy/camera/frameprocessor/VisionCameraScheduler;";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis); static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
static void registerNatives(); static void registerNatives();
// schedules the given job to be run on the VisionCamera FP Thread at some future point in time // schedules the given job to be run on the VisionCamera FP Thread at some future point in time
void scheduleOnUI(std::function<void()> job) override; void dispatchAsync(std::function<void()> job);
private: private:
friend HybridBase; friend HybridBase;
jni::global_ref<VisionCameraScheduler::javaobject> javaPart_; jni::global_ref<VisionCameraScheduler::javaobject> javaPart_;
std::queue<std::function<void()>> _jobs;
std::mutex _mutex;
explicit VisionCameraScheduler(jni::alias_ref<VisionCameraScheduler::jhybridobject> jThis): explicit VisionCameraScheduler(jni::alias_ref<VisionCameraScheduler::jhybridobject> jThis):
javaPart_(jni::make_global(jThis)) {} javaPart_(jni::make_global(jThis)) {}
// Schedules a call to `triggerUI` on the VisionCamera FP Thread // Schedules a call to `trigger` on the VisionCamera FP Thread
void scheduleTrigger(); void scheduleTrigger();
// Calls the latest job in the job queue // Calls the latest job in the job queue
void triggerUI() override; void trigger();
}; };
} // namespace vision } // namespace vision

View File

@ -1,30 +0,0 @@
// copied from https://github.com/software-mansion/react-native-reanimated/blob/master/android/src/main/cpp/headers/AndroidErrorHandler.h
#pragma once
#include "ErrorHandler.h"
#include "AndroidScheduler.h"
#include "Scheduler.h"
#include <jni.h>
#include <memory>
#include <fbjni/fbjni.h>
#include "Logger.h"
namespace reanimated
{
class AndroidErrorHandler : public JavaClass<AndroidErrorHandler>, public ErrorHandler {
std::shared_ptr<ErrorWrapper> error;
std::shared_ptr<Scheduler> scheduler;
void raiseSpec() override;
public:
static auto constexpr kJavaDescriptor = "Lcom/swmansion/reanimated/AndroidErrorHandler;";
AndroidErrorHandler(
std::shared_ptr<Scheduler> scheduler);
std::shared_ptr<Scheduler> getScheduler() override;
std::shared_ptr<ErrorWrapper> getError() override;
void setError(std::string message) override;
virtual ~AndroidErrorHandler() {}
};
}

View File

@ -1,37 +0,0 @@
// copied from https://github.com/software-mansion/react-native-reanimated/blob/master/android/src/main/cpp/headers/AndroidScheduler.h
#pragma once
#include <jni.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>
#include "Scheduler.h"
namespace reanimated {
using namespace facebook;
class AndroidScheduler : public jni::HybridClass<AndroidScheduler> {
public:
static auto constexpr kJavaDescriptor = "Lcom/swmansion/reanimated/Scheduler;";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
static void registerNatives();
std::shared_ptr<Scheduler> getScheduler() { return scheduler_; }
void scheduleOnUI();
private:
friend HybridBase;
void triggerUI();
jni::global_ref<AndroidScheduler::javaobject> javaPart_;
std::shared_ptr<Scheduler> scheduler_;
explicit AndroidScheduler(jni::alias_ref<AndroidScheduler::jhybridobject> jThis);
};
}

View File

@ -210,9 +210,7 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
} }
init { init {
if (FrameProcessorRuntimeManager.enableFrameProcessors) { mHybridData = initHybrid()
mHybridData = initHybrid()
}
previewView = PreviewView(context) previewView = PreviewView(context)
previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)

View File

@ -20,9 +20,6 @@ import com.facebook.react.modules.core.PermissionAwareActivity
import com.facebook.react.modules.core.PermissionListener import com.facebook.react.modules.core.PermissionListener
import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
import com.mrousavy.camera.CameraView
import com.mrousavy.camera.ViewNotFoundError
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import com.mrousavy.camera.frameprocessor.FrameProcessorRuntimeManager import com.mrousavy.camera.frameprocessor.FrameProcessorRuntimeManager
import com.mrousavy.camera.parsers.* import com.mrousavy.camera.parsers.*
@ -55,17 +52,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
if (coroutineScope.isActive) { if (coroutineScope.isActive) {
coroutineScope.cancel("CameraViewModule has been destroyed.") coroutineScope.cancel("CameraViewModule has been destroyed.")
} }
frameProcessorManager = null
}
override fun initialize() {
super.initialize()
if (frameProcessorManager == null) {
frameProcessorThread.execute {
frameProcessorManager = FrameProcessorRuntimeManager(reactApplicationContext, frameProcessorThread)
}
}
} }
override fun onCatalystInstanceDestroy() { override fun onCatalystInstanceDestroy() {
@ -165,6 +151,18 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
} }
} }
@ReactMethod(isBlockingSynchronousMethod = true)
fun installFrameProcessorBindings(): Boolean {
try {
frameProcessorManager = FrameProcessorRuntimeManager(reactApplicationContext, frameProcessorThread)
frameProcessorManager!!.installBindings()
return true
} catch (e: Error) {
Log.e(TAG, "Failed to install Frame Processor JSI Bindings!", e)
return false
}
}
// TODO: This uses the Camera2 API to list all characteristics of a camera device and therefore doesn't work with Camera1. Find a way to use CameraX for this // TODO: This uses the Camera2 API to list all characteristics of a camera device and therefore doesn't work with Camera1. Find a way to use CameraX for this
// https://issuetracker.google.com/issues/179925896 // https://issuetracker.google.com/issues/179925896
@ReactMethod @ReactMethod

View File

@ -48,6 +48,6 @@ public abstract class FrameProcessorPlugin {
* @param plugin An instance of a plugin. * @param plugin An instance of a plugin.
*/ */
public static void register(@NonNull FrameProcessorPlugin plugin) { public static void register(@NonNull FrameProcessorPlugin plugin) {
FrameProcessorRuntimeManager.Companion.getPlugins().add(plugin); FrameProcessorRuntimeManager.Companion.addPlugin(plugin);
} }
} }

View File

@ -16,18 +16,20 @@ import java.util.concurrent.ExecutorService
class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProcessorThread: ExecutorService) { class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProcessorThread: ExecutorService) {
companion object { companion object {
const val TAG = "FrameProcessorRuntime" const val TAG = "FrameProcessorRuntime"
val Plugins: ArrayList<FrameProcessorPlugin> = ArrayList() private val Plugins: ArrayList<FrameProcessorPlugin> = ArrayList()
var enableFrameProcessors = true
init { init {
try { try {
System.loadLibrary("reanimated")
System.loadLibrary("VisionCamera") System.loadLibrary("VisionCamera")
} catch (e: UnsatisfiedLinkError) { } catch (e: UnsatisfiedLinkError) {
Log.w(TAG, "Failed to load Reanimated/VisionCamera C++ library. Frame Processors are disabled!") Log.e(TAG, "Failed to load VisionCamera C++ library!", e)
enableFrameProcessors = false throw e
} }
} }
fun addPlugin(plugin: FrameProcessorPlugin) {
Plugins.add(plugin)
}
} }
@DoNotStrip @DoNotStrip
@ -36,24 +38,11 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces
private var mScheduler: VisionCameraScheduler? = null private var mScheduler: VisionCameraScheduler? = null
init { init {
if (enableFrameProcessors) { val jsCallInvokerHolder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl
val holder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl val jsRuntimeHolder = context.javaScriptContextHolder.get()
mScheduler = VisionCameraScheduler(frameProcessorThread) mScheduler = VisionCameraScheduler(frameProcessorThread)
mContext = WeakReference(context) mContext = WeakReference(context)
mHybridData = initHybrid(context.javaScriptContextHolder.get(), holder, mScheduler!!) mHybridData = initHybrid(jsRuntimeHolder, jsCallInvokerHolder, mScheduler!!)
initializeRuntime()
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()
}
}
} }
@Suppress("unused") @Suppress("unused")
@ -67,13 +56,22 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces
return view ?: throw ViewNotFoundError(viewId) return view ?: throw ViewNotFoundError(viewId)
} }
fun installBindings() {
Log.i(TAG, "Installing JSI Bindings on JS Thread...")
installJSIBindings()
Log.i(TAG, "Installing Frame Processor Plugins...")
Plugins.forEach { plugin ->
registerPlugin(plugin)
}
Log.i(TAG, "Successfully installed ${Plugins.count()} Frame Processor Plugins!")
}
// private C++ funcs // private C++ funcs
private external fun initHybrid( private external fun initHybrid(
jsContext: Long, jsContext: Long,
jsCallInvokerHolder: CallInvokerHolderImpl, jsCallInvokerHolder: CallInvokerHolderImpl,
scheduler: VisionCameraScheduler scheduler: VisionCameraScheduler
): HybridData ): HybridData
private external fun initializeRuntime()
private external fun registerPlugin(plugin: FrameProcessorPlugin) private external fun registerPlugin(plugin: FrameProcessorPlugin)
private external fun installJSIBindings() private external fun installJSIBindings()
} }

View File

@ -19,7 +19,7 @@ public class ImageProxyUtils {
Image image = imageProxy.getImage(); Image image = imageProxy.getImage();
if (image == null) return false; if (image == null) return false;
// will throw an exception if the image is already closed // will throw an exception if the image is already closed
imageProxy.getImage().getCropRect(); image.getCropRect();
// no exception thrown, image must still be valid. // no exception thrown, image must still be valid.
return true; return true;
} catch (Exception e) { } catch (Exception e) {

View File

@ -17,11 +17,11 @@ public class VisionCameraScheduler {
} }
private native HybridData initHybrid(); private native HybridData initHybrid();
private native void triggerUI(); private native void trigger();
@SuppressWarnings("unused") @SuppressWarnings("unused")
@DoNotStrip @DoNotStrip
private void scheduleTrigger() { private void scheduleTrigger() {
frameProcessorThread.submit(this::triggerUI); frameProcessorThread.submit(this::trigger);
} }
} }

View File

@ -48,17 +48,13 @@ Frame processors are by far not limited to Hotdog detection, other examples incl
* Creating scanners for **QR codes**, **Barcodes** or even custom codes such as **Snapchat's SnapCodes** or **Apple's AppClips** * Creating scanners for **QR codes**, **Barcodes** or even custom codes such as **Snapchat's SnapCodes** or **Apple's AppClips**
* Creating **snapchat-like filters**, e.g. draw a dog-mask filter over the user's face * Creating **snapchat-like filters**, e.g. draw a dog-mask filter over the user's face
* Creating **color filters** with depth-detection * Creating **color filters** with depth-detection
* Drawing boxes, text, overlays, or colors on the screen in realtime
* Rendering filters and shaders such as Blur, inverted colors, beauty filter, or more on the screen
Because they are written in JS, Frame Processors are **simple**, **powerful**, **extensible** and **easy to create** while still running at **native performance**. (Frame Processors can run up to **1000 times a second**!) Also, you can use **fast-refresh** to quickly see changes while developing or publish [over-the-air updates](https://github.com/microsoft/react-native-code-push) to tweak the Hotdog detector's sensitivity in live apps without pushing a native update. Because they are written in JS, Frame Processors are **simple**, **powerful**, **extensible** and **easy to create** while still running at **native performance**. (Frame Processors can run up to **1000 times a second**!) Also, you can use **fast-refresh** to quickly see changes while developing or publish [over-the-air updates](https://github.com/microsoft/react-native-code-push) to tweak the Hotdog detector's sensitivity in live apps without pushing a native update.
:::note :::note
Frame Processors require [**react-native-reanimated**](https://github.com/software-mansion/react-native-reanimated) 2.2.0 or higher. Also make sure to add Frame Processors require [**react-native-worklets**](https://github.com/chrfalch/react-native-worklets) 1.0.0 or higher.
```js
import 'react-native-reanimated'
```
to the top of the file when using `useFrameProcessor`.
::: :::
### Interacting with Frame Processors ### Interacting with Frame Processors
@ -80,7 +76,7 @@ You can also easily read from, and assign to [**Shared Values**](https://docs.sw
In this example, we detect a cat in the frame - if a cat was found, we assign the `catBounds` Shared Value to the coordinates of the cat (relative to the screen) which we can then use in a `useAnimatedStyle` hook to position the red rectangle surrounding the cat. This updates in realtime on the UI Thread, and can also be smoothly animated with `withSpring` or `withTiming`. In this example, we detect a cat in the frame - if a cat was found, we assign the `catBounds` Shared Value to the coordinates of the cat (relative to the screen) which we can then use in a `useAnimatedStyle` hook to position the red rectangle surrounding the cat. This updates in realtime on the UI Thread, and can also be smoothly animated with `withSpring` or `withTiming`.
```tsx {6} ```tsx {7}
// represents position of the cat on the screen 🐈 // represents position of the cat on the screen 🐈
const catBounds = useSharedValue({ top: 0, left: 0, right: 0, bottom: 0 }) const catBounds = useSharedValue({ top: 0, left: 0, right: 0, bottom: 0 })
@ -108,18 +104,18 @@ return (
) )
``` ```
And you can also call back to the React-JS thread by using [`runOnJS`](https://docs.swmansion.com/react-native-reanimated/docs/api/miscellaneous/runOnJS/): And you can also call back to the React-JS thread by using `createRunInJsFn(...)`:
```tsx {9} ```tsx {1}
const onQRCodeDetected = useCallback((qrCode: string) => { const onQRCodeDetected = Worklets.createRunInJsFn((qrCode: string) => {
navigation.push("ProductPage", { productId: qrCode }) navigation.push("ProductPage", { productId: qrCode })
}, []) })
const frameProcessor = useFrameProcessor((frame) => { const frameProcessor = useFrameProcessor((frame) => {
'worklet' 'worklet'
const qrCodes = scanQRCodes(frame) const qrCodes = scanQRCodes(frame)
if (qrCodes.length > 0) { if (qrCodes.length > 0) {
runOnJS(onQRCodeDetected)(qrCodes[0]) onQRCodeDetected(qrCodes[0])
} }
}, [onQRCodeDetected]) }, [onQRCodeDetected])
``` ```
@ -133,23 +129,6 @@ npm i vision-camera-image-labeler
cd ios && pod install cd ios && pod install
``` ```
Then add it to your `babel.config.js`. For the Image Labeler, this will be `__labelImage`:
```js {6}
module.exports = {
plugins: [
[
'react-native-reanimated/plugin',
{
globals: ['__labelImage'],
},
],
```
:::note
You have to restart metro-bundler for changes in the `babel.config.js` file to take effect.
:::
That's it! 🎉 Now you can use it: That's it! 🎉 Now you can use it:
```ts ```ts
@ -192,7 +171,7 @@ If you are using the [react-hooks ESLint plugin](https://www.npmjs.com/package/e
#### Frame Processors #### Frame Processors
**Frame Processors** are JS functions that will be **workletized** using [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated). They are created on a **parallel camera thread** using a separate JavaScript Runtime (_"VisionCamera JS-Runtime"_) and are **invoked synchronously** (using JSI) without ever going over the bridge. In a **Frame Processor** you can write normal JS code, call back to the React-JS Thread (e.g. `setState`), use [Shared Values](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/shared-values/) and call **Frame Processor Plugins**. **Frame Processors** are JS functions that will be **workletized** using [react-native-worklets](https://github.com/chrfalch/react-native-worklets). They are created on a **parallel camera thread** using a separate JavaScript Runtime (_"VisionCamera JS-Runtime"_) and are **invoked synchronously** (using JSI) without ever going over the bridge. In a **Frame Processor** you can write normal JS code, call back to the React-JS Thread (e.g. `setState`), use [Shared Values](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/shared-values/) and call **Frame Processor Plugins**.
> See [**the example Frame Processor**](https://github.com/mrousavy/react-native-vision-camera/blob/cf68a4c6476d085ec48fc424a53a96962e0c33f9/example/src/CameraPage.tsx#L199-L203) > See [**the example Frame Processor**](https://github.com/mrousavy/react-native-vision-camera/blob/cf68a4c6476d085ec48fc424a53a96962e0c33f9/example/src/CameraPage.tsx#L199-L203)

View File

@ -61,7 +61,7 @@ Returns a `string` in JS:
```js ```js
export function detectObject(frame: Frame): string { export function detectObject(frame: Frame): string {
'worklet' 'worklet'
const result = __detectObject(frame) const result = FrameProcessorPlugins.detectObject(frame)
console.log(result) // <-- "cat" console.log(result) // <-- "cat"
} }
``` ```

View File

@ -9,36 +9,17 @@ sidebar_label: Finish creating your Frame Processor Plugin
To make the Frame Processor Plugin available to the Frame Processor Worklet Runtime, create the following wrapper function in JS/TS: To make the Frame Processor Plugin available to the Frame Processor Worklet Runtime, create the following wrapper function in JS/TS:
```ts ```ts
import type { Frame } from 'react-native-vision-camera' import { FrameProcessorPlugins, Frame } from 'react-native-vision-camera'
/** /**
* Scans QR codes. * Scans QR codes.
*/ */
export function scanQRCodes(frame: Frame): string[] { export function scanQRCodes(frame: Frame): string[] {
'worklet' 'worklet'
return __scanQRCodes(frame) return FrameProcessorPlugins.scanQRCodes(frame)
} }
``` ```
Users will then have to add the Frame Processor Plugin's name to their `babel.config.js`.
For the QR Code Scanner, this will be `__scanQRCodes`:
```js {6}
module.exports = {
plugins: [
[
'react-native-reanimated/plugin',
{
globals: ['__scanQRCodes'],
},
],
```
:::note
You have to restart metro-bundler for changes in the `babel.config.js` file to take effect.
:::
## Test it! ## Test it!
Simply call the wrapper Worklet in your Frame Processor: Simply call the wrapper Worklet in your Frame Processor:
@ -64,11 +45,10 @@ If you want to distribute your Frame Processor Plugin, simply use npm.
1. Create a blank Native Module using [bob](https://github.com/callstack/react-native-builder-bob) or [create-react-native-module](https://github.com/brodybits/create-react-native-module) 1. Create a blank Native Module using [bob](https://github.com/callstack/react-native-builder-bob) or [create-react-native-module](https://github.com/brodybits/create-react-native-module)
2. Name it `vision-camera-plugin-xxxxx` where `xxxxx` is the name of your plugin 2. Name it `vision-camera-plugin-xxxxx` where `xxxxx` is the name of your plugin
3. Remove the generated template code from the Example Native Module 3. Remove the generated template code from the Example Native Module
4. Add VisionCamera to `peerDependencies`: `"react-native-vision-camera": ">= 2"` 4. Add VisionCamera to `peerDependencies`: `"react-native-vision-camera": ">= 3"`
5. Implement the Frame Processor Plugin in the iOS, Android and JS/TS Codebase using the guides above 5. Implement the Frame Processor Plugin in the iOS, Android and JS/TS Codebase using the guides above
6. Add installation instructions to the `README.md` to let users know they have to add your frame processor in the `babel.config.js` configuration. 6. Publish the plugin to npm. Users will only have to install the plugin using `npm i vision-camera-plugin-xxxxx` and add it to their `babel.config.js` file.
7. Publish the plugin to npm. Users will only have to install the plugin using `npm i vision-camera-plugin-xxxxx` and add it to their `babel.config.js` file. 7. [Add the plugin to the **official VisionCamera plugin list**](https://github.com/mrousavy/react-native-vision-camera/edit/main/docs/docs/guides/FRAME_PROCESSOR_PLUGIN_LIST.mdx) for more visibility
8. [Add the plugin to the **official VisionCamera plugin list**](https://github.com/mrousavy/react-native-vision-camera/edit/main/docs/docs/guides/FRAME_PROCESSOR_PLUGIN_LIST.mdx) for more visibility
<br /> <br />

View File

@ -73,7 +73,7 @@ public class QRCodeFrameProcessorPlugin extends FrameProcessorPlugin {
``` ```
:::note :::note
The JS function name will be equal to the name you pass to the `super(...)` call (with a `__` prefix). Make sure it is unique across other Frame Processor Plugins. The Frame Processor Plugin will be exposed to JS through the `FrameProcessorPlugins` object using the name you pass to the `super(...)` call. In this case, it would be `FrameProcessorPlugins.scanQRCodes(...)`.
::: :::
4. **Implement your Frame Processing.** See the [Example Plugin (Java)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/android/app/src/main/java/com/mrousavy/camera/example/ExampleFrameProcessorPlugin.java) for reference. 4. **Implement your Frame Processing.** See the [Example Plugin (Java)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/android/app/src/main/java/com/mrousavy/camera/example/ExampleFrameProcessorPlugin.java) for reference.
@ -137,7 +137,7 @@ class ExampleFrameProcessorPluginKotlin: FrameProcessorPlugin("scanQRCodes") {
``` ```
:::note :::note
The JS function name will be equal to the name you pass to the `FrameProcessorPlugin(...)` call (with a `__` prefix). Make sure it is unique across other Frame Processor Plugins. The Frame Processor Plugin will be exposed to JS through the `FrameProcessorPlugins` object using the name you pass to the `FrameProcessorPlugin(...)` call. In this case, it would be `FrameProcessorPlugins.scanQRCodes(...)`.
::: :::
4. **Implement your Frame Processing.** See the [Example Plugin (Java)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/android/app/src/main/java/com/mrousavy/camera/example/ExampleFrameProcessorPlugin.java) for reference. 4. **Implement your Frame Processing.** See the [Example Plugin (Java)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/android/app/src/main/java/com/mrousavy/camera/example/ExampleFrameProcessorPlugin.java) for reference.

View File

@ -16,7 +16,7 @@ iOS Frame Processor Plugins can be written in either **Objective-C** or **Swift*
### Automatic setup ### Automatic setup
Run [Vision Camera Plugin Builder CLI](https://github.com/mateusz1913/vision-camera-plugin-builder), Run [Vision Camera Plugin Builder CLI](https://github.com/mateusz1913/vision-camera-plugin-builder),
```sh ```sh
npx vision-camera-plugin-builder ios npx vision-camera-plugin-builder ios
@ -63,7 +63,7 @@ VISION_EXPORT_FRAME_PROCESSOR(scanQRCodes)
``` ```
:::note :::note
The JS function name will be equal to the Objective-C function name you choose (with a `__` prefix). Make sure it is unique across other Frame Processor Plugins. The Frame Processor Plugin will be exposed to JS through the `FrameProcessorPlugins` object using the same name as the Objective-C function. In this case, it would be `FrameProcessorPlugins.scanQRCodes(...)`.
::: :::
4. **Implement your Frame Processing.** See the [Example Plugin (Objective-C)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/ios/Frame%20Processor%20Plugins/Example%20Plugin%20%28Objective%2DC%29) for reference. 4. **Implement your Frame Processing.** See the [Example Plugin (Objective-C)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/ios/Frame%20Processor%20Plugins/Example%20Plugin%20%28Objective%2DC%29) for reference.

View File

@ -12,13 +12,10 @@ These are VisionCamera Frame Processor Plugins created by the community.
``` ```
npm i vision-camera-xxxxx npm i vision-camera-xxxxx
cd ios && pod install
``` ```
2. Add the native function's name (the one with the `__` prefix) to your `babel.config.js`. (See their README for instructions) 2. Rebuild your app and use the plugin
:::note
You have to restart metro-bundler for changes in the `babel.config.js` file to take effect.
:::
## Plugin List ## Plugin List

View File

@ -44,7 +44,7 @@ expo install react-native-vision-camera
VisionCamera requires **iOS 11 or higher**, and **Android-SDK version 21 or higher**. See [Troubleshooting](/docs/guides/troubleshooting) if you're having installation issues. VisionCamera requires **iOS 11 or higher**, and **Android-SDK version 21 or higher**. See [Troubleshooting](/docs/guides/troubleshooting) if you're having installation issues.
> **(Optional)** If you want to use [**Frame Processors**](/docs/guides/frame-processors), you need to install [**react-native-reanimated**](https://github.com/software-mansion/react-native-reanimated) 2.2.0 or higher. > **(Optional)** If you want to use [**Frame Processors**](/docs/guides/frame-processors), you need to install [**react-native-worklets**](https://github.com/chrfalch/react-native-worklets) 1.0.0 or higher.
## Updating manifests ## Updating manifests

View File

@ -6,5 +6,5 @@ This is an internal TODO list which I am using to keep track of some of the feat
* [ ] Allow camera switching (front <-> back) while recording and stich videos together * [ ] Allow camera switching (front <-> back) while recording and stich videos together
* [ ] Make `startRecording()` async. Due to NativeModules limitations, we can only have either one callback or one promise in a native function. For `startRecording()` we need both, since you probably also want to catch any errors that occured during a `startRecording()` call (or wait until the recording has actually started, since this can also take some time) * [ ] Make `startRecording()` async. Due to NativeModules limitations, we can only have either one callback or one promise in a native function. For `startRecording()` we need both, since you probably also want to catch any errors that occured during a `startRecording()` call (or wait until the recording has actually started, since this can also take some time)
* [ ] Return a `jsi::Value` reference for images (`UIImage`/`Bitmap`) on `takePhoto()` and `takeSnapshot()`. This way, we skip the entire file writing and reading, making image capture _a lot_ faster. * [ ] Return a `jsi::Value` reference for images (`UIImage`/`Bitmap`) on `takePhoto()` and `takeSnapshot()`. This way, we skip the entire file writing and reading, making image capture _a lot_ faster.
* [ ] Implement frame processors. The idea here is that the user passes a small JS function (reanimated worklet) to the `Camera::frameProcessor` prop which will then get called on every frame the camera previews. (I'd say we cap it to 30 times per second, even if the camera fps is higher) This can then be used to scan QR codes, detect faces, detect depth, render something ontop of the camera such as color filters, QR code boundaries or even dog filters, possibly even use AR - all from a single, small, and highly flexible JS function! * [ ] Implement frame processors. The idea here is that the user passes a small JS function (worklet) to the `Camera::frameProcessor` prop which will then get called on every frame the camera previews. (I'd say we cap it to 30 times per second, even if the camera fps is higher) This can then be used to scan QR codes, detect faces, detect depth, render something ontop of the camera such as color filters, QR code boundaries or even dog filters, possibly even use AR - all from a single, small, and highly flexible JS function!
* [ ] Create a custom MPEG4 encoder to allow for more customizability in `recordVideo()` (`bitRate`, `priority`, `minQuantizationParameter`, `allowFrameReordering`, `expectedFrameRate`, `realTime`, `minimizeMemoryUsage`) * [ ] Create a custom MPEG4 encoder to allow for more customizability in `recordVideo()` (`bitRate`, `priority`, `minQuantizationParameter`, `allowFrameReordering`, `expectedFrameRate`, `realTime`, `minimizeMemoryUsage`)

View File

@ -12,8 +12,6 @@ import com.facebook.react.defaults.DefaultReactNativeHost;
import com.mrousavy.camera.CameraPackage; import com.mrousavy.camera.CameraPackage;
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin; import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
public class MainApplication extends Application implements ReactApplication { public class MainApplication extends Application implements ReactApplication {
@ -47,11 +45,6 @@ public class MainApplication extends Application implements ReactApplication {
protected Boolean isHermesEnabled() { protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED; return BuildConfig.IS_HERMES_ENABLED;
} }
@Override
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
}; };
@Override @Override

View File

@ -13,7 +13,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle") classpath('com.android.tools.build:gradle:7.4.1')
classpath("com.facebook.react:react-native-gradle-plugin") classpath("com.facebook.react:react-native-gradle-plugin")
} }
} }

View File

@ -5,12 +5,8 @@ const pak = require('../package.json');
module.exports = { module.exports = {
presets: ['module:metro-react-native-babel-preset'], presets: ['module:metro-react-native-babel-preset'],
plugins: [ plugins: [
[ ['react-native-reanimated/plugin'],
'react-native-reanimated/plugin', ['react-native-worklets/plugin'],
{
globals: ['__example_plugin', '__example_plugin_swift'],
},
],
[ [
'module-resolver', 'module-resolver',
{ {

View File

@ -283,6 +283,10 @@ PODS:
- react-native-video/Video (= 5.2.1) - react-native-video/Video (= 5.2.1)
- react-native-video/Video (5.2.1): - react-native-video/Video (5.2.1):
- React-Core - React-Core
- react-native-worklets (0.1.0):
- React
- React-callinvoker
- React-Core
- React-perflogger (0.71.2) - React-perflogger (0.71.2)
- React-RCTActionSheet (0.71.2): - React-RCTActionSheet (0.71.2):
- React-Core/RCTActionSheetHeaders (= 0.71.2) - React-Core/RCTActionSheetHeaders (= 0.71.2)
@ -369,7 +373,7 @@ PODS:
- React-perflogger (= 0.71.2) - React-perflogger (= 0.71.2)
- RNGestureHandler (2.9.0): - RNGestureHandler (2.9.0):
- React-Core - React-Core
- RNReanimated (2.14.4): - RNReanimated (3.0.0-rc.10):
- DoubleConversion - DoubleConversion
- FBLazyVector - FBLazyVector
- FBReactNativeSpec - FBReactNativeSpec
@ -407,6 +411,7 @@ PODS:
- React - React
- React-callinvoker - React-callinvoker
- React-Core - React-Core
- react-native-worklets
- Yoga (1.14.0) - Yoga (1.14.0)
DEPENDENCIES: DEPENDENCIES:
@ -437,6 +442,7 @@ DEPENDENCIES:
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)" - "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-video (from `../node_modules/react-native-video`) - react-native-video (from `../node_modules/react-native-video`)
- react-native-worklets (from `../node_modules/react-native-worklets`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
@ -514,6 +520,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/slider" :path: "../node_modules/@react-native-community/slider"
react-native-video: react-native-video:
:path: "../node_modules/react-native-video" :path: "../node_modules/react-native-video"
react-native-worklets:
:path: "../node_modules/react-native-worklets"
React-perflogger: React-perflogger:
:path: "../node_modules/react-native/ReactCommon/reactperflogger" :path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-RCTActionSheet: React-RCTActionSheet:
@ -583,6 +591,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d
react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253 react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253
react-native-worklets: c7576ad4ad0f030ff41e8d74ad0077c96054a6c1
React-perflogger: c7ccda3d1d1da837f7ff4e54e816022a6803ee87 React-perflogger: c7ccda3d1d1da837f7ff4e54e816022a6803ee87
React-RCTActionSheet: 01c125aebbad462a24228f68c584c7a921d6c28e React-RCTActionSheet: 01c125aebbad462a24228f68c584c7a921d6c28e
React-RCTAnimation: 5277a9440acffc4a5b7baa6ae3880fe467277ae6 React-RCTAnimation: 5277a9440acffc4a5b7baa6ae3880fe467277ae6
@ -597,11 +606,11 @@ SPEC CHECKSUMS:
React-runtimeexecutor: 4bf9a9086d27f74065fce1dddac274aa95216952 React-runtimeexecutor: 4bf9a9086d27f74065fce1dddac274aa95216952
ReactCommon: f697c0ac52e999aa818e43e2b6f277787c735e2d ReactCommon: f697c0ac52e999aa818e43e2b6f277787c735e2d
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
RNReanimated: cc5e3aa479cb9170bcccf8204291a6950a3be128 RNReanimated: fbc356493970e3acddc15586b1bccb5eab3ff1ec
RNScreens: ea4cd3a853063cda19a4e3c28d2e52180c80f4eb RNScreens: ea4cd3a853063cda19a4e3c28d2e52180c80f4eb
RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8 RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
VisionCamera: b9345e7da5eff343cc1603dd19153e2b9acd0c07 VisionCamera: 312151eb95370d1d764720de3b7dad33d8c7fb40
Yoga: 5b0304b3dbef2b52e078052138e23a19c7dacaef Yoga: 5b0304b3dbef2b52e078052138e23a19c7dacaef
PODFILE CHECKSUM: d53724fe402c2547f1dd1cc571bbe77d9820e636 PODFILE CHECKSUM: d53724fe402c2547f1dd1cc571bbe77d9820e636

View File

@ -14,8 +14,8 @@
"typescript": "tsc --noEmit" "typescript": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@react-native-community/blur": "^4.3.0",
"@react-native-camera-roll/camera-roll": "^5.2.3", "@react-native-camera-roll/camera-roll": "^5.2.3",
"@react-native-community/blur": "^4.3.0",
"@react-native-community/slider": "^4.4.2", "@react-native-community/slider": "^4.4.2",
"@react-navigation/native": "^6.1.3", "@react-navigation/native": "^6.1.3",
"@react-navigation/native-stack": "^6.9.9", "@react-navigation/native-stack": "^6.9.9",
@ -23,12 +23,13 @@
"react-native": "^0.71.2", "react-native": "^0.71.2",
"react-native-gesture-handler": "^2.9.0", "react-native-gesture-handler": "^2.9.0",
"react-native-pressable-opacity": "^1.0.10", "react-native-pressable-opacity": "^1.0.10",
"react-native-reanimated": "^2.14.4", "react-native-reanimated": "^3.0.0-rc.10",
"react-native-safe-area-context": "^4.5.0", "react-native-safe-area-context": "^4.5.0",
"react-native-screens": "^3.19.0", "react-native-screens": "^3.19.0",
"react-native-static-safe-area-insets": "^2.2.0", "react-native-static-safe-area-insets": "^2.2.0",
"react-native-vector-icons": "^9.2.0", "react-native-vector-icons": "^9.2.0",
"react-native-video": "^5.2.1" "react-native-video": "^5.2.1",
"react-native-worklets": "https://github.com/chrfalch/react-native-worklets#50950aa"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.12", "@babel/core": "^7.20.12",

View File

@ -6,6 +6,7 @@ import { MediaPage } from './MediaPage';
import { CameraPage } from './CameraPage'; import { CameraPage } from './CameraPage';
import type { Routes } from './Routes'; import type { Routes } from './Routes';
import { Camera, CameraPermissionStatus } from 'react-native-vision-camera'; import { Camera, CameraPermissionStatus } from 'react-native-vision-camera';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
const Stack = createNativeStackNavigator<Routes>(); const Stack = createNativeStackNavigator<Routes>();
@ -28,24 +29,26 @@ export function App(): React.ReactElement | null {
const showPermissionsPage = cameraPermission !== 'authorized' || microphonePermission === 'not-determined'; const showPermissionsPage = cameraPermission !== 'authorized' || microphonePermission === 'not-determined';
return ( return (
<NavigationContainer> <NavigationContainer>
<Stack.Navigator <GestureHandlerRootView style={{ flex: 1 }}>
screenOptions={{ <Stack.Navigator
headerShown: false, screenOptions={{
statusBarStyle: 'dark', headerShown: false,
animationTypeForReplace: 'push', statusBarStyle: 'dark',
}} animationTypeForReplace: 'push',
initialRouteName={showPermissionsPage ? 'PermissionsPage' : 'CameraPage'}>
<Stack.Screen name="PermissionsPage" component={PermissionsPage} />
<Stack.Screen name="CameraPage" component={CameraPage} />
<Stack.Screen
name="MediaPage"
component={MediaPage}
options={{
animation: 'none',
presentation: 'transparentModal',
}} }}
/> initialRouteName={showPermissionsPage ? 'PermissionsPage' : 'CameraPage'}>
</Stack.Navigator> <Stack.Screen name="PermissionsPage" component={PermissionsPage} />
<Stack.Screen name="CameraPage" component={CameraPage} />
<Stack.Screen
name="MediaPage"
component={MediaPage}
options={{
animation: 'none',
presentation: 'transparentModal',
}}
/>
</Stack.Navigator>
</GestureHandlerRootView>
</NavigationContainer> </NavigationContainer>
); );
} }

View File

@ -1,20 +1,16 @@
/* global __example_plugin __example_plugin_swift */ import { FrameProcessorPlugins, Frame } from 'react-native-vision-camera';
import type { Frame } from 'react-native-vision-camera';
declare let _WORKLET: true | undefined;
export function examplePluginSwift(frame: Frame): string[] { export function examplePluginSwift(frame: Frame): string[] {
'worklet'; 'worklet';
if (!_WORKLET) throw new Error('examplePluginSwift must be called from a frame processor!');
// @ts-expect-error because this function is dynamically injected by VisionCamera // @ts-expect-error because this function is dynamically injected by VisionCamera
return __example_plugin_swift(frame, 'hello!', 'parameter2', true, 42, { test: 0, second: 'test' }, ['another test', 5]); return FrameProcessorPlugins.example_plugin_swift(frame, 'hello!', 'parameter2', true, 42, { test: 0, second: 'test' }, [
'another test',
5,
]);
} }
export function examplePlugin(frame: Frame): string[] { export function examplePlugin(frame: Frame): string[] {
'worklet'; 'worklet';
if (!_WORKLET) throw new Error('examplePlugin must be called from a frame processor!');
// @ts-expect-error because this function is dynamically injected by VisionCamera // @ts-expect-error because this function is dynamically injected by VisionCamera
return __example_plugin(frame, 'hello!', 'parameter2', true, 42, { test: 0, second: 'test' }, ['another test', 5]); return FrameProcessorPlugins.example_plugin(frame, 'hello!', 'parameter2', true, 42, { test: 0, second: 'test' }, ['another test', 5]);
} }

View File

@ -1190,7 +1190,7 @@
dependencies: dependencies:
"@hapi/hoek" "^9.0.0" "@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0": "@sideway/formula@^3.0.1":
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
@ -2259,9 +2259,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.4.284: electron-to-chromium@^1.4.284:
version "1.4.292" version "1.4.294"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.292.tgz#e3a3dca3780c8ce01e2c1866b5ec2fbe31c423e3" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.294.tgz#ad80317b85f0859a9454680fbc1c726fefa7e6fd"
integrity sha512-ESWOSyJy5odDlE8wvh5NNAMORv4r6assPwIPGHEMWrWD0SONXcG/xT+9aD9CQyeRwyYDPo6dJT4Bbeg5uevVQQ== integrity sha512-PuHZB3jEN7D8WPPjLmBQAsqQz8tWHlkkB4n0E2OYw8RwVdmBYV0Wn+rUFH8JqYyIRb4HQhhedgxlZL163wqLrQ==
eme-encryption-scheme-polyfill@^2.0.1: eme-encryption-scheme-polyfill@^2.0.1:
version "2.1.1" version "2.1.1"
@ -2884,7 +2884,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
@ -3162,11 +3162,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
internal-slot@^1.0.3, internal-slot@^1.0.4: internal-slot@^1.0.3, internal-slot@^1.0.4:
version "1.0.4" version "1.0.5"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ== integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
dependencies: dependencies:
get-intrinsic "^1.1.3" get-intrinsic "^1.2.0"
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.4" side-channel "^1.0.4"
@ -3550,14 +3550,14 @@ jest-worker@^27.2.0:
supports-color "^8.0.0" supports-color "^8.0.0"
joi@^17.2.1: joi@^17.2.1:
version "17.7.0" version "17.7.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3" resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.1.tgz#854fc85c7fa3cfc47c91124d30bffdbb58e06cec"
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg== integrity sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==
dependencies: dependencies:
"@hapi/hoek" "^9.0.0" "@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0" "@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.3" "@sideway/address" "^4.1.3"
"@sideway/formula" "^3.0.0" "@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0" "@sideway/pinpoint" "^2.0.0"
js-sdsl@^4.1.4: js-sdsl@^4.1.4:
@ -4444,9 +4444,9 @@ minimatch@^5.0.1:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimist@^1.2.6: minimist@^1.2.6:
version "1.2.7" version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
mixin-deep@^1.2.0: mixin-deep@^1.2.0:
version "1.3.2" version "1.3.2"
@ -4984,9 +4984,9 @@ range-parser@~1.2.1:
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
react-devtools-core@^4.26.1: react-devtools-core@^4.26.1:
version "4.27.1" version "4.27.2"
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.1.tgz#167aa174383c65786cbb7e965a5b39c702f0a2d3" resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.2.tgz#d20fc57e258c656eedabafc2c851d38b33583148"
integrity sha512-qXhcxxDWiFmFAOq48jts9YQYe1+wVoUXzJTlY4jbaATzyio6dd6CUGu3dXBhREeVgpZ+y4kg6vFJzIOZh6vY2w== integrity sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==
dependencies: dependencies:
shell-quote "^1.6.1" shell-quote "^1.6.1"
ws "^7" ws "^7"
@ -5042,10 +5042,10 @@ react-native-pressable-opacity@^1.0.10:
resolved "https://registry.yarnpkg.com/react-native-pressable-opacity/-/react-native-pressable-opacity-1.0.10.tgz#799df1a913d3b28f42ada765465fe7723eb7166d" resolved "https://registry.yarnpkg.com/react-native-pressable-opacity/-/react-native-pressable-opacity-1.0.10.tgz#799df1a913d3b28f42ada765465fe7723eb7166d"
integrity sha512-Py9YH9TlS3Lv1so5JCj6bgiqkeYYGupF4ZImlpoyhhId/t/RiSqR68LlASOHgdctqQuqVJObQiFfzX8oZI9+6w== integrity sha512-Py9YH9TlS3Lv1so5JCj6bgiqkeYYGupF4ZImlpoyhhId/t/RiSqR68LlASOHgdctqQuqVJObQiFfzX8oZI9+6w==
react-native-reanimated@^2.14.4: react-native-reanimated@^3.0.0-rc.10:
version "2.14.4" version "3.0.0-rc.10"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.14.4.tgz#3fa3da4e7b99f5dfb28f86bcf24d9d1024d38836" resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.0.0-rc.10.tgz#40e5b628759aa81f94317fd0301231292b4eacf5"
integrity sha512-DquSbl7P8j4SAmc+kRdd75Ianm8G+IYQ9T4AQ6lrpLVeDkhZmjWI0wkutKWnp6L7c5XNVUrFDUf69dwETLCItQ== integrity sha512-0P2jSO+dXHRxSzqSxNp08VaUy89nqeUIvqBS0wlI8lsli8CJcqulL3pjNqTGzBkxXjt13mGdIzJv4u9lSjHPzg==
dependencies: dependencies:
"@babel/plugin-transform-object-assign" "^7.16.7" "@babel/plugin-transform-object-assign" "^7.16.7"
"@babel/preset-typescript" "^7.16.7" "@babel/preset-typescript" "^7.16.7"
@ -5091,6 +5091,10 @@ react-native-video@^5.2.1:
prop-types "^15.7.2" prop-types "^15.7.2"
shaka-player "^2.5.9" shaka-player "^2.5.9"
"react-native-worklets@https://github.com/chrfalch/react-native-worklets#50950aa":
version "0.1.0"
resolved "https://github.com/chrfalch/react-native-worklets#50950aa1b671a0d8a9e79878e63a3445991e7192"
react-native@^0.71.2: react-native@^0.71.2:
version "0.71.2" version "0.71.2"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.2.tgz#b6977eda2a6dc10baa006bf4ab1ee08318607ce9" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.2.tgz#b6977eda2a6dc10baa006bf4ab1ee08318607ce9"

View File

@ -19,12 +19,6 @@
#import "RCTBridge+runOnJS.h" #import "RCTBridge+runOnJS.h"
#import "JSConsoleHelper.h" #import "JSConsoleHelper.h"
#ifdef VISION_CAMERA_DISABLE_FRAME_PROCESSORS
static bool VISION_CAMERA_ENABLE_FRAME_PROCESSORS = false;
#else
static bool VISION_CAMERA_ENABLE_FRAME_PROCESSORS = true;
#endif
@interface CameraBridge: RCTViewManager @interface CameraBridge: RCTViewManager
@end @end

View File

@ -29,10 +29,10 @@ extension CameraView {
internal func updateOrientation() { internal func updateOrientation() {
// Updates the Orientation for all rotable // Updates the Orientation for all rotable
let isMirrored = self.videoDeviceInput?.device.position == .front let isMirrored = videoDeviceInput?.device.position == .front
let connectionOrientation = self.outputOrientation let connectionOrientation = outputOrientation
self.captureSession.outputs.forEach { output in captureSession.outputs.forEach { output in
output.connections.forEach { connection in output.connections.forEach { connection in
if connection.isVideoMirroringSupported { if connection.isVideoMirroringSupported {
connection.automaticallyAdjustsVideoMirroring = false connection.automaticallyAdjustsVideoMirroring = false

View File

@ -60,6 +60,8 @@ RCT_EXTERN_METHOD(stopRecording:(nonnull NSNumber *)node resolve:(RCTPromiseReso
RCT_EXTERN_METHOD(takePhoto:(nonnull NSNumber *)node options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(takePhoto:(nonnull NSNumber *)node options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject);
RCT_EXTERN_METHOD(focus:(nonnull NSNumber *)node point:(NSDictionary *)point resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(focus:(nonnull NSNumber *)node point:(NSDictionary *)point resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject);
// Static Methods
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(installFrameProcessorBindings);
RCT_EXTERN_METHOD(getAvailableVideoCodecs:(nonnull NSNumber *)node fileType:(NSString *)fileType resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(getAvailableVideoCodecs:(nonnull NSNumber *)node fileType:(NSString *)fileType resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject);
@end @end

View File

@ -15,20 +15,6 @@ final class CameraViewManager: RCTViewManager {
private var runtimeManager: FrameProcessorRuntimeManager? private var runtimeManager: FrameProcessorRuntimeManager?
override var bridge: RCTBridge! {
didSet {
// Install Frame Processor bindings and setup Runtime
if VISION_CAMERA_ENABLE_FRAME_PROCESSORS {
CameraQueues.frameProcessorQueue.async {
self.runtimeManager = FrameProcessorRuntimeManager(bridge: self.bridge)
self.bridge.runOnJS {
self.runtimeManager!.installFrameProcessorBindings()
}
}
}
}
}
override var methodQueue: DispatchQueue! { override var methodQueue: DispatchQueue! {
return DispatchQueue.main return DispatchQueue.main
} }
@ -43,6 +29,14 @@ final class CameraViewManager: RCTViewManager {
// pragma MARK: React Functions // pragma MARK: React Functions
@objc
final func installFrameProcessorBindings() -> NSNumber {
// Runs on JS Thread
runtimeManager = FrameProcessorRuntimeManager()
runtimeManager!.installFrameProcessorBindings()
return NSNumber(booleanLiteral: true)
}
@objc @objc
final func startRecording(_ node: NSNumber, options: NSDictionary, onRecordCallback: @escaping RCTResponseSenderBlock) { final func startRecording(_ node: NSNumber, options: NSDictionary, onRecordCallback: @escaping RCTResponseSenderBlock) {
let component = getCameraView(withTag: node) let component = getCameraView(withTag: node)

View File

@ -28,14 +28,13 @@
* * Make sure your frame processor returns a Value that can be converted to JS * * Make sure your frame processor returns a Value that can be converted to JS
* * Make sure to use this Macro in an @implementation, not @interface * * Make sure to use this Macro in an @implementation, not @interface
* *
* The JS function will have the same name as the given Objective-C function, but with a "__" prefix. * The JS function will have the same name as the given Objective-C function. It can be accessed through the FrameProcessorPlugins object exposed by VisionCamera.
* Make sure to add that function to the babel.config.js under reanimated's "globals" option, and add TypeScript type declarations.
*/ */
#define VISION_EXPORT_FRAME_PROCESSOR(frame_processor) \ #define VISION_EXPORT_FRAME_PROCESSOR(frame_processor) \
\ \
+(void)load \ +(void)load \
{ \ { \
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@"__" @ #frame_processor callback:^id(Frame* frame, NSArray<id>* args) { \ [FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #frame_processor callback:^id(Frame* frame, NSArray<id>* args) { \
return frame_processor(frame, args); \ return frame_processor(frame, args); \
}]; \ }]; \
} }
@ -55,7 +54,7 @@ objc_name : NSObject<FrameProcessorPluginBase>
\ \
+(void)load \ +(void)load \
{ \ { \
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@"__" @ #name callback:^id(Frame* frame, NSArray<id>* args) { \ [FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #name callback:^id(Frame* frame, NSArray<id>* args) { \
return [objc_name callback:frame withArgs:args]; \ return [objc_name callback:frame withArgs:args]; \
}]; \ }]; \
} }

View File

@ -13,13 +13,7 @@
@interface FrameProcessorRuntimeManager : NSObject @interface FrameProcessorRuntimeManager : NSObject
- (instancetype)init NS_UNAVAILABLE; - (instancetype) init;
/**
Initializes the Frame Processor Runtime Manager with the given bridge.
This init is not thread safe, so only init this on the Thread you want the runtime to run on.
*/
- (instancetype) initWithBridge:(RCTBridge*)bridge;
- (void) installFrameProcessorBindings; - (void) installFrameProcessorBindings;

View File

@ -19,25 +19,12 @@
#import <React/RCTUIManager.h> #import <React/RCTUIManager.h>
#import <ReactCommon/RCTTurboModuleManager.h> #import <ReactCommon/RCTTurboModuleManager.h>
#ifndef VISION_CAMERA_DISABLE_FRAME_PROCESSORS #import "JsiWorkletContext.h"
#if __has_include(<RNReanimated/NativeReanimatedModule.h>) #import "JsiWorkletApi.h"
#if __has_include(<RNReanimated/RuntimeManager.h>) #import "JsiWorklet.h"
#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!
#endif
#else
#warning The NativeReanimatedModule.h header could not be found, Frame Processors are disabled. If you want to use Frame Processors, make sure you install react-native-reanimated!
#endif
#endif
#import "FrameProcessorUtils.h" #import "FrameProcessorUtils.h"
#import "FrameProcessorCallback.h" #import "FrameProcessorCallback.h"
#import "../React Utils/MakeJSIRuntime.h"
#import "../React Utils/JSIUtils.h" #import "../React Utils/JSIUtils.h"
// Forward declarations for the Swift classes // Forward declarations for the Swift classes
@ -51,84 +38,101 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
@end @end
@implementation FrameProcessorRuntimeManager { @implementation FrameProcessorRuntimeManager {
#ifdef ENABLE_FRAME_PROCESSORS std::shared_ptr<RNWorklet::JsiWorkletContext> workletContext;
std::unique_ptr<reanimated::RuntimeManager> runtimeManager;
#endif
__weak RCTBridge* weakBridge;
} }
- (instancetype) initWithBridge:(RCTBridge*)bridge { - (instancetype)init {
self = [super init]; if (self = [super init]) {
if (self) { // Initialize self
#ifdef ENABLE_FRAME_PROCESSORS
NSLog(@"FrameProcessorBindings: Creating Runtime Manager...");
weakBridge = bridge;
auto runtime = vision::makeJSIRuntime();
reanimated::RuntimeDecorator::decorateRuntime(*runtime, "FRAME_PROCESSOR");
runtime->global().setProperty(*runtime, "_FRAME_PROCESSOR", jsi::Value(true));
auto callInvoker = bridge.jsCallInvoker;
auto scheduler = std::make_shared<vision::VisionCameraScheduler>(callInvoker);
runtimeManager = std::make_unique<reanimated::RuntimeManager>(std::move(runtime),
std::make_shared<reanimated::REAIOSErrorHandler>(scheduler),
scheduler);
NSLog(@"FrameProcessorBindings: Runtime Manager created!");
NSLog(@"FrameProcessorBindings: Installing Frame Processor plugins...");
auto& visionRuntime = *runtimeManager->runtime;
auto visionGlobal = visionRuntime.global();
for (NSString* pluginKey in [FrameProcessorPluginRegistry frameProcessorPlugins]) {
auto pluginName = [pluginKey UTF8String];
NSLog(@"FrameProcessorBindings: Installing Frame Processor plugin \"%s\"...", pluginName);
FrameProcessorPlugin callback = [[FrameProcessorPluginRegistry frameProcessorPlugins] valueForKey:pluginKey];
auto function = [callback, callInvoker](jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
auto frameHostObject = arguments[0].asObject(runtime).asHostObject(runtime);
auto frame = static_cast<FrameHostObject*>(frameHostObject.get());
auto args = convertJSICStyleArrayToNSArray(runtime,
arguments + 1, // start at index 1 since first arg = Frame
count - 1, // use smaller count
callInvoker);
id result = callback(frame->frame, args);
return convertObjCObjectToJSIValue(runtime, result);
};
visionGlobal.setProperty(visionRuntime, pluginName, jsi::Function::createFromHostFunction(visionRuntime,
jsi::PropNameID::forAscii(visionRuntime, pluginName),
1, // frame
function));
} }
return self;
}
NSLog(@"FrameProcessorBindings: Frame Processor plugins installed!"); - (void) setupWorkletContext:(jsi::Runtime&)runtime {
#else NSLog(@"FrameProcessorBindings: Creating Worklet Context...");
NSLog(@"Reanimated not found, Frame Processors are disabled.");
#endif auto callInvoker = RCTBridge.currentBridge.jsCallInvoker;
auto runOnJS = [callInvoker](std::function<void()>&& f) {
// Run on React JS Runtime
callInvoker->invokeAsync(std::move(f));
};
auto runOnWorklet = [](std::function<void()>&& f) {
// Run on Frame Processor Worklet Runtime
dispatch_async(CameraQueues.frameProcessorQueue, [f = std::move(f)](){
f();
});
};
workletContext = std::make_shared<RNWorklet::JsiWorkletContext>("VisionCamera");
workletContext->initialize("VisionCamera",
&runtime,
runOnJS,
runOnWorklet);
NSLog(@"FrameProcessorBindings: Worklet Context Created!");
NSLog(@"FrameProcessorBindings: Installing Frame Processor plugins...");
jsi::Object frameProcessorPlugins(runtime);
// Iterate through all registered plugins (+init)
for (NSString* pluginKey in [FrameProcessorPluginRegistry frameProcessorPlugins]) {
auto pluginName = [pluginKey UTF8String];
NSLog(@"FrameProcessorBindings: Installing Frame Processor plugin \"%s\"...", pluginName);
// Get the Plugin callback func
FrameProcessorPlugin callback = [[FrameProcessorPluginRegistry frameProcessorPlugins] valueForKey:pluginKey];
// Create the JSI host function
auto function = [callback, callInvoker](jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
// Get the first parameter, which is always the native Frame Host Object.
auto frameHostObject = arguments[0].asObject(runtime).asHostObject(runtime);
auto frame = static_cast<FrameHostObject*>(frameHostObject.get());
// Convert any additional parameters to the Frame Processor to ObjC objects
auto args = convertJSICStyleArrayToNSArray(runtime,
arguments + 1, // start at index 1 since first arg = Frame
count - 1, // use smaller count
callInvoker);
// Call the FP Plugin, which might return something.
id result = callback(frame->frame, args);
// Convert the return value (or null) to a JS Value and return it to JS
return convertObjCObjectToJSIValue(runtime, result);
};
// Assign it to the Proxy.
// A FP Plugin called "example_plugin" can be now called from JS using "FrameProcessorPlugins.example_plugin(frame)"
frameProcessorPlugins.setProperty(runtime,
pluginName,
jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forAscii(runtime, pluginName),
1, // frame
function));
} }
return self;
// global.FrameProcessorPlugins Proxy
runtime.global().setProperty(runtime, "FrameProcessorPlugins", frameProcessorPlugins);
NSLog(@"FrameProcessorBindings: Frame Processor plugins installed!");
} }
- (void) installFrameProcessorBindings { - (void) installFrameProcessorBindings {
#ifdef ENABLE_FRAME_PROCESSORS
if (!weakBridge) {
NSLog(@"FrameProcessorBindings: Failed to install Frame Processor Bindings - bridge was null!");
return;
}
NSLog(@"FrameProcessorBindings: Installing Frame Processor Bindings for Bridge..."); NSLog(@"FrameProcessorBindings: Installing Frame Processor Bindings for Bridge...");
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)weakBridge; RCTCxxBridge *cxxBridge = (RCTCxxBridge *)[RCTBridge currentBridge];
if (!cxxBridge.runtime) { if (!cxxBridge.runtime) {
return; return;
} }
jsi::Runtime& jsiRuntime = *(jsi::Runtime*)cxxBridge.runtime; jsi::Runtime& jsiRuntime = *(jsi::Runtime*)cxxBridge.runtime;
// Install the Worklet Runtime in the main React JS Runtime
[self setupWorkletContext:jsiRuntime];
NSLog(@"FrameProcessorBindings: Installing global functions..."); NSLog(@"FrameProcessorBindings: Installing global functions...");
// setFrameProcessor(viewTag: number, frameProcessor: (frame: Frame) => void) // setFrameProcessor(viewTag: number, frameProcessor: (frame: Frame) => void)
@ -139,27 +143,21 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
NSLog(@"FrameProcessorBindings: Setting new frame processor..."); NSLog(@"FrameProcessorBindings: Setting new frame processor...");
if (!arguments[0].isNumber()) throw jsi::JSError(runtime, "Camera::setFrameProcessor: First argument ('viewTag') must be a number!"); if (!arguments[0].isNumber()) throw jsi::JSError(runtime, "Camera::setFrameProcessor: First argument ('viewTag') must be a number!");
if (!arguments[1].isObject()) throw jsi::JSError(runtime, "Camera::setFrameProcessor: Second argument ('frameProcessor') must be a function!"); if (!arguments[1].isObject()) throw jsi::JSError(runtime, "Camera::setFrameProcessor: Second argument ('frameProcessor') must be a function!");
if (!runtimeManager || !runtimeManager->runtime) throw jsi::JSError(runtime, "Camera::setFrameProcessor: The RuntimeManager is not yet initialized!");
auto viewTag = arguments[0].asNumber(); auto viewTag = arguments[0].asNumber();
NSLog(@"FrameProcessorBindings: Adapting Shareable value from function (conversion to worklet)..."); NSLog(@"FrameProcessorBindings: Converting JSI Function to Worklet...");
auto worklet = reanimated::ShareableValue::adapt(runtime, arguments[1], runtimeManager.get()); auto worklet = std::make_shared<RNWorklet::JsiWorklet>(runtime, arguments[1]);
NSLog(@"FrameProcessorBindings: Successfully created worklet!");
RCTExecuteOnMainQueue([=]() { RCTExecuteOnMainQueue([=]() {
auto currentBridge = [RCTBridge currentBridge]; auto currentBridge = [RCTBridge currentBridge];
auto anonymousView = [currentBridge.uiManager viewForReactTag:[NSNumber numberWithDouble:viewTag]]; auto anonymousView = [currentBridge.uiManager viewForReactTag:[NSNumber numberWithDouble:viewTag]];
auto view = static_cast<CameraView*>(anonymousView); auto view = static_cast<CameraView*>(anonymousView);
dispatch_async(CameraQueues.frameProcessorQueue, [=]() { NSLog(@"FrameProcessorBindings: Converting worklet to Objective-C callback...");
NSLog(@"FrameProcessorBindings: Converting worklet to Objective-C callback...");
auto& rt = *runtimeManager->runtime; view.frameProcessorCallback = convertWorkletToFrameProcessorCallback(workletContext->getWorkletRuntime(), worklet);
auto function = worklet->getValue(rt).asObject(rt).asFunction(rt);
view.frameProcessorCallback = convertJSIFunctionToFrameProcessorCallback(rt, function); NSLog(@"FrameProcessorBindings: Frame processor set!");
NSLog(@"FrameProcessorBindings: Frame processor set!");
});
}); });
return jsi::Value::undefined(); return jsi::Value::undefined();
@ -198,7 +196,6 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
unsetFrameProcessor)); unsetFrameProcessor));
NSLog(@"FrameProcessorBindings: Finished installing bindings."); NSLog(@"FrameProcessorBindings: Finished installing bindings.");
#endif
} }
@end @end

View File

@ -17,7 +17,9 @@
#endif #endif
#import <jsi/jsi.h> #import <jsi/jsi.h>
#import "JsiWorklet.h"
#import <memory>
using namespace facebook; using namespace facebook;
FrameProcessorCallback convertJSIFunctionToFrameProcessorCallback(jsi::Runtime &runtime, const jsi::Function &value); FrameProcessorCallback convertWorkletToFrameProcessorCallback(jsi::Runtime& runtime, std::shared_ptr<RNWorklet::JsiWorklet> worklet);

View File

@ -19,18 +19,25 @@
#import "JSConsoleHelper.h" #import "JSConsoleHelper.h"
#import <ReactCommon/RCTTurboModule.h> #import <ReactCommon/RCTTurboModule.h>
FrameProcessorCallback convertJSIFunctionToFrameProcessorCallback(jsi::Runtime& runtime, const jsi::Function& value) { #import "JsiWorklet.h"
__block auto cb = value.getFunction(runtime);
FrameProcessorCallback convertWorkletToFrameProcessorCallback(jsi::Runtime& runtime, std::shared_ptr<RNWorklet::JsiWorklet> worklet) {
auto workletInvoker = std::make_shared<RNWorklet::WorkletInvoker>(worklet);
// Converts a Worklet to a callable Objective-C block function
return ^(Frame* frame) { return ^(Frame* frame) {
auto frameHostObject = std::make_shared<FrameHostObject>(frame); auto frameHostObject = std::make_shared<FrameHostObject>(frame);
try { try {
cb.callWithThis(runtime, cb, jsi::Object::createFromHostObject(runtime, frameHostObject)); // Call JS Frame Processor function with boxed Frame Host Object
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
jsi::Value jsValue(std::move(argument));
workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
} catch (jsi::JSError& jsError) { } catch (jsi::JSError& jsError) {
auto stack = std::regex_replace(jsError.getStack(), std::regex("\n"), "\n "); auto stack = std::regex_replace(jsError.getStack(), std::regex("\n"), "\n ");
auto message = [NSString stringWithFormat:@"Frame Processor threw an error: %s\nIn: %s", jsError.getMessage().c_str(), stack.c_str()]; auto message = [NSString stringWithFormat:@"Frame Processor threw an error: %s\nIn: %s", jsError.getMessage().c_str(), stack.c_str()];
RCTBridge* bridge = [RCTBridge currentBridge]; RCTBridge* bridge = [RCTBridge currentBridge];
if (bridge != nil) { if (bridge != nil) {
bridge.jsCallInvoker->invokeAsync([bridge, message]() { bridge.jsCallInvoker->invokeAsync([bridge, message]() {

View File

@ -1,38 +0,0 @@
//
// VisionCameraScheduler.h
// VisionCamera
//
// Created by Marc Rousavy on 23.07.21.
// Copyright © 2021 mrousavy. All rights reserved.
//
#pragma once
#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 {
class VisionCameraScheduler : public reanimated::Scheduler {
public:
VisionCameraScheduler(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
virtual ~VisionCameraScheduler();
void scheduleOnUI(std::function<void()> job) override;
};
} // namespace vision

View File

@ -1,39 +0,0 @@
//
// VisionCameraScheduler.mm
// VisionCamera
//
// Created by Marc Rousavy on 23.07.21.
// Copyright © 2021 mrousavy. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "VisionCameraScheduler.h"
#import <React-callinvoker/ReactCommon/CallInvoker.h>
// Forward declarations for the Swift classes
__attribute__((objc_runtime_name("_TtC12VisionCamera12CameraQueues")))
@interface CameraQueues : NSObject
@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull frameProcessorQueue;
@end
namespace vision {
using namespace facebook;
VisionCameraScheduler::VisionCameraScheduler(std::shared_ptr<react::CallInvoker> jsInvoker) {
this->jsCallInvoker_ = jsInvoker;
}
// does not schedule on UI thread but rather on Frame Processor Thread
void VisionCameraScheduler::scheduleOnUI(std::function<void()> job) {
dispatch_async(CameraQueues.frameProcessorQueue, ^{
job();
});
}
VisionCameraScheduler::~VisionCameraScheduler(){
}
} // namespace vision

View File

@ -1,42 +0,0 @@
//
// MakeJSIRuntime.h
// VisionCamera
//
// Created by Marc Rousavy on 06.07.21.
// Copyright © 2021 mrousavy. All rights reserved.
//
#pragma once
#include <jsi/jsi.h>
#include <memory>
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
// Hermes (https://hermesengine.dev) (RN 0.65+)
#include <reacthermes/HermesExecutorFactory.h>
#elif __has_include(<hermes/hermes.h>)
// Hermes (https://hermesengine.dev)
#include <hermes/hermes.h>
#elif __has_include(<v8runtime/V8RuntimeFactory.h>)
// V8 (https://github.com/Kudo/react-native-v8)
#include <v8runtime/V8RuntimeFactory.h>
#else
// JSC
#include <jsi/JSCRuntime.h>
#endif
using namespace facebook;
namespace vision {
static std::unique_ptr<jsi::Runtime> makeJSIRuntime() {
#if __has_include(<hermes/hermes.h>) || __has_include(<reacthermes/HermesExecutorFactory.h>)
return facebook::hermes::makeHermesRuntime();
#elif __has_include(<v8runtime/V8RuntimeFactory.h>)
return facebook::createV8Runtime("");
#else
return facebook::jsc::makeJSCRuntime();
#endif
}
} // namespace vision

View File

@ -94,7 +94,6 @@
B84760A22608EE38004C3180 /* FrameHostObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameHostObject.h; sourceTree = "<group>"; }; B84760A22608EE38004C3180 /* FrameHostObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameHostObject.h; sourceTree = "<group>"; };
B84760A52608EE7C004C3180 /* FrameHostObject.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FrameHostObject.mm; sourceTree = "<group>"; }; B84760A52608EE7C004C3180 /* FrameHostObject.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FrameHostObject.mm; sourceTree = "<group>"; };
B84760DE2608F57D004C3180 /* CameraQueues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraQueues.swift; sourceTree = "<group>"; }; B84760DE2608F57D004C3180 /* CameraQueues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraQueues.swift; sourceTree = "<group>"; };
B84C10592694A182006EFA70 /* MakeJSIRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MakeJSIRuntime.h; sourceTree = "<group>"; };
B864004F27849A2400E9D2CA /* UIInterfaceOrientation+descriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIInterfaceOrientation+descriptor.swift"; sourceTree = "<group>"; }; B864004F27849A2400E9D2CA /* UIInterfaceOrientation+descriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIInterfaceOrientation+descriptor.swift"; sourceTree = "<group>"; };
B86400512784A23400E9D2CA /* CameraView+Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Orientation.swift"; sourceTree = "<group>"; }; B86400512784A23400E9D2CA /* CameraView+Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Orientation.swift"; sourceTree = "<group>"; };
B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioSession+trySetAllowHaptics.swift"; sourceTree = "<group>"; }; B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioSession+trySetAllowHaptics.swift"; sourceTree = "<group>"; };
@ -231,7 +230,6 @@
B8994E6B263F03E100069589 /* JSIUtils.mm */, B8994E6B263F03E100069589 /* JSIUtils.mm */,
B8805065266798AB00EAD7F2 /* JSConsoleHelper.h */, B8805065266798AB00EAD7F2 /* JSConsoleHelper.h */,
B8805066266798B600EAD7F2 /* JSConsoleHelper.mm */, B8805066266798B600EAD7F2 /* JSConsoleHelper.mm */,
B84C10592694A182006EFA70 /* MakeJSIRuntime.h */,
); );
path = "React Utils"; path = "React Utils";
sourceTree = "<group>"; sourceTree = "<group>";

View File

@ -82,13 +82,13 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-native": "^0.71.2", "react-native": "^0.71.2",
"react-native-builder-bob": "^0.20.3", "react-native-builder-bob": "^0.20.3",
"react-native-reanimated": "^2.14.4",
"release-it": "^15.6.0", "release-it": "^15.6.0",
"typescript": "^4.9.5" "typescript": "^4.9.5"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
"react-native": "*" "react-native": "*",
"react-native-worklets": "*"
}, },
"prettier": { "prettier": {
"bracketSpacing": true, "bracketSpacing": true,

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
if which cpplint >/dev/null; then if which cpplint >/dev/null; then
cpplint --linelength=230 --filter=-legal/copyright,-readability/todo,-build/namespaces,-whitespace/comments,-build/include_order,-build/c++11 --quiet --recursive --exclude "android/src/main/cpp/reanimated-headers" cpp android/src/main/cpp cpplint --linelength=230 --filter=-legal/copyright,-readability/todo,-build/namespaces,-whitespace/comments,-build/include_order,-build/c++11 --quiet --recursive cpp android/src/main/cpp
else else
echo "warning: cpplint not installed, download from https://github.com/cpplint/cpplint" echo "warning: cpplint not installed, download from https://github.com/cpplint/cpplint"
fi fi

View File

@ -315,6 +315,14 @@ export class Camera extends React.PureComponent<CameraProps> {
} }
//#region Static Functions (NativeModule) //#region Static Functions (NativeModule)
/**
* Install JSI Bindings for Frame Processors
*/
public static installFrameProcessorBindings(): void {
const result = CameraModule.installFrameProcessorBindings() as unknown;
if (result !== true) throw new Error('Failed to install Frame Processor JSI bindings!');
}
/** /**
* Get a list of all available camera devices on the current phone. * Get a list of all available camera devices on the current phone.
* *

View File

@ -0,0 +1,13 @@
import type { Frame } from './Frame';
import { Camera } from './Camera';
// Install VisionCamera Frame Processor JSI Bindings and Plugins
Camera.installFrameProcessorBindings();
type FrameProcessor = (frame: Frame, ...args: unknown[]) => unknown;
type TFrameProcessorPlugins = Record<string, FrameProcessor>;
/**
* All natively installed Frame Processor Plugins.
*/
export const FrameProcessorPlugins = global.FrameProcessorPlugins as TFrameProcessorPlugins;

22
src/globals.d.ts vendored
View File

@ -1,24 +1,6 @@
/* eslint-disable no-var */ /* eslint-disable no-var */
/** /**
* `true` if currently running in a Frame Processor runtime * The global Frame Processor plugins registry - will be initialized after the `installFrameProcessorBindings()` call
*/ */
declare var _FRAME_PROCESSOR: true | undefined; declare var FrameProcessorPlugins: Record<string | symbol, unknown> | undefined;
/**
* `true` if currently running in a reanimated UI runtime
*/
declare var _UI: true | undefined;
/**
* `true` if currently running in a Worklet runtime (frame processor, multithreading, reanimated)
*/
declare var _WORKLET: true | undefined;
/**
* A native logging function (outputs to Xcode console/Android Logcat)
*/
declare var _log: (message: string) => void | undefined;
/**
* Set a Proxy for global.console in this Runtime
*/
declare var _setGlobalConsole: (console: unknown) => void;

View File

@ -1,12 +1,10 @@
/* global _setGlobalConsole */
import { DependencyList, useCallback } from 'react'; import { DependencyList, useCallback } from 'react';
import type { Frame } from '../Frame'; import type { Frame } from '../Frame';
// Install RN Worklets by importing it
import 'react-native-worklets/src';
type FrameProcessor = (frame: Frame) => void; type FrameProcessor = (frame: Frame) => void;
const capturableConsole = console;
/** /**
* Returns a memoized Frame Processor function wich you can pass to the `<Camera>`. (See ["Frame Processors"](https://mrousavy.github.io/react-native-vision-camera/docs/guides/frame-processors)) * Returns a memoized Frame Processor function wich you can pass to the `<Camera>`. (See ["Frame Processors"](https://mrousavy.github.io/react-native-vision-camera/docs/guides/frame-processors))
* *
@ -25,34 +23,6 @@ const capturableConsole = console;
* ``` * ```
*/ */
export function useFrameProcessor(frameProcessor: FrameProcessor, dependencies: DependencyList): FrameProcessor { export function useFrameProcessor(frameProcessor: FrameProcessor, dependencies: DependencyList): FrameProcessor {
return useCallback((frame: Frame) => { // eslint-disable-next-line react-hooks/exhaustive-deps
'worklet'; return useCallback(frameProcessor, dependencies);
// @ts-expect-error
if (global.didSetConsole == null || global.didSetConsole === false) {
const console = {
// @ts-expect-error __callAsync is injected by native REA
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
debug: capturableConsole.debug.__callAsync,
// @ts-expect-error __callAsync is injected by native REA
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
log: capturableConsole.log.__callAsync,
// @ts-expect-error __callAsync is injected by native REA
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
warn: capturableConsole.warn.__callAsync,
// @ts-expect-error __callAsync is injected by native REA
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
error: capturableConsole.error.__callAsync,
// @ts-expect-error __callAsync is injected by native REA
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
info: capturableConsole.info.__callAsync,
};
_setGlobalConsole(console);
// @ts-expect-error
global.didSetConsole = true;
}
frameProcessor(frame);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
} }

View File

@ -5,6 +5,7 @@ export * from './CameraPosition';
export * from './CameraPreset'; export * from './CameraPreset';
export * from './CameraProps'; export * from './CameraProps';
export * from './Frame'; export * from './Frame';
export * from './FrameProcessorPlugins';
export * from './CameraProps'; export * from './CameraProps';
export * from './PhotoFile'; export * from './PhotoFile';
export * from './Point'; export * from './Point';

View File

@ -758,13 +758,6 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-object-assign@^7.16.7":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz#7830b4b6f83e1374a5afb9f6111bcfaea872cdd2"
integrity sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A==
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.18.6": "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c"
@ -1034,7 +1027,7 @@
"@babel/plugin-transform-react-jsx-development" "^7.18.6" "@babel/plugin-transform-react-jsx-development" "^7.18.6"
"@babel/plugin-transform-react-pure-annotations" "^7.18.6" "@babel/plugin-transform-react-pure-annotations" "^7.18.6"
"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.17.12": "@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.17.12":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399"
integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==
@ -1703,7 +1696,7 @@
dependencies: dependencies:
"@hapi/hoek" "^9.0.0" "@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0": "@sideway/formula@^3.0.1":
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
@ -3313,9 +3306,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.4.284: electron-to-chromium@^1.4.284:
version "1.4.292" version "1.4.294"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.292.tgz#e3a3dca3780c8ce01e2c1866b5ec2fbe31c423e3" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.294.tgz#ad80317b85f0859a9454680fbc1c726fefa7e6fd"
integrity sha512-ESWOSyJy5odDlE8wvh5NNAMORv4r6assPwIPGHEMWrWD0SONXcG/xT+9aD9CQyeRwyYDPo6dJT4Bbeg5uevVQQ== integrity sha512-PuHZB3jEN7D8WPPjLmBQAsqQz8tWHlkkB4n0E2OYw8RwVdmBYV0Wn+rUFH8JqYyIRb4HQhhedgxlZL163wqLrQ==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
@ -4078,7 +4071,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
@ -4611,11 +4604,11 @@ inquirer@9.1.4:
wrap-ansi "^8.0.1" wrap-ansi "^8.0.1"
internal-slot@^1.0.3, internal-slot@^1.0.4: internal-slot@^1.0.3, internal-slot@^1.0.4:
version "1.0.4" version "1.0.5"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ== integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
dependencies: dependencies:
get-intrinsic "^1.1.3" get-intrinsic "^1.2.0"
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.4" side-channel "^1.0.4"
@ -5189,14 +5182,14 @@ jetifier@^2.0.0:
integrity sha512-J4Au9KuT74te+PCCCHKgAjyLlEa+2VyIAEPNCdE5aNkAJ6FAJcAqcdzEkSnzNksIa9NkGmC4tPiClk2e7tCJuQ== integrity sha512-J4Au9KuT74te+PCCCHKgAjyLlEa+2VyIAEPNCdE5aNkAJ6FAJcAqcdzEkSnzNksIa9NkGmC4tPiClk2e7tCJuQ==
joi@^17.2.1: joi@^17.2.1:
version "17.7.0" version "17.7.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3" resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.1.tgz#854fc85c7fa3cfc47c91124d30bffdbb58e06cec"
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg== integrity sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==
dependencies: dependencies:
"@hapi/hoek" "^9.0.0" "@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0" "@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.3" "@sideway/address" "^4.1.3"
"@sideway/formula" "^3.0.0" "@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0" "@sideway/pinpoint" "^2.0.0"
js-sdsl@^4.1.4: js-sdsl@^4.1.4:
@ -5454,11 +5447,6 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.ismatch@^4.4.0: lodash.ismatch@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
@ -5979,9 +5967,9 @@ minimist-options@4.1.0:
kind-of "^6.0.3" kind-of "^6.0.3"
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.7" version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
mixin-deep@^1.2.0: mixin-deep@^1.2.0:
version "1.3.2" version "1.3.2"
@ -6830,9 +6818,9 @@ rc@1.2.8:
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
react-devtools-core@^4.26.1: react-devtools-core@^4.26.1:
version "4.27.1" version "4.27.2"
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.1.tgz#167aa174383c65786cbb7e965a5b39c702f0a2d3" resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.2.tgz#d20fc57e258c656eedabafc2c851d38b33583148"
integrity sha512-qXhcxxDWiFmFAOq48jts9YQYe1+wVoUXzJTlY4jbaATzyio6dd6CUGu3dXBhREeVgpZ+y4kg6vFJzIOZh6vY2w== integrity sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==
dependencies: dependencies:
shell-quote "^1.6.1" shell-quote "^1.6.1"
ws "^7" ws "^7"
@ -6894,19 +6882,6 @@ react-native-gradle-plugin@^0.71.14:
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.14.tgz#cc399662f04fbfcc0e352d03eae1d3efbd5f635a" resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.14.tgz#cc399662f04fbfcc0e352d03eae1d3efbd5f635a"
integrity sha512-nnLawTZEPPRAKq92UqDkzoGgCBl9aa9zAihFHMwmwzn4WRVdK4O6Cd4XYiyoNOiQzx3Hh9k5WOckHE80C92ivQ== integrity sha512-nnLawTZEPPRAKq92UqDkzoGgCBl9aa9zAihFHMwmwzn4WRVdK4O6Cd4XYiyoNOiQzx3Hh9k5WOckHE80C92ivQ==
react-native-reanimated@^2.14.4:
version "2.14.4"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.14.4.tgz#3fa3da4e7b99f5dfb28f86bcf24d9d1024d38836"
integrity sha512-DquSbl7P8j4SAmc+kRdd75Ianm8G+IYQ9T4AQ6lrpLVeDkhZmjWI0wkutKWnp6L7c5XNVUrFDUf69dwETLCItQ==
dependencies:
"@babel/plugin-transform-object-assign" "^7.16.7"
"@babel/preset-typescript" "^7.16.7"
convert-source-map "^1.7.0"
invariant "^2.2.4"
lodash.isequal "^4.5.0"
setimmediate "^1.0.5"
string-hash-64 "^1.0.3"
react-native@^0.71.2: react-native@^0.71.2:
version "0.71.2" version "0.71.2"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.2.tgz#b6977eda2a6dc10baa006bf4ab1ee08318607ce9" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.2.tgz#b6977eda2a6dc10baa006bf4ab1ee08318607ce9"
@ -7431,11 +7406,6 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3" is-plain-object "^2.0.3"
split-string "^3.0.1" split-string "^3.0.1"
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
setprototypeof@1.2.0: setprototypeof@1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
@ -7725,11 +7695,6 @@ stream-buffers@2.2.x:
resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4"
integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==
string-hash-64@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==
string-natural-compare@^3.0.1: string-natural-compare@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
@ -8094,9 +8059,9 @@ type-fest@^2.13.0, type-fest@^2.5.1:
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
type-fest@^3.0.0: type-fest@^3.0.0:
version "3.5.6" version "3.5.7"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.6.tgz#f8f3a630c185fb5d66ca6950c7cbc2893deb6b84" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.7.tgz#1ee9efc9a172f4002c40b896689928a7bba537f2"
integrity sha512-6bd2bflx8ed7c99tc6zSTIzHr1/QG29bQoK4Qh8MYGnlPbODUzGxklLShjwc/xWQQFHgIci+y5Arv7Rbb0LjXw== integrity sha512-6J4bYzb4sdkcLBty4XW7F18VPI66M4boXNE+CY40532oq2OJe6AVMB5NmjOp6skt/jw5mRjz/hLRpuglz0U+FA==
typed-array-length@^1.0.4: typed-array-length@^1.0.4:
version "1.0.4" version "1.0.4"