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:
parent
11d1e7178d
commit
a0590dccb5
2
.github/workflows/validate-cpp.yml
vendored
2
.github/workflows/validate-cpp.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
reporter: github-pr-review
|
||||
flags: --linelength=230 --exclude "android/src/main/cpp/reanimated-headers"
|
||||
flags: --linelength=230
|
||||
targets: --recursive cpp android/src/main/cpp
|
||||
filter: "-legal/copyright\
|
||||
,-readability/todo\
|
||||
|
@ -64,4 +64,5 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-callinvoker"
|
||||
s.dependency "React"
|
||||
s.dependency "React-Core"
|
||||
s.dependency "react-native-worklets"
|
||||
end
|
||||
|
@ -1,20 +1,23 @@
|
||||
project(VisionCamera)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
cmake_minimum_required(VERSION 3.9.0)
|
||||
|
||||
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")
|
||||
add_compile_options(${folly_FLAGS})
|
||||
|
||||
|
||||
set (PACKAGE_NAME "VisionCamera")
|
||||
set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
|
||||
# Consume shared libraries and headers from prefabs
|
||||
find_package(fbjni REQUIRED CONFIG)
|
||||
# Third party libraries (Prefabs)
|
||||
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(
|
||||
${PACKAGE_NAME}
|
||||
SHARED
|
||||
@ -29,87 +32,24 @@ add_library(
|
||||
src/main/cpp/java-bindings/JHashMap.cpp
|
||||
)
|
||||
|
||||
# includes
|
||||
# Header Search Paths (includes)
|
||||
target_include_directories(
|
||||
${PACKAGE_NAME}
|
||||
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/callinvoker"
|
||||
"${NODE_MODULES_DIR}/react-native/ReactCommon/jsi"
|
||||
"${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"
|
||||
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper
|
||||
)
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
# Link everything together
|
||||
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(
|
||||
${PACKAGE_NAME}
|
||||
ReactAndroid::folly_runtime
|
||||
ReactAndroid::glog
|
||||
ReactAndroid::jsi
|
||||
ReactAndroid::reactnativejni
|
||||
fbjni::fbjni
|
||||
)
|
||||
|
||||
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
|
||||
${LOG_LIB} # <-- Logcat logger
|
||||
android # <-- Android JNI core
|
||||
ReactAndroid::jsi # <-- RN: JSI
|
||||
ReactAndroid::reactnativejni # <-- RN: React Native JNI bindings
|
||||
ReactAndroid::folly_runtime # <-- RN: For casting JSI <> Java objects
|
||||
fbjni::fbjni # <-- fbjni
|
||||
react-native-worklets::rnworklets # <-- RN Worklets
|
||||
)
|
||||
|
@ -1,3 +1,5 @@
|
||||
import java.nio.file.Paths
|
||||
|
||||
buildscript {
|
||||
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 resolveBuildType() {
|
||||
@ -49,6 +50,23 @@ def reactNativeArchitectures() {
|
||||
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 {
|
||||
google()
|
||||
mavenCentral()
|
||||
@ -77,29 +95,26 @@ android {
|
||||
versionName "1.0"
|
||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||
|
||||
if (ENABLE_FRAME_PROCESSORS) {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
if (ENABLE_FRAME_PROCESSORS) {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
doNotStrip resolveBuildType() == 'debug' ? "**/**/*.so" : ''
|
||||
excludes = [
|
||||
@ -128,6 +143,8 @@ dependencies {
|
||||
implementation "androidx.camera:camera-extensions:1.1.0-beta02"
|
||||
|
||||
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".
|
||||
|
@ -1,6 +1,3 @@
|
||||
rootProject.name = 'VisionCamera'
|
||||
|
||||
include ':react-native-reanimated'
|
||||
project(':react-native-reanimated').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-reanimated/android/')
|
||||
|
||||
include ':VisionCamera'
|
||||
|
@ -7,17 +7,11 @@
|
||||
#include <jni.h>
|
||||
#include <utility>
|
||||
#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 "FrameHostObject.h"
|
||||
#include "JSIJNIConversion.h"
|
||||
#include "VisionCameraScheduler.h"
|
||||
#include "java-bindings/JImageProxy.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 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
|
||||
void vision::FrameProcessorRuntimeManager::registerNatives() {
|
||||
registerHybrid({
|
||||
@ -35,8 +51,6 @@ void vision::FrameProcessorRuntimeManager::registerNatives() {
|
||||
FrameProcessorRuntimeManager::initHybrid),
|
||||
makeNativeMethod("installJSIBindings",
|
||||
FrameProcessorRuntimeManager::installJSIBindings),
|
||||
makeNativeMethod("initializeRuntime",
|
||||
FrameProcessorRuntimeManager::initializeRuntime),
|
||||
makeNativeMethod("registerPlugin",
|
||||
FrameProcessorRuntimeManager::registerPlugin),
|
||||
});
|
||||
@ -52,32 +66,11 @@ TSelf vision::FrameProcessorRuntimeManager::initHybrid(
|
||||
"Initializing FrameProcessorRuntimeManager...");
|
||||
|
||||
// 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 scheduler = std::shared_ptr<VisionCameraScheduler>(androidScheduler->cthis());
|
||||
scheduler->setJSCallInvoker(jsCallInvoker);
|
||||
|
||||
return makeCxxInstance(jThis, runtime, 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!");
|
||||
return makeCxxInstance(jThis, jsRuntime, jsCallInvoker, scheduler);
|
||||
}
|
||||
|
||||
global_ref<CameraView::javaobject> FrameProcessorRuntimeManager::findCameraViewById(int viewId) {
|
||||
@ -87,16 +80,11 @@ global_ref<CameraView::javaobject> FrameProcessorRuntimeManager::findCameraViewB
|
||||
}
|
||||
|
||||
void FrameProcessorRuntimeManager::logErrorToJS(const std::string& message) {
|
||||
if (!this->jsCallInvoker_) {
|
||||
if (!_workletContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->jsCallInvoker_->invokeAsync([this, message]() {
|
||||
if (this->runtime_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& runtime = *this->runtime_;
|
||||
// Call console.error() on JS Thread
|
||||
_workletContext->invokeOnJsThread([message](jsi::Runtime& runtime) {
|
||||
auto consoleError = runtime
|
||||
.global()
|
||||
.getPropertyAsObject(runtime, "console")
|
||||
@ -111,42 +99,38 @@ void FrameProcessorRuntimeManager::setFrameProcessor(jsi::Runtime& runtime,
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG,
|
||||
"Setting new Frame Processor...");
|
||||
|
||||
if (!_runtimeManager || !_runtimeManager->runtime) {
|
||||
if (!_workletContext) {
|
||||
throw jsi::JSError(runtime,
|
||||
"setFrameProcessor(..): VisionCamera's RuntimeManager is not yet initialized!");
|
||||
"setFrameProcessor(..): VisionCamera's Worklet Context is not yet initialized!");
|
||||
}
|
||||
|
||||
// find camera view
|
||||
auto cameraView = findCameraViewById(viewTag);
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG, "Found CameraView!");
|
||||
|
||||
// convert jsi::Function to a ShareableValue (can be shared across runtimes)
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG,
|
||||
"Adapting Shareable value from function (conversion to worklet)...");
|
||||
auto worklet = reanimated::ShareableValue::adapt(runtime,
|
||||
frameProcessor,
|
||||
_runtimeManager.get());
|
||||
// convert jsi::Function to a Worklet (can be shared across runtimes)
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG, "Creating Worklet...");
|
||||
auto worklet = std::make_shared<RNWorklet::JsiWorklet>(runtime, frameProcessor);
|
||||
auto workletInvoker = std::make_shared<RNWorklet::WorkletInvoker>(worklet);
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG, "Successfully created worklet!");
|
||||
|
||||
scheduler_->scheduleOnUI([=]() {
|
||||
// cast worklet to a jsi::Function for the new runtime
|
||||
auto& rt = *_runtimeManager->runtime;
|
||||
auto function = std::make_shared<jsi::Function>(worklet->getValue(rt).asObject(rt).asFunction(rt));
|
||||
|
||||
// assign lambda to frame processor
|
||||
cameraView->cthis()->setFrameProcessor([this, &rt, function](jni::alias_ref<JImageProxy::javaobject> frame) {
|
||||
_workletContext->invokeOnWorkletThread([=](RNWorklet::JsiWorkletContext*, jsi::Runtime& rt) {
|
||||
// Set Frame Processor as callable C++ lambda - this will then call the Worklet
|
||||
cameraView->cthis()->setFrameProcessor([this, workletInvoker, &rt](jni::alias_ref<JImageProxy::javaobject> frame) {
|
||||
try {
|
||||
// create HostObject which holds the Frame (JImageProxy)
|
||||
auto hostObject = std::make_shared<FrameHostObject>(frame);
|
||||
function->callWithThis(rt, *function, jsi::Object::createFromHostObject(rt, hostObject));
|
||||
auto frameHostObject = std::make_shared<FrameHostObject>(frame);
|
||||
auto argument = jsi::Object::createFromHostObject(rt, frameHostObject);
|
||||
jsi::Value jsValue(std::move(argument));
|
||||
// Call the Worklet on the Worklet Runtime
|
||||
workletInvoker->call(rt, jsi::Value::undefined(), &jsValue, 1);
|
||||
} 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();
|
||||
__android_log_write(ANDROID_LOG_ERROR, TAG, message.c_str());
|
||||
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() {
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG, "Installing JSI bindings...");
|
||||
|
||||
if (runtime_ == nullptr) {
|
||||
if (_jsRuntime == nullptr) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, TAG,
|
||||
"JS-Runtime was null, Frame Processor JSI bindings could not be installed!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& jsiRuntime = *runtime_;
|
||||
auto& jsiRuntime = *_jsRuntime;
|
||||
|
||||
auto setFrameProcessor = [this](jsi::Runtime &runtime,
|
||||
const jsi::Value &thisValue,
|
||||
@ -234,27 +218,26 @@ void FrameProcessorRuntimeManager::installJSIBindings() {
|
||||
}
|
||||
|
||||
void FrameProcessorRuntimeManager::registerPlugin(alias_ref<JFrameProcessorPlugin::javaobject> plugin) {
|
||||
// _runtimeManager might never be null, but we can never be too sure.
|
||||
if (!_runtimeManager || !_runtimeManager->runtime) {
|
||||
throw std::runtime_error("Tried to register plugin before initializing JS runtime! Call `initializeRuntime()` first.");
|
||||
}
|
||||
|
||||
auto& runtime = *_runtimeManager->runtime;
|
||||
auto& runtime = *_jsRuntime;
|
||||
|
||||
// we need a strong reference on the plugin, make_global does that.
|
||||
auto pluginGlobal = make_global(plugin);
|
||||
// name is always prefixed with two underscores (__)
|
||||
auto name = "__" + pluginGlobal->getName();
|
||||
auto pluginName = 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* arguments,
|
||||
size_t count) -> jsi::Value {
|
||||
// Unbox object and get typed HostObject
|
||||
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.
|
||||
auto params = JArrayClass<jobject>::newArray(count - 1);
|
||||
@ -269,10 +252,14 @@ void FrameProcessorRuntimeManager::registerPlugin(alias_ref<JFrameProcessorPlugi
|
||||
return JSIJNIConversion::convertJNIObjectToJSIValue(runtime, result);
|
||||
};
|
||||
|
||||
runtime.global().setProperty(runtime, name.c_str(), jsi::Function::createFromHostFunction(runtime,
|
||||
jsi::PropNameID::forAscii(runtime, name),
|
||||
// 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.c_str(),
|
||||
jsi::Function::createFromHostFunction(runtime,
|
||||
jsi::PropNameID::forAscii(runtime, pluginName),
|
||||
1, // frame
|
||||
callback));
|
||||
function));
|
||||
}
|
||||
|
||||
} // namespace vision
|
||||
|
@ -9,9 +9,7 @@
|
||||
#include <ReactCommon/CallInvokerHolder.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "RuntimeManager.h"
|
||||
#include "reanimated-headers/AndroidScheduler.h"
|
||||
#include <JsiWorkletContext.h>
|
||||
|
||||
#include "CameraView.h"
|
||||
#include "VisionCameraScheduler.h"
|
||||
@ -32,25 +30,17 @@ class FrameProcessorRuntimeManager : public jni::HybridClass<FrameProcessorRunti
|
||||
static void registerNatives();
|
||||
|
||||
explicit FrameProcessorRuntimeManager(jni::alias_ref<FrameProcessorRuntimeManager::jhybridobject> jThis,
|
||||
jsi::Runtime* runtime,
|
||||
jsi::Runtime* jsRuntime,
|
||||
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker,
|
||||
std::shared_ptr<vision::VisionCameraScheduler> scheduler) :
|
||||
javaPart_(jni::make_global(jThis)),
|
||||
runtime_(runtime),
|
||||
jsCallInvoker_(jsCallInvoker),
|
||||
scheduler_(scheduler)
|
||||
{}
|
||||
std::shared_ptr<vision::VisionCameraScheduler> scheduler);
|
||||
|
||||
private:
|
||||
friend HybridBase;
|
||||
jni::global_ref<FrameProcessorRuntimeManager::javaobject> javaPart_;
|
||||
jsi::Runtime* runtime_;
|
||||
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker_;
|
||||
std::shared_ptr<reanimated::RuntimeManager> _runtimeManager;
|
||||
std::shared_ptr<vision::VisionCameraScheduler> scheduler_;
|
||||
jsi::Runtime* _jsRuntime;
|
||||
std::shared_ptr<RNWorklet::JsiWorkletContext> _workletContext;
|
||||
|
||||
jni::global_ref<CameraView::javaobject> findCameraViewById(int viewId);
|
||||
void initializeRuntime();
|
||||
void installJSIBindings();
|
||||
void registerPlugin(alias_ref<JFrameProcessorPlugin::javaobject> plugin);
|
||||
void logErrorToJS(const std::string& message);
|
||||
|
@ -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
|
@ -14,9 +14,9 @@ TSelf VisionCameraScheduler::initHybrid(jni::alias_ref<jhybridobject> jThis) {
|
||||
return makeCxxInstance(jThis);
|
||||
}
|
||||
|
||||
void VisionCameraScheduler::scheduleOnUI(std::function<void()> job) {
|
||||
void VisionCameraScheduler::dispatchAsync(std::function<void()> job) {
|
||||
// 1. add job to queue
|
||||
uiJobs.push(job);
|
||||
_jobs.push(job);
|
||||
scheduleTrigger();
|
||||
}
|
||||
|
||||
@ -26,16 +26,18 @@ void VisionCameraScheduler::scheduleTrigger() {
|
||||
method(javaPart_.get());
|
||||
}
|
||||
|
||||
void VisionCameraScheduler::triggerUI() {
|
||||
void VisionCameraScheduler::trigger() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
// 3. call job we enqueued in step 1.
|
||||
auto job = uiJobs.pop();
|
||||
auto job = _jobs.front();
|
||||
job();
|
||||
_jobs.pop();
|
||||
}
|
||||
|
||||
void VisionCameraScheduler::registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod("initHybrid", VisionCameraScheduler::initHybrid),
|
||||
makeNativeMethod("triggerUI", VisionCameraScheduler::triggerUI),
|
||||
makeNativeMethod("trigger", VisionCameraScheduler::trigger),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,35 +4,47 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Scheduler.h"
|
||||
#include <ReactCommon/CallInvokerHolder.h>
|
||||
#include <jni.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
|
||||
namespace vision {
|
||||
|
||||
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:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/mrousavy/camera/frameprocessor/VisionCameraScheduler;";
|
||||
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
|
||||
static void registerNatives();
|
||||
|
||||
// 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:
|
||||
friend HybridBase;
|
||||
jni::global_ref<VisionCameraScheduler::javaobject> javaPart_;
|
||||
std::queue<std::function<void()>> _jobs;
|
||||
std::mutex _mutex;
|
||||
|
||||
explicit VisionCameraScheduler(jni::alias_ref<VisionCameraScheduler::jhybridobject> 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();
|
||||
// Calls the latest job in the job queue
|
||||
void triggerUI() override;
|
||||
void trigger();
|
||||
};
|
||||
|
||||
} // namespace vision
|
@ -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() {}
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
@ -210,9 +210,7 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
|
||||
}
|
||||
|
||||
init {
|
||||
if (FrameProcessorRuntimeManager.enableFrameProcessors) {
|
||||
mHybridData = initHybrid()
|
||||
}
|
||||
|
||||
previewView = PreviewView(context)
|
||||
previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
|
@ -20,9 +20,6 @@ import com.facebook.react.modules.core.PermissionAwareActivity
|
||||
import com.facebook.react.modules.core.PermissionListener
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
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 com.mrousavy.camera.frameprocessor.FrameProcessorRuntimeManager
|
||||
import com.mrousavy.camera.parsers.*
|
||||
@ -55,17 +52,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
||||
if (coroutineScope.isActive) {
|
||||
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() {
|
||||
@ -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
|
||||
// https://issuetracker.google.com/issues/179925896
|
||||
@ReactMethod
|
||||
|
@ -48,6 +48,6 @@ public abstract class FrameProcessorPlugin {
|
||||
* @param plugin An instance of a plugin.
|
||||
*/
|
||||
public static void register(@NonNull FrameProcessorPlugin plugin) {
|
||||
FrameProcessorRuntimeManager.Companion.getPlugins().add(plugin);
|
||||
FrameProcessorRuntimeManager.Companion.addPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
@ -16,18 +16,20 @@ import java.util.concurrent.ExecutorService
|
||||
class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProcessorThread: ExecutorService) {
|
||||
companion object {
|
||||
const val TAG = "FrameProcessorRuntime"
|
||||
val Plugins: ArrayList<FrameProcessorPlugin> = ArrayList()
|
||||
var enableFrameProcessors = true
|
||||
private val Plugins: ArrayList<FrameProcessorPlugin> = ArrayList()
|
||||
|
||||
init {
|
||||
try {
|
||||
System.loadLibrary("reanimated")
|
||||
System.loadLibrary("VisionCamera")
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
Log.w(TAG, "Failed to load Reanimated/VisionCamera C++ library. Frame Processors are disabled!")
|
||||
enableFrameProcessors = false
|
||||
Log.e(TAG, "Failed to load VisionCamera C++ library!", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun addPlugin(plugin: FrameProcessorPlugin) {
|
||||
Plugins.add(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
@ -36,24 +38,11 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces
|
||||
private var mScheduler: VisionCameraScheduler? = null
|
||||
|
||||
init {
|
||||
if (enableFrameProcessors) {
|
||||
val holder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl
|
||||
val jsCallInvokerHolder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl
|
||||
val jsRuntimeHolder = context.javaScriptContextHolder.get()
|
||||
mScheduler = VisionCameraScheduler(frameProcessorThread)
|
||||
mContext = WeakReference(context)
|
||||
mHybridData = initHybrid(context.javaScriptContextHolder.get(), holder, mScheduler!!)
|
||||
initializeRuntime()
|
||||
|
||||
Log.i(TAG, "Installing Frame Processor Plugins...")
|
||||
Plugins.forEach { plugin ->
|
||||
registerPlugin(plugin)
|
||||
}
|
||||
Log.i(TAG, "Successfully installed ${Plugins.count()} Frame Processor Plugins!")
|
||||
|
||||
Log.i(TAG, "Installing JSI Bindings on JS Thread...")
|
||||
context.runOnJSQueueThread {
|
||||
installJSIBindings()
|
||||
}
|
||||
}
|
||||
mHybridData = initHybrid(jsRuntimeHolder, jsCallInvokerHolder, mScheduler!!)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@ -67,13 +56,22 @@ class FrameProcessorRuntimeManager(context: ReactApplicationContext, frameProces
|
||||
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 external fun initHybrid(
|
||||
jsContext: Long,
|
||||
jsCallInvokerHolder: CallInvokerHolderImpl,
|
||||
scheduler: VisionCameraScheduler
|
||||
): HybridData
|
||||
private external fun initializeRuntime()
|
||||
private external fun registerPlugin(plugin: FrameProcessorPlugin)
|
||||
private external fun installJSIBindings()
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public class ImageProxyUtils {
|
||||
Image image = imageProxy.getImage();
|
||||
if (image == null) return false;
|
||||
// will throw an exception if the image is already closed
|
||||
imageProxy.getImage().getCropRect();
|
||||
image.getCropRect();
|
||||
// no exception thrown, image must still be valid.
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
@ -17,11 +17,11 @@ public class VisionCameraScheduler {
|
||||
}
|
||||
|
||||
private native HybridData initHybrid();
|
||||
private native void triggerUI();
|
||||
private native void trigger();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@DoNotStrip
|
||||
private void scheduleTrigger() {
|
||||
frameProcessorThread.submit(this::triggerUI);
|
||||
frameProcessorThread.submit(this::trigger);
|
||||
}
|
||||
}
|
||||
|
@ -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 **snapchat-like filters**, e.g. draw a dog-mask filter over the user's face
|
||||
* 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.
|
||||
|
||||
:::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
|
||||
|
||||
```js
|
||||
import 'react-native-reanimated'
|
||||
```
|
||||
|
||||
to the top of the file when using `useFrameProcessor`.
|
||||
Frame Processors require [**react-native-worklets**](https://github.com/chrfalch/react-native-worklets) 1.0.0 or higher.
|
||||
:::
|
||||
|
||||
### 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`.
|
||||
|
||||
```tsx {6}
|
||||
```tsx {7}
|
||||
// represents position of the cat on the screen 🐈
|
||||
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}
|
||||
const onQRCodeDetected = useCallback((qrCode: string) => {
|
||||
```tsx {1}
|
||||
const onQRCodeDetected = Worklets.createRunInJsFn((qrCode: string) => {
|
||||
navigation.push("ProductPage", { productId: qrCode })
|
||||
}, [])
|
||||
})
|
||||
|
||||
const frameProcessor = useFrameProcessor((frame) => {
|
||||
'worklet'
|
||||
const qrCodes = scanQRCodes(frame)
|
||||
if (qrCodes.length > 0) {
|
||||
runOnJS(onQRCodeDetected)(qrCodes[0])
|
||||
onQRCodeDetected(qrCodes[0])
|
||||
}
|
||||
}, [onQRCodeDetected])
|
||||
```
|
||||
@ -133,23 +129,6 @@ npm i vision-camera-image-labeler
|
||||
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:
|
||||
|
||||
```ts
|
||||
@ -192,7 +171,7 @@ If you are using the [react-hooks ESLint plugin](https://www.npmjs.com/package/e
|
||||
|
||||
#### 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)
|
||||
|
||||
|
@ -61,7 +61,7 @@ Returns a `string` in JS:
|
||||
```js
|
||||
export function detectObject(frame: Frame): string {
|
||||
'worklet'
|
||||
const result = __detectObject(frame)
|
||||
const result = FrameProcessorPlugins.detectObject(frame)
|
||||
console.log(result) // <-- "cat"
|
||||
}
|
||||
```
|
||||
|
@ -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:
|
||||
|
||||
```ts
|
||||
import type { Frame } from 'react-native-vision-camera'
|
||||
import { FrameProcessorPlugins, Frame } from 'react-native-vision-camera'
|
||||
|
||||
/**
|
||||
* Scans QR codes.
|
||||
*/
|
||||
export function scanQRCodes(frame: Frame): string[] {
|
||||
'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!
|
||||
|
||||
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)
|
||||
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
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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
|
||||
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. [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 />
|
||||
|
||||
|
@ -73,7 +73,7 @@ public class QRCodeFrameProcessorPlugin extends FrameProcessorPlugin {
|
||||
```
|
||||
|
||||
:::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.
|
||||
@ -137,7 +137,7 @@ class ExampleFrameProcessorPluginKotlin: FrameProcessorPlugin("scanQRCodes") {
|
||||
```
|
||||
|
||||
:::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.
|
||||
|
@ -63,7 +63,7 @@ VISION_EXPORT_FRAME_PROCESSOR(scanQRCodes)
|
||||
```
|
||||
|
||||
:::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.
|
||||
|
@ -12,13 +12,10 @@ These are VisionCamera Frame Processor Plugins created by the community.
|
||||
|
||||
```
|
||||
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)
|
||||
|
||||
:::note
|
||||
You have to restart metro-bundler for changes in the `babel.config.js` file to take effect.
|
||||
:::
|
||||
2. Rebuild your app and use the plugin
|
||||
|
||||
## Plugin List
|
||||
|
||||
|
@ -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.
|
||||
|
||||
> **(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
|
||||
|
||||
|
@ -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
|
||||
* [ ] 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.
|
||||
* [ ] 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`)
|
||||
|
@ -12,8 +12,6 @@ import com.facebook.react.defaults.DefaultReactNativeHost;
|
||||
|
||||
import com.mrousavy.camera.CameraPackage;
|
||||
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;
|
||||
import com.facebook.react.bridge.JSIModulePackage;
|
||||
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
@ -47,11 +45,6 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
protected Boolean isHermesEnabled() {
|
||||
return BuildConfig.IS_HERMES_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JSIModulePackage getJSIModulePackage() {
|
||||
return new ReanimatedJSIModulePackage();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
|
@ -13,7 +13,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath('com.android.tools.build:gradle:7.4.1')
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,8 @@ const pak = require('../package.json');
|
||||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: [
|
||||
[
|
||||
'react-native-reanimated/plugin',
|
||||
{
|
||||
globals: ['__example_plugin', '__example_plugin_swift'],
|
||||
},
|
||||
],
|
||||
['react-native-reanimated/plugin'],
|
||||
['react-native-worklets/plugin'],
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
|
@ -283,6 +283,10 @@ PODS:
|
||||
- react-native-video/Video (= 5.2.1)
|
||||
- react-native-video/Video (5.2.1):
|
||||
- React-Core
|
||||
- react-native-worklets (0.1.0):
|
||||
- React
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-perflogger (0.71.2)
|
||||
- React-RCTActionSheet (0.71.2):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.71.2)
|
||||
@ -369,7 +373,7 @@ PODS:
|
||||
- React-perflogger (= 0.71.2)
|
||||
- RNGestureHandler (2.9.0):
|
||||
- React-Core
|
||||
- RNReanimated (2.14.4):
|
||||
- RNReanimated (3.0.0-rc.10):
|
||||
- DoubleConversion
|
||||
- FBLazyVector
|
||||
- FBReactNativeSpec
|
||||
@ -407,6 +411,7 @@ PODS:
|
||||
- React
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- react-native-worklets
|
||||
- Yoga (1.14.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
@ -437,6 +442,7 @@ DEPENDENCIES:
|
||||
- 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-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-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
||||
@ -514,6 +520,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/@react-native-community/slider"
|
||||
react-native-video:
|
||||
:path: "../node_modules/react-native-video"
|
||||
react-native-worklets:
|
||||
:path: "../node_modules/react-native-worklets"
|
||||
React-perflogger:
|
||||
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
|
||||
React-RCTActionSheet:
|
||||
@ -583,6 +591,7 @@ SPEC CHECKSUMS:
|
||||
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
|
||||
react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d
|
||||
react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253
|
||||
react-native-worklets: c7576ad4ad0f030ff41e8d74ad0077c96054a6c1
|
||||
React-perflogger: c7ccda3d1d1da837f7ff4e54e816022a6803ee87
|
||||
React-RCTActionSheet: 01c125aebbad462a24228f68c584c7a921d6c28e
|
||||
React-RCTAnimation: 5277a9440acffc4a5b7baa6ae3880fe467277ae6
|
||||
@ -597,11 +606,11 @@ SPEC CHECKSUMS:
|
||||
React-runtimeexecutor: 4bf9a9086d27f74065fce1dddac274aa95216952
|
||||
ReactCommon: f697c0ac52e999aa818e43e2b6f277787c735e2d
|
||||
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
|
||||
RNReanimated: cc5e3aa479cb9170bcccf8204291a6950a3be128
|
||||
RNReanimated: fbc356493970e3acddc15586b1bccb5eab3ff1ec
|
||||
RNScreens: ea4cd3a853063cda19a4e3c28d2e52180c80f4eb
|
||||
RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8
|
||||
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
|
||||
VisionCamera: b9345e7da5eff343cc1603dd19153e2b9acd0c07
|
||||
VisionCamera: 312151eb95370d1d764720de3b7dad33d8c7fb40
|
||||
Yoga: 5b0304b3dbef2b52e078052138e23a19c7dacaef
|
||||
|
||||
PODFILE CHECKSUM: d53724fe402c2547f1dd1cc571bbe77d9820e636
|
||||
|
@ -14,8 +14,8 @@
|
||||
"typescript": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/blur": "^4.3.0",
|
||||
"@react-native-camera-roll/camera-roll": "^5.2.3",
|
||||
"@react-native-community/blur": "^4.3.0",
|
||||
"@react-native-community/slider": "^4.4.2",
|
||||
"@react-navigation/native": "^6.1.3",
|
||||
"@react-navigation/native-stack": "^6.9.9",
|
||||
@ -23,12 +23,13 @@
|
||||
"react-native": "^0.71.2",
|
||||
"react-native-gesture-handler": "^2.9.0",
|
||||
"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-screens": "^3.19.0",
|
||||
"react-native-static-safe-area-insets": "^2.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": {
|
||||
"@babel/core": "^7.20.12",
|
||||
|
@ -6,6 +6,7 @@ import { MediaPage } from './MediaPage';
|
||||
import { CameraPage } from './CameraPage';
|
||||
import type { Routes } from './Routes';
|
||||
import { Camera, CameraPermissionStatus } from 'react-native-vision-camera';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
|
||||
const Stack = createNativeStackNavigator<Routes>();
|
||||
|
||||
@ -28,6 +29,7 @@ export function App(): React.ReactElement | null {
|
||||
const showPermissionsPage = cameraPermission !== 'authorized' || microphonePermission === 'not-determined';
|
||||
return (
|
||||
<NavigationContainer>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
@ -46,6 +48,7 @@ export function App(): React.ReactElement | null {
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</GestureHandlerRootView>
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
/* global __example_plugin __example_plugin_swift */
|
||||
import type { Frame } from 'react-native-vision-camera';
|
||||
|
||||
declare let _WORKLET: true | undefined;
|
||||
import { FrameProcessorPlugins, Frame } from 'react-native-vision-camera';
|
||||
|
||||
export function examplePluginSwift(frame: Frame): string[] {
|
||||
'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
|
||||
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[] {
|
||||
'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
|
||||
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]);
|
||||
}
|
||||
|
@ -1190,7 +1190,7 @@
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@sideway/formula@^3.0.0":
|
||||
"@sideway/formula@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
|
||||
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
|
||||
@ -2259,9 +2259,9 @@ ee-first@1.1.1:
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
electron-to-chromium@^1.4.284:
|
||||
version "1.4.292"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.292.tgz#e3a3dca3780c8ce01e2c1866b5ec2fbe31c423e3"
|
||||
integrity sha512-ESWOSyJy5odDlE8wvh5NNAMORv4r6assPwIPGHEMWrWD0SONXcG/xT+9aD9CQyeRwyYDPo6dJT4Bbeg5uevVQQ==
|
||||
version "1.4.294"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.294.tgz#ad80317b85f0859a9454680fbc1c726fefa7e6fd"
|
||||
integrity sha512-PuHZB3jEN7D8WPPjLmBQAsqQz8tWHlkkB4n0E2OYw8RwVdmBYV0Wn+rUFH8JqYyIRb4HQhhedgxlZL163wqLrQ==
|
||||
|
||||
eme-encryption-scheme-polyfill@^2.0.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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
|
||||
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==
|
||||
|
||||
internal-slot@^1.0.3, internal-slot@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
|
||||
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
|
||||
integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
get-intrinsic "^1.2.0"
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
@ -3550,14 +3550,14 @@ jest-worker@^27.2.0:
|
||||
supports-color "^8.0.0"
|
||||
|
||||
joi@^17.2.1:
|
||||
version "17.7.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3"
|
||||
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==
|
||||
version "17.7.1"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.1.tgz#854fc85c7fa3cfc47c91124d30bffdbb58e06cec"
|
||||
integrity sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
"@sideway/address" "^4.1.3"
|
||||
"@sideway/formula" "^3.0.0"
|
||||
"@sideway/formula" "^3.0.1"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
js-sdsl@^4.1.4:
|
||||
@ -4444,9 +4444,9 @@ minimatch@^5.0.1:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.6:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.2"
|
||||
@ -4984,9 +4984,9 @@ range-parser@~1.2.1:
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
react-devtools-core@^4.26.1:
|
||||
version "4.27.1"
|
||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.1.tgz#167aa174383c65786cbb7e965a5b39c702f0a2d3"
|
||||
integrity sha512-qXhcxxDWiFmFAOq48jts9YQYe1+wVoUXzJTlY4jbaATzyio6dd6CUGu3dXBhREeVgpZ+y4kg6vFJzIOZh6vY2w==
|
||||
version "4.27.2"
|
||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.2.tgz#d20fc57e258c656eedabafc2c851d38b33583148"
|
||||
integrity sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==
|
||||
dependencies:
|
||||
shell-quote "^1.6.1"
|
||||
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"
|
||||
integrity sha512-Py9YH9TlS3Lv1so5JCj6bgiqkeYYGupF4ZImlpoyhhId/t/RiSqR68LlASOHgdctqQuqVJObQiFfzX8oZI9+6w==
|
||||
|
||||
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==
|
||||
react-native-reanimated@^3.0.0-rc.10:
|
||||
version "3.0.0-rc.10"
|
||||
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.0.0-rc.10.tgz#40e5b628759aa81f94317fd0301231292b4eacf5"
|
||||
integrity sha512-0P2jSO+dXHRxSzqSxNp08VaUy89nqeUIvqBS0wlI8lsli8CJcqulL3pjNqTGzBkxXjt13mGdIzJv4u9lSjHPzg==
|
||||
dependencies:
|
||||
"@babel/plugin-transform-object-assign" "^7.16.7"
|
||||
"@babel/preset-typescript" "^7.16.7"
|
||||
@ -5091,6 +5091,10 @@ react-native-video@^5.2.1:
|
||||
prop-types "^15.7.2"
|
||||
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:
|
||||
version "0.71.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.2.tgz#b6977eda2a6dc10baa006bf4ab1ee08318607ce9"
|
||||
|
@ -19,12 +19,6 @@
|
||||
#import "RCTBridge+runOnJS.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
|
||||
|
||||
@end
|
||||
|
@ -29,10 +29,10 @@ extension CameraView {
|
||||
|
||||
internal func updateOrientation() {
|
||||
// Updates the Orientation for all rotable
|
||||
let isMirrored = self.videoDeviceInput?.device.position == .front
|
||||
let isMirrored = videoDeviceInput?.device.position == .front
|
||||
|
||||
let connectionOrientation = self.outputOrientation
|
||||
self.captureSession.outputs.forEach { output in
|
||||
let connectionOrientation = outputOrientation
|
||||
captureSession.outputs.forEach { output in
|
||||
output.connections.forEach { connection in
|
||||
if connection.isVideoMirroringSupported {
|
||||
connection.automaticallyAdjustsVideoMirroring = false
|
||||
|
@ -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(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);
|
||||
|
||||
@end
|
||||
|
@ -15,20 +15,6 @@ final class CameraViewManager: RCTViewManager {
|
||||
|
||||
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! {
|
||||
return DispatchQueue.main
|
||||
}
|
||||
@ -43,6 +29,14 @@ final class CameraViewManager: RCTViewManager {
|
||||
|
||||
// pragma MARK: React Functions
|
||||
|
||||
@objc
|
||||
final func installFrameProcessorBindings() -> NSNumber {
|
||||
// Runs on JS Thread
|
||||
runtimeManager = FrameProcessorRuntimeManager()
|
||||
runtimeManager!.installFrameProcessorBindings()
|
||||
return NSNumber(booleanLiteral: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
final func startRecording(_ node: NSNumber, options: NSDictionary, onRecordCallback: @escaping RCTResponseSenderBlock) {
|
||||
let component = getCameraView(withTag: node)
|
||||
|
@ -28,14 +28,13 @@
|
||||
* * 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
|
||||
*
|
||||
* The JS function will have the same name as the given Objective-C function, but with a "__" prefix.
|
||||
* Make sure to add that function to the babel.config.js under reanimated's "globals" option, and add TypeScript type declarations.
|
||||
* 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.
|
||||
*/
|
||||
#define VISION_EXPORT_FRAME_PROCESSOR(frame_processor) \
|
||||
\
|
||||
+(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); \
|
||||
}]; \
|
||||
}
|
||||
@ -55,7 +54,7 @@ objc_name : NSObject<FrameProcessorPluginBase>
|
||||
\
|
||||
+(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]; \
|
||||
}]; \
|
||||
}
|
||||
|
@ -13,13 +13,7 @@
|
||||
|
||||
@interface FrameProcessorRuntimeManager : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
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;
|
||||
- (instancetype) init;
|
||||
|
||||
- (void) installFrameProcessorBindings;
|
||||
|
||||
|
@ -19,25 +19,12 @@
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <ReactCommon/RCTTurboModuleManager.h>
|
||||
|
||||
#ifndef VISION_CAMERA_DISABLE_FRAME_PROCESSORS
|
||||
#if __has_include(<RNReanimated/NativeReanimatedModule.h>)
|
||||
#if __has_include(<RNReanimated/RuntimeManager.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 "JsiWorkletContext.h"
|
||||
#import "JsiWorkletApi.h"
|
||||
#import "JsiWorklet.h"
|
||||
|
||||
#import "FrameProcessorUtils.h"
|
||||
#import "FrameProcessorCallback.h"
|
||||
#import "../React Utils/MakeJSIRuntime.h"
|
||||
#import "../React Utils/JSIUtils.h"
|
||||
|
||||
// Forward declarations for the Swift classes
|
||||
@ -51,84 +38,101 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
|
||||
@end
|
||||
|
||||
@implementation FrameProcessorRuntimeManager {
|
||||
#ifdef ENABLE_FRAME_PROCESSORS
|
||||
std::unique_ptr<reanimated::RuntimeManager> runtimeManager;
|
||||
#endif
|
||||
__weak RCTBridge* weakBridge;
|
||||
std::shared_ptr<RNWorklet::JsiWorkletContext> workletContext;
|
||||
}
|
||||
|
||||
- (instancetype) initWithBridge:(RCTBridge*)bridge {
|
||||
self = [super init];
|
||||
if (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));
|
||||
}
|
||||
|
||||
NSLog(@"FrameProcessorBindings: Frame Processor plugins installed!");
|
||||
#else
|
||||
NSLog(@"Reanimated not found, Frame Processors are disabled.");
|
||||
#endif
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
// Initialize self
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) installFrameProcessorBindings {
|
||||
#ifdef ENABLE_FRAME_PROCESSORS
|
||||
if (!weakBridge) {
|
||||
NSLog(@"FrameProcessorBindings: Failed to install Frame Processor Bindings - bridge was null!");
|
||||
return;
|
||||
- (void) setupWorkletContext:(jsi::Runtime&)runtime {
|
||||
NSLog(@"FrameProcessorBindings: Creating Worklet Context...");
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// global.FrameProcessorPlugins Proxy
|
||||
runtime.global().setProperty(runtime, "FrameProcessorPlugins", frameProcessorPlugins);
|
||||
|
||||
NSLog(@"FrameProcessorBindings: Frame Processor plugins installed!");
|
||||
}
|
||||
|
||||
- (void) installFrameProcessorBindings {
|
||||
NSLog(@"FrameProcessorBindings: Installing Frame Processor Bindings for Bridge...");
|
||||
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)weakBridge;
|
||||
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)[RCTBridge currentBridge];
|
||||
if (!cxxBridge.runtime) {
|
||||
return;
|
||||
}
|
||||
|
||||
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...");
|
||||
|
||||
// setFrameProcessor(viewTag: number, frameProcessor: (frame: Frame) => void)
|
||||
@ -139,28 +143,22 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
|
||||
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[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();
|
||||
NSLog(@"FrameProcessorBindings: Adapting Shareable value from function (conversion to worklet)...");
|
||||
auto worklet = reanimated::ShareableValue::adapt(runtime, arguments[1], runtimeManager.get());
|
||||
NSLog(@"FrameProcessorBindings: Successfully created worklet!");
|
||||
NSLog(@"FrameProcessorBindings: Converting JSI Function to Worklet...");
|
||||
auto worklet = std::make_shared<RNWorklet::JsiWorklet>(runtime, arguments[1]);
|
||||
|
||||
RCTExecuteOnMainQueue([=]() {
|
||||
auto currentBridge = [RCTBridge currentBridge];
|
||||
auto anonymousView = [currentBridge.uiManager viewForReactTag:[NSNumber numberWithDouble:viewTag]];
|
||||
auto view = static_cast<CameraView*>(anonymousView);
|
||||
|
||||
dispatch_async(CameraQueues.frameProcessorQueue, [=]() {
|
||||
NSLog(@"FrameProcessorBindings: Converting worklet to Objective-C callback...");
|
||||
|
||||
auto& rt = *runtimeManager->runtime;
|
||||
auto function = worklet->getValue(rt).asObject(rt).asFunction(rt);
|
||||
view.frameProcessorCallback = convertWorkletToFrameProcessorCallback(workletContext->getWorkletRuntime(), worklet);
|
||||
|
||||
view.frameProcessorCallback = convertJSIFunctionToFrameProcessorCallback(rt, function);
|
||||
NSLog(@"FrameProcessorBindings: Frame processor set!");
|
||||
});
|
||||
});
|
||||
|
||||
return jsi::Value::undefined();
|
||||
};
|
||||
@ -198,7 +196,6 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
|
||||
unsetFrameProcessor));
|
||||
|
||||
NSLog(@"FrameProcessorBindings: Finished installing bindings.");
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -17,7 +17,9 @@
|
||||
#endif
|
||||
|
||||
#import <jsi/jsi.h>
|
||||
#import "JsiWorklet.h"
|
||||
#import <memory>
|
||||
|
||||
using namespace facebook;
|
||||
|
||||
FrameProcessorCallback convertJSIFunctionToFrameProcessorCallback(jsi::Runtime &runtime, const jsi::Function &value);
|
||||
FrameProcessorCallback convertWorkletToFrameProcessorCallback(jsi::Runtime& runtime, std::shared_ptr<RNWorklet::JsiWorklet> worklet);
|
||||
|
@ -19,14 +19,21 @@
|
||||
#import "JSConsoleHelper.h"
|
||||
#import <ReactCommon/RCTTurboModule.h>
|
||||
|
||||
FrameProcessorCallback convertJSIFunctionToFrameProcessorCallback(jsi::Runtime& runtime, const jsi::Function& value) {
|
||||
__block auto cb = value.getFunction(runtime);
|
||||
#import "JsiWorklet.h"
|
||||
|
||||
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) {
|
||||
|
||||
auto frameHostObject = std::make_shared<FrameHostObject>(frame);
|
||||
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) {
|
||||
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()];
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -94,7 +94,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -231,7 +230,6 @@
|
||||
B8994E6B263F03E100069589 /* JSIUtils.mm */,
|
||||
B8805065266798AB00EAD7F2 /* JSConsoleHelper.h */,
|
||||
B8805066266798B600EAD7F2 /* JSConsoleHelper.mm */,
|
||||
B84C10592694A182006EFA70 /* MakeJSIRuntime.h */,
|
||||
);
|
||||
path = "React Utils";
|
||||
sourceTree = "<group>";
|
||||
|
@ -82,13 +82,13 @@
|
||||
"react": "^18.2.0",
|
||||
"react-native": "^0.71.2",
|
||||
"react-native-builder-bob": "^0.20.3",
|
||||
"react-native-reanimated": "^2.14.4",
|
||||
"release-it": "^15.6.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
"react-native": "*",
|
||||
"react-native-worklets": "*"
|
||||
},
|
||||
"prettier": {
|
||||
"bracketSpacing": true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
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
|
||||
echo "warning: cpplint not installed, download from https://github.com/cpplint/cpplint"
|
||||
fi
|
||||
|
@ -315,6 +315,14 @@ export class Camera extends React.PureComponent<CameraProps> {
|
||||
}
|
||||
|
||||
//#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.
|
||||
*
|
||||
|
13
src/FrameProcessorPlugins.ts
Normal file
13
src/FrameProcessorPlugins.ts
Normal 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
22
src/globals.d.ts
vendored
@ -1,24 +1,6 @@
|
||||
/* 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;
|
||||
/**
|
||||
* `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;
|
||||
declare var FrameProcessorPlugins: Record<string | symbol, unknown> | undefined;
|
||||
|
@ -1,12 +1,10 @@
|
||||
/* global _setGlobalConsole */
|
||||
|
||||
import { DependencyList, useCallback } from 'react';
|
||||
import type { Frame } from '../Frame';
|
||||
// Install RN Worklets by importing it
|
||||
import 'react-native-worklets/src';
|
||||
|
||||
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))
|
||||
*
|
||||
@ -25,34 +23,6 @@ const capturableConsole = console;
|
||||
* ```
|
||||
*/
|
||||
export function useFrameProcessor(frameProcessor: FrameProcessor, dependencies: DependencyList): FrameProcessor {
|
||||
return useCallback((frame: Frame) => {
|
||||
'worklet';
|
||||
|
||||
// @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);
|
||||
return useCallback(frameProcessor, dependencies);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ export * from './CameraPosition';
|
||||
export * from './CameraPreset';
|
||||
export * from './CameraProps';
|
||||
export * from './Frame';
|
||||
export * from './FrameProcessorPlugins';
|
||||
export * from './CameraProps';
|
||||
export * from './PhotoFile';
|
||||
export * from './Point';
|
||||
|
81
yarn.lock
81
yarn.lock
@ -758,13 +758,6 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "7.18.6"
|
||||
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-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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399"
|
||||
integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==
|
||||
@ -1703,7 +1696,7 @@
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@sideway/formula@^3.0.0":
|
||||
"@sideway/formula@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
|
||||
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
|
||||
@ -3313,9 +3306,9 @@ ee-first@1.1.1:
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
electron-to-chromium@^1.4.284:
|
||||
version "1.4.292"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.292.tgz#e3a3dca3780c8ce01e2c1866b5ec2fbe31c423e3"
|
||||
integrity sha512-ESWOSyJy5odDlE8wvh5NNAMORv4r6assPwIPGHEMWrWD0SONXcG/xT+9aD9CQyeRwyYDPo6dJT4Bbeg5uevVQQ==
|
||||
version "1.4.294"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.294.tgz#ad80317b85f0859a9454680fbc1c726fefa7e6fd"
|
||||
integrity sha512-PuHZB3jEN7D8WPPjLmBQAsqQz8tWHlkkB4n0E2OYw8RwVdmBYV0Wn+rUFH8JqYyIRb4HQhhedgxlZL163wqLrQ==
|
||||
|
||||
emoji-regex@^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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
|
||||
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
|
||||
@ -4611,11 +4604,11 @@ inquirer@9.1.4:
|
||||
wrap-ansi "^8.0.1"
|
||||
|
||||
internal-slot@^1.0.3, internal-slot@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
|
||||
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
|
||||
integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
get-intrinsic "^1.2.0"
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
@ -5189,14 +5182,14 @@ jetifier@^2.0.0:
|
||||
integrity sha512-J4Au9KuT74te+PCCCHKgAjyLlEa+2VyIAEPNCdE5aNkAJ6FAJcAqcdzEkSnzNksIa9NkGmC4tPiClk2e7tCJuQ==
|
||||
|
||||
joi@^17.2.1:
|
||||
version "17.7.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3"
|
||||
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==
|
||||
version "17.7.1"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.1.tgz#854fc85c7fa3cfc47c91124d30bffdbb58e06cec"
|
||||
integrity sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
"@sideway/address" "^4.1.3"
|
||||
"@sideway/formula" "^3.0.0"
|
||||
"@sideway/formula" "^3.0.1"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
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"
|
||||
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:
|
||||
version "4.4.0"
|
||||
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"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.2"
|
||||
@ -6830,9 +6818,9 @@ rc@1.2.8:
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-devtools-core@^4.26.1:
|
||||
version "4.27.1"
|
||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.1.tgz#167aa174383c65786cbb7e965a5b39c702f0a2d3"
|
||||
integrity sha512-qXhcxxDWiFmFAOq48jts9YQYe1+wVoUXzJTlY4jbaATzyio6dd6CUGu3dXBhREeVgpZ+y4kg6vFJzIOZh6vY2w==
|
||||
version "4.27.2"
|
||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.2.tgz#d20fc57e258c656eedabafc2c851d38b33583148"
|
||||
integrity sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==
|
||||
dependencies:
|
||||
shell-quote "^1.6.1"
|
||||
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"
|
||||
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:
|
||||
version "0.71.2"
|
||||
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"
|
||||
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:
|
||||
version "1.2.0"
|
||||
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"
|
||||
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:
|
||||
version "3.0.1"
|
||||
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==
|
||||
|
||||
type-fest@^3.0.0:
|
||||
version "3.5.6"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.6.tgz#f8f3a630c185fb5d66ca6950c7cbc2893deb6b84"
|
||||
integrity sha512-6bd2bflx8ed7c99tc6zSTIzHr1/QG29bQoK4Qh8MYGnlPbODUzGxklLShjwc/xWQQFHgIci+y5Arv7Rbb0LjXw==
|
||||
version "3.5.7"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.7.tgz#1ee9efc9a172f4002c40b896689928a7bba537f2"
|
||||
integrity sha512-6J4bYzb4sdkcLBty4XW7F18VPI66M4boXNE+CY40532oq2OJe6AVMB5NmjOp6skt/jw5mRjz/hLRpuglz0U+FA==
|
||||
|
||||
typed-array-length@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
Loading…
Reference in New Issue
Block a user