react-native-image-stitcher 0.16.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/RNImageStitcher.podspec +26 -1
- package/android/build.gradle +20 -0
- package/android/src/main/cpp/CMakeLists.txt +46 -3
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +436 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +6 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +711 -6
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +156 -0
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +103 -0
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +338 -0
- package/cpp/{stitcher_frame_data.hpp → camera_frame_data.hpp} +96 -13
- package/cpp/camera_frame_jsi.cpp +357 -0
- package/cpp/camera_frame_jsi.hpp +108 -0
- package/cpp/stitcher_proxy_jsi.cpp +140 -0
- package/cpp/stitcher_proxy_jsi.hpp +62 -0
- package/cpp/stitcher_worklet_dispatch.cpp +103 -0
- package/cpp/stitcher_worklet_dispatch.hpp +71 -0
- package/cpp/stitcher_worklet_registry.cpp +91 -0
- package/cpp/stitcher_worklet_registry.hpp +146 -0
- package/dist/camera/ARCameraView.d.ts +77 -0
- package/dist/camera/ARCameraView.js +90 -1
- package/dist/camera/Camera.d.ts +63 -4
- package/dist/camera/Camera.js +2 -2
- package/dist/camera/CaptureMemoryPill.d.ts +4 -3
- package/dist/camera/CaptureMemoryPill.js +4 -3
- package/dist/index.d.ts +2 -1
- package/dist/stitching/ARFrameMeta.d.ts +100 -0
- package/dist/stitching/{StitcherFrame.js → ARFrameMeta.js} +1 -1
- package/dist/stitching/{StitcherFrame.d.ts → CameraFrame.d.ts} +70 -11
- package/dist/stitching/CameraFrame.js +4 -0
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +8 -0
- package/dist/stitching/ensureStitcherProxyInstalled.js +81 -0
- package/dist/stitching/useStitcherWorklet.d.ts +4 -4
- package/dist/stitching/useStitcherWorklet.js +4 -4
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +23 -1
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +137 -2
- package/ios/Sources/RNImageStitcher/CameraFrameHostObject.h +83 -0
- package/ios/Sources/RNImageStitcher/CameraFrameHostObject.mm +760 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +336 -40
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +128 -0
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +313 -0
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +42 -0
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +160 -0
- package/package.json +1 -1
- package/src/camera/ARCameraView.tsx +211 -2
- package/src/camera/Camera.tsx +81 -4
- package/src/camera/CaptureMemoryPill.tsx +4 -3
- package/src/index.ts +7 -3
- package/src/stitching/ARFrameMeta.ts +107 -0
- package/src/stitching/{StitcherFrame.ts → CameraFrame.ts} +79 -11
- package/src/stitching/ensureStitcherProxyInstalled.ts +141 -0
- package/src/stitching/useStitcherWorklet.ts +9 -9
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
import { NativeModules } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* v0.8.0 Phase 4b — one-shot installer that asks the native side
|
|
7
|
+
* to install `globalThis.__stitcherProxy` on the main JS runtime.
|
|
8
|
+
*
|
|
9
|
+
* ## When this runs
|
|
10
|
+
*
|
|
11
|
+
* The first call to `useFrameProcessor` triggers this. Idempotent:
|
|
12
|
+
* once the global is installed, subsequent calls short-circuit.
|
|
13
|
+
*
|
|
14
|
+
* ## What it does
|
|
15
|
+
*
|
|
16
|
+
* Calls into the platform-native `StitcherJsiInstaller` RN module
|
|
17
|
+
* which is registered with a `RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)`
|
|
18
|
+
* on iOS (see `ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm`)
|
|
19
|
+
* and — Phase 4b.ii — an analogous Kotlin TurboModule on Android.
|
|
20
|
+
*
|
|
21
|
+
* The native module reaches into the main JS runtime via
|
|
22
|
+
* `RCTCxxBridge.runtime` (iOS) / the equivalent Android JSI access
|
|
23
|
+
* pattern and installs a host object on `globalThis.__stitcherProxy`
|
|
24
|
+
* exposing `install(workletFn)` / `uninstall(id)` / `count()`.
|
|
25
|
+
*
|
|
26
|
+
* ## Failure modes (and what happens then)
|
|
27
|
+
*
|
|
28
|
+
* 1. **Module not registered** (Android in Phase 4b.i; old iOS
|
|
29
|
+
* builds without the new pod files). `NativeModules
|
|
30
|
+
* .StitcherJsiInstaller` is `undefined`. This function returns
|
|
31
|
+
* `false` and the hook falls back to the JS-side
|
|
32
|
+
* `StitcherWorkletRegistry` — host worklets are registered
|
|
33
|
+
* on the JS side but never fan out to AR mode. No crash, no
|
|
34
|
+
* regression vs. Phase 4a.
|
|
35
|
+
*
|
|
36
|
+
* 2. **JSI runtime unreachable** (e.g., remote debug mode). The
|
|
37
|
+
* sync method returns `false`. Same JS-side-registry fallback.
|
|
38
|
+
*
|
|
39
|
+
* 3. **Native install succeeds but global not yet visible.**
|
|
40
|
+
* The native call is SYNCHRONOUS (`BLOCKING_SYNCHRONOUS_METHOD`),
|
|
41
|
+
* so by the time the function returns the global is installed.
|
|
42
|
+
* No race here.
|
|
43
|
+
*
|
|
44
|
+
* ## Why a separate module
|
|
45
|
+
*
|
|
46
|
+
* The install method is a one-time runtime bootstrap, not a
|
|
47
|
+
* per-call API. Putting it on its own RN module (vs. on the
|
|
48
|
+
* existing `StitcherBridge` / `IncrementalStitcherBridge`) keeps
|
|
49
|
+
* the responsibility surface narrow and the failure mode easy
|
|
50
|
+
* to diagnose ("`__stitcherProxy` not installed" → check
|
|
51
|
+
* `StitcherJsiInstaller` module registration first).
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
interface StitcherJsiInstallerModule {
|
|
55
|
+
install(): boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `__DEV__` is RN's global dev-flag. Guard the read with `typeof`
|
|
60
|
+
* so the helper works in any environment that imports it without
|
|
61
|
+
* defining __DEV__ (jest, SSR, custom tooling). Same pattern RN's
|
|
62
|
+
* own debug code uses.
|
|
63
|
+
*/
|
|
64
|
+
function isDev(): boolean {
|
|
65
|
+
return typeof __DEV__ !== 'undefined' && __DEV__;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let installed = false;
|
|
69
|
+
|
|
70
|
+
export function ensureStitcherProxyInstalled(): boolean {
|
|
71
|
+
if (installed) return true;
|
|
72
|
+
// Already installed by an earlier hook mount. Cheap fast-path.
|
|
73
|
+
if (typeof (globalThis as { __stitcherProxy?: unknown }).__stitcherProxy !== 'undefined') {
|
|
74
|
+
installed = true;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const mod = (NativeModules as { StitcherJsiInstaller?: StitcherJsiInstallerModule })
|
|
79
|
+
.StitcherJsiInstaller;
|
|
80
|
+
if (mod == null || typeof mod.install !== 'function') {
|
|
81
|
+
// Module not present — Android until Phase 4b.ii lands, or
|
|
82
|
+
// an old iOS build. Surface this once at debug-info level so
|
|
83
|
+
// the host can see "your worklets are JS-registered only" in
|
|
84
|
+
// logcat / Console.app without a noisy per-frame warning.
|
|
85
|
+
if (isDev() && !warnedAboutMissingModule) {
|
|
86
|
+
warnedAboutMissingModule = true;
|
|
87
|
+
console.info(
|
|
88
|
+
'[react-native-image-stitcher] StitcherJsiInstaller native ' +
|
|
89
|
+
'module not found; host worklets registered in JS-side ' +
|
|
90
|
+
'registry only. AR-mode dispatch requires the native install ' +
|
|
91
|
+
'(iOS Phase 4b.i — included in v0.8.0; Android Phase 4b.ii ' +
|
|
92
|
+
'— follow-up release).',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const ok = mod.install();
|
|
100
|
+
if (!ok) {
|
|
101
|
+
// Native module ran but couldn't install (JSI runtime
|
|
102
|
+
// unreachable). Same fallback as the missing-module case.
|
|
103
|
+
if (isDev() && !warnedAboutFailedInstall) {
|
|
104
|
+
warnedAboutFailedInstall = true;
|
|
105
|
+
console.info(
|
|
106
|
+
'[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
|
|
107
|
+
'returned false (JSI runtime unreachable — remote debug ' +
|
|
108
|
+
'mode?). Falling back to JS-side host worklet registry.',
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
installed = true;
|
|
114
|
+
return true;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (isDev() && !warnedAboutFailedInstall) {
|
|
117
|
+
warnedAboutFailedInstall = true;
|
|
118
|
+
console.info(
|
|
119
|
+
'[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
|
|
120
|
+
'threw: ' +
|
|
121
|
+
String(err) +
|
|
122
|
+
'. Falling back to JS-side host worklet registry.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let warnedAboutMissingModule = false;
|
|
130
|
+
let warnedAboutFailedInstall = false;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Test-only — reset module-internal state. Used by jest to allow
|
|
134
|
+
* multiple test cases to re-trigger the install path independently.
|
|
135
|
+
* NOT exported from `src/index.ts`.
|
|
136
|
+
*/
|
|
137
|
+
export function _resetStitcherProxyInstallStateForTests(): void {
|
|
138
|
+
installed = false;
|
|
139
|
+
warnedAboutMissingModule = false;
|
|
140
|
+
warnedAboutFailedInstall = false;
|
|
141
|
+
}
|
|
@@ -126,18 +126,18 @@ import type {
|
|
|
126
126
|
FrameProcessorPlugin,
|
|
127
127
|
} from 'react-native-vision-camera';
|
|
128
128
|
|
|
129
|
-
import type {
|
|
129
|
+
import type { CameraFrame } from './CameraFrame';
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Frames the lib's stitching worklet accepts. Accepting either a
|
|
133
133
|
* vc `Frame` (what the host's `useFrameProcessor` body sees) or the
|
|
134
|
-
* lib's `
|
|
134
|
+
* lib's `CameraFrame` (what the lib's `useFrameProcessor` body
|
|
135
135
|
* sees) keeps the same `useStitcherWorklet` usable from both kinds
|
|
136
136
|
* of host worklet bodies without a cast on the call site. The
|
|
137
137
|
* worklet only reads `width` / `height`; the rest of the frame
|
|
138
138
|
* object is forwarded verbatim to the native plugin.
|
|
139
139
|
*/
|
|
140
|
-
export type StitcherWorkletInput = Frame |
|
|
140
|
+
export type StitcherWorkletInput = Frame | CameraFrame;
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
export interface UseStitcherWorkletOptions {
|
|
@@ -173,7 +173,7 @@ export interface UseStitcherWorkletOptions {
|
|
|
173
173
|
|
|
174
174
|
export interface StitcherWorkletHandle {
|
|
175
175
|
/**
|
|
176
|
-
* Worklet function: pass a `
|
|
176
|
+
* Worklet function: pass a `CameraFrame` to perform one frame of
|
|
177
177
|
* the lib's first-party stitching (throttle + pose synthesis +
|
|
178
178
|
* native plugin call). Safe to call from inside another
|
|
179
179
|
* `'worklet'`-prefixed function (this is the canonical
|
|
@@ -341,7 +341,7 @@ export function useStitcherWorklet(
|
|
|
341
341
|
// party callback installed in `RNSARWorkletRuntime`). Calling
|
|
342
342
|
// the vc Frame Processor plugin here would throw
|
|
343
343
|
// `getPropertyAsObject: property '__frame' is undefined`
|
|
344
|
-
// because AR frames are `
|
|
344
|
+
// because AR frames are `CameraFrameHostObject` instances
|
|
345
345
|
// and don't carry the vc `Frame` proxy's JSI marker. The
|
|
346
346
|
// throw is caught silently by the per-worklet error handler
|
|
347
347
|
// (`RNSARWorkletRuntime.mm:284-301`) and bubbles up only to
|
|
@@ -353,13 +353,13 @@ export function useStitcherWorklet(
|
|
|
353
353
|
// hook (the AR-side stitching path runs natively, independent
|
|
354
354
|
// of the composed worklet body).
|
|
355
355
|
//
|
|
356
|
-
// The `(frame as
|
|
356
|
+
// The `(frame as CameraFrame).source` cast is safe: vc
|
|
357
357
|
// `Frame` doesn't carry a `source` property so the check
|
|
358
358
|
// returns `undefined !== 'ar'` → `true`, and the worklet
|
|
359
359
|
// proceeds normally. Only frames that explicitly tag
|
|
360
360
|
// themselves as AR-source (which our native AR dispatcher
|
|
361
|
-
// does — see `
|
|
362
|
-
if ((frame as
|
|
361
|
+
// does — see `CameraFrameHostObject.mm`) get short-circuited.
|
|
362
|
+
if ((frame as CameraFrame).source === 'ar') return;
|
|
363
363
|
|
|
364
364
|
// Throttle (verbatim from useFrameProcessorDriver).
|
|
365
365
|
sharedFrameCounter.value += 1;
|
|
@@ -388,7 +388,7 @@ export function useStitcherWorklet(
|
|
|
388
388
|
const fy = h * sharedFyNumerator.value;
|
|
389
389
|
|
|
390
390
|
// vc's `plugin.call` is typed against vc's `Frame`. The worklet
|
|
391
|
-
// accepts the union (`Frame |
|
|
391
|
+
// accepts the union (`Frame | CameraFrame`); cast through
|
|
392
392
|
// `unknown` because the union doesn't satisfy vc's interface
|
|
393
393
|
// even though structurally both members do.
|
|
394
394
|
plugin.call(frame as unknown as Frame, {
|