react-native-image-stitcher 0.10.0 → 0.11.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 +96 -0
- package/dist/camera/Camera.d.ts +30 -14
- package/dist/camera/Camera.js +18 -18
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/stitching/useFrameProcessorDriver.d.ts +50 -95
- package/dist/stitching/useFrameProcessorDriver.js +76 -294
- package/dist/stitching/useStitcherWorklet.d.ts +185 -0
- package/dist/stitching/useStitcherWorklet.js +275 -0
- package/package.json +1 -1
- package/src/camera/Camera.tsx +48 -32
- package/src/index.ts +13 -0
- package/src/stitching/useFrameProcessorDriver.ts +79 -320
- package/src/stitching/useStitcherWorklet.ts +390 -0
|
@@ -5,123 +5,66 @@
|
|
|
5
5
|
* from v0.6 onward (replaced the deprecated `useIncrementalJSDriver`
|
|
6
6
|
* hook, which was removed in v0.6).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* qw = cy*cp*cr + sy*sp*sr
|
|
57
|
-
*
|
|
58
|
-
* When roll=0 this collapses to the 2-axis form
|
|
59
|
-
* `(cy*sp, sy*cp, -sy*sp, cy*cp)` the legacy driver used, so
|
|
60
|
-
* captures held perfectly level produce identical poses to the
|
|
61
|
-
* pre-roll behaviour.
|
|
62
|
-
*
|
|
63
|
-
* Intrinsics are synthesised from the actual frame dimensions
|
|
64
|
-
* (`frame.width`, `frame.height`) plus the host-provided
|
|
65
|
-
* horizontal/vertical FoV defaults. The stitcher derives its FoV-
|
|
66
|
-
* overlap window from these, so the assumed FoV matters for the
|
|
67
|
-
* gate's overlap math but not for the panorama itself (the
|
|
68
|
-
* stitcher feature-matches + RANSACs the final alignment).
|
|
69
|
-
*
|
|
70
|
-
* Throttling
|
|
71
|
-
*
|
|
72
|
-
* `evalEveryNFrames` controls how often the worklet calls the
|
|
73
|
-
* plugin. Default 1 (every frame). Set higher to amortise the
|
|
74
|
-
* plugin call + consumeFrame's gate evaluation across multiple
|
|
75
|
-
* producer-thread frames on lower-end devices. Independent of —
|
|
76
|
-
* and stacks on top of — the stitcher's own internal
|
|
77
|
-
* `flowEvalEveryNFrames` (see `KeyframeGate.swift`); both
|
|
78
|
-
* throttles can be active simultaneously and the effective cadence
|
|
79
|
-
* is `evalEveryNFrames * flowEvalEveryNFrames`.
|
|
80
|
-
*
|
|
81
|
-
* Lifecycle
|
|
82
|
-
*
|
|
83
|
-
* `start()` subscribes to the gyro and resets pose accumulators.
|
|
84
|
-
* `stop()` unsubscribes and resets. The returned `frameProcessor`
|
|
85
|
-
* is meant to be passed to `<Camera frameProcessor={...} />` —
|
|
86
|
-
* it's stable as long as the plugin reference and the FoV props
|
|
87
|
-
* haven't changed. Returns `null` when the plugin isn't loaded
|
|
88
|
-
* yet; pass `null`-or-fallback to the Camera in that case.
|
|
89
|
-
*
|
|
90
|
-
* Pairing with `IncrementalStitcher.start({frameSourceMode})`
|
|
91
|
-
*
|
|
92
|
-
* The plugin's per-frame call into `consumeFrameFromPlugin` is
|
|
93
|
-
* gated by `IncrementalStitcher.frameProcessorIngestEnabled`,
|
|
94
|
-
* which is TRUE only when the stitcher was started with
|
|
95
|
-
* `frameSourceMode === 'frameProcessor'`. Hosts MUST call
|
|
96
|
-
* `incrementalStitcher.start({ frameSourceMode: 'frameProcessor',
|
|
97
|
-
* ... })` to actually get frames into the engine — otherwise the
|
|
98
|
-
* worklet runs to completion but the wrapper drops the call.
|
|
99
|
-
* `Camera.tsx` does this wiring automatically when the host opts
|
|
100
|
-
* into this driver.
|
|
8
|
+
* v0.11.0 — refactored to a thin wrapper around `useStitcherWorklet`.
|
|
9
|
+
* The plugin acquisition + shared-value declarations + gyro
|
|
10
|
+
* subscription + worklet body all live in `useStitcherWorklet` now;
|
|
11
|
+
* this hook just binds the returned worklet via vision-camera's
|
|
12
|
+
* `useFrameProcessor` and exposes the legacy `start` / `stop` /
|
|
13
|
+
* `isRunning` API for backwards compatibility with v0.10.x.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the v0.11.0 split
|
|
16
|
+
*
|
|
17
|
+
* vision-camera v4 allows ONE frame processor per `<Camera>` mount.
|
|
18
|
+
* Pre-v0.11.0, hosts that wanted to compose their own worklet with
|
|
19
|
+
* the lib's first-party stitching couldn't — passing a host
|
|
20
|
+
* `frameProcessor` REPLACED the lib's processor. v0.11.0 closes
|
|
21
|
+
* this gap by exposing the worklet body via `useStitcherWorklet`
|
|
22
|
+
* so hosts can write:
|
|
23
|
+
*
|
|
24
|
+
* const stitcher = useStitcherWorklet();
|
|
25
|
+
* const fp = useFrameProcessor((frame) => {
|
|
26
|
+
* 'worklet';
|
|
27
|
+
* hostPreLogic(frame);
|
|
28
|
+
* stitcher.call(frame); // ← first-party stitching
|
|
29
|
+
* hostPostLogic(frame);
|
|
30
|
+
* }, [stitcher.call]);
|
|
31
|
+
*
|
|
32
|
+
* `useFrameProcessorDriver` keeps the legacy default-integration
|
|
33
|
+
* shape (start / stop / isRunning) for the `<Camera>` component's
|
|
34
|
+
* built-in non-AR path and for any host still using the v0.10.x API
|
|
35
|
+
* directly. No behavioural change for those callers.
|
|
36
|
+
*
|
|
37
|
+
* ## start / stop behaviour
|
|
38
|
+
*
|
|
39
|
+
* - `start()` calls `stitcher.reset()` to zero the accumulated
|
|
40
|
+
* pose (preserves the pre-v0.11.0 "each capture starts with
|
|
41
|
+
* pose = (0, 0, 0)" contract).
|
|
42
|
+
* - `stop()` also resets the pose (idempotent; matches the
|
|
43
|
+
* pre-v0.11.0 stop() side effect of zeroing yaw / pitch / roll).
|
|
44
|
+
* - The gyro subscription itself is owned by `useStitcherWorklet`
|
|
45
|
+
* and runs for the lifetime of the hook. In the default
|
|
46
|
+
* `<Camera>` integration this means gyro is on while the camera
|
|
47
|
+
* screen is mounted — same practical scope as pre-v0.11.0 in
|
|
48
|
+
* all observed host integrations (capture screens mount
|
|
49
|
+
* `<Camera>` for the duration of capture; idle screens don't).
|
|
50
|
+
*
|
|
51
|
+
* ## Pose synthesis / intrinsics / throttling
|
|
52
|
+
*
|
|
53
|
+
* Owned by `useStitcherWorklet`. See that file's header for the
|
|
54
|
+
* quaternion math, FoV-to-intrinsics derivation, throttle gate, and
|
|
55
|
+
* pairing-with-IncrementalStitcher.start docs.
|
|
101
56
|
*/
|
|
102
57
|
|
|
103
|
-
import { useCallback,
|
|
104
|
-
import {
|
|
105
|
-
gyroscope,
|
|
106
|
-
setUpdateIntervalForType,
|
|
107
|
-
SensorTypes,
|
|
108
|
-
} from 'react-native-sensors';
|
|
109
|
-
import type { Subscription } from 'rxjs';
|
|
110
|
-
// Reanimated's `useSharedValue` is the documented vision-camera
|
|
111
|
-
// idiom, but it's a heavy peer dep. `react-native-worklets-core`
|
|
112
|
-
// (already a transitive dep via vision-camera v4 on RN 0.84) exposes
|
|
113
|
-
// the same API surface (a `value` getter/setter readable from
|
|
114
|
-
// worklets and the JS thread) and is sufficient for our use.
|
|
115
|
-
import { useSharedValue } from 'react-native-worklets-core';
|
|
58
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
116
59
|
import {
|
|
117
60
|
useFrameProcessor,
|
|
118
|
-
VisionCameraProxy,
|
|
119
61
|
} from 'react-native-vision-camera';
|
|
120
62
|
import type {
|
|
121
|
-
FrameProcessorPlugin,
|
|
122
63
|
ReadonlyFrameProcessor,
|
|
123
64
|
} from 'react-native-vision-camera';
|
|
124
65
|
|
|
66
|
+
import { useStitcherWorklet } from './useStitcherWorklet';
|
|
67
|
+
|
|
125
68
|
|
|
126
69
|
export interface UseFrameProcessorDriverOptions {
|
|
127
70
|
/**
|
|
@@ -160,9 +103,9 @@ export interface UseFrameProcessorDriverOptions {
|
|
|
160
103
|
|
|
161
104
|
|
|
162
105
|
export interface FrameProcessorDriverHandle {
|
|
163
|
-
/**
|
|
106
|
+
/** Reset pose accumulators + mark the driver as running. Idempotent. */
|
|
164
107
|
start: () => void;
|
|
165
|
-
/**
|
|
108
|
+
/** Reset pose + mark the driver as stopped. Idempotent. */
|
|
166
109
|
stop: () => void;
|
|
167
110
|
/**
|
|
168
111
|
* Pass this to `<Camera frameProcessor={...} />`. `null` until
|
|
@@ -179,230 +122,46 @@ export interface FrameProcessorDriverHandle {
|
|
|
179
122
|
export function useFrameProcessorDriver(
|
|
180
123
|
options: UseFrameProcessorDriverOptions = {},
|
|
181
124
|
): FrameProcessorDriverHandle {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
} = options;
|
|
125
|
+
// v0.11.0 — delegate plugin / shared values / gyro / worklet body
|
|
126
|
+
// to `useStitcherWorklet`. This hook is now a thin wrapper that
|
|
127
|
+
// binds the returned worklet via `useFrameProcessor` and exposes
|
|
128
|
+
// the legacy lifecycle API.
|
|
129
|
+
const stitcher = useStitcherWorklet(options);
|
|
188
130
|
|
|
189
|
-
// ── Plugin acquisition ──────────────────────────────────────────
|
|
190
|
-
//
|
|
191
|
-
// `initFrameProcessorPlugin` can return `undefined` if called
|
|
192
|
-
// before vision-camera's plugin registry has finished initialising
|
|
193
|
-
// (race observed in F8.1.a). We retry on a fixed timer instead of
|
|
194
|
-
// firing on every render — the earlier render-driven pattern
|
|
195
|
-
// (adversarial-review H3) re-invoked `initFrameProcessorPlugin`
|
|
196
|
-
// 60+ times per second during recording, and the vision-camera
|
|
197
|
-
// contract for repeated lookups is undocumented.
|
|
198
|
-
//
|
|
199
|
-
// Pattern: mount-once useEffect, try synchronously, fall back to a
|
|
200
|
-
// 16-ms retry timer until success or unmount.
|
|
201
|
-
const [plugin, setPlugin] = useState<FrameProcessorPlugin | null>(null);
|
|
202
|
-
useEffect(() => {
|
|
203
|
-
let cancelled = false;
|
|
204
|
-
let timerId: ReturnType<typeof setTimeout> | null = null;
|
|
205
|
-
const tryAcquire = () => {
|
|
206
|
-
if (cancelled) return;
|
|
207
|
-
const p = VisionCameraProxy.initFrameProcessorPlugin(
|
|
208
|
-
'cv_flow_gate_process_frame',
|
|
209
|
-
{},
|
|
210
|
-
);
|
|
211
|
-
if (p != null) {
|
|
212
|
-
setPlugin(p);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
// ~one display-frame retry — matches the F8.1.a observation
|
|
216
|
-
// that the registry becomes ready by the next render tick.
|
|
217
|
-
timerId = setTimeout(tryAcquire, 16);
|
|
218
|
-
};
|
|
219
|
-
tryAcquire();
|
|
220
|
-
return () => {
|
|
221
|
-
cancelled = true;
|
|
222
|
-
if (timerId != null) clearTimeout(timerId);
|
|
223
|
-
};
|
|
224
|
-
// Empty deps on purpose — runs ONCE on mount. Re-acquiring on
|
|
225
|
-
// re-render would race with worklet binding.
|
|
226
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
227
|
-
}, []);
|
|
228
|
-
|
|
229
|
-
// ── Shared values (worklet ↔ JS thread) ─────────────────────────
|
|
230
|
-
//
|
|
231
|
-
// Reanimated guarantees coherent reads from the producer thread.
|
|
232
|
-
// We write yaw/pitch on the JS thread (gyro callbacks); the worklet
|
|
233
|
-
// reads them every frame. No round-trip cost — these are mapped
|
|
234
|
-
// into the worklet's runtime by the Reanimated bridge.
|
|
235
|
-
//
|
|
236
|
-
// FoV-derived values (the "half-angle tangent reciprocal"
|
|
237
|
-
// f-numerators) are pre-computed on the JS thread + published via
|
|
238
|
-
// shared values so the worklet's dependency array shrinks to just
|
|
239
|
-
// `[plugin]`. Earlier draft baked `fovHorizDegrees` /
|
|
240
|
-
// `fovVertDegrees` into the closure → worklet re-serialised on
|
|
241
|
-
// every host re-render that changed the prop refs (adversarial-
|
|
242
|
-
// review M1).
|
|
243
|
-
const sharedYaw = useSharedValue(0);
|
|
244
|
-
const sharedPitch = useSharedValue(0);
|
|
245
|
-
// F8.3-followup-roll — integrate gyroscope Z (out-of-screen for a
|
|
246
|
-
// portrait device) to track wrist-twist roll. Field captures with
|
|
247
|
-
// casual hand-hold rarely stay perfectly level; without this the
|
|
248
|
-
// pose stream lies and the cv::Stitcher's intrinsic estimator may
|
|
249
|
-
// pick a worse projection mode.
|
|
250
|
-
const sharedRoll = useSharedValue(0);
|
|
251
|
-
const sharedFrameCounter = useSharedValue(0);
|
|
252
|
-
const sharedEvalEveryN = useSharedValue(Math.max(1, evalEveryNFrames));
|
|
253
|
-
const sharedFxNumerator = useSharedValue(
|
|
254
|
-
1.0 / (2.0 * Math.tan((fovHorizDegrees * Math.PI / 180) / 2)),
|
|
255
|
-
);
|
|
256
|
-
const sharedFyNumerator = useSharedValue(
|
|
257
|
-
1.0 / (2.0 * Math.tan((fovVertDegrees * Math.PI / 180) / 2)),
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
// Keep prop-derived shared values in sync. Cheap re-renders;
|
|
261
|
-
// these don't trigger worklet rebuild.
|
|
262
|
-
useEffect(() => {
|
|
263
|
-
sharedEvalEveryN.value = Math.max(1, evalEveryNFrames);
|
|
264
|
-
}, [evalEveryNFrames, sharedEvalEveryN]);
|
|
265
|
-
useEffect(() => {
|
|
266
|
-
sharedFxNumerator.value =
|
|
267
|
-
1.0 / (2.0 * Math.tan((fovHorizDegrees * Math.PI / 180) / 2));
|
|
268
|
-
}, [fovHorizDegrees, sharedFxNumerator]);
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
sharedFyNumerator.value =
|
|
271
|
-
1.0 / (2.0 * Math.tan((fovVertDegrees * Math.PI / 180) / 2));
|
|
272
|
-
}, [fovVertDegrees, sharedFyNumerator]);
|
|
273
|
-
|
|
274
|
-
// ── Lifecycle state (JS thread only) ────────────────────────────
|
|
275
|
-
const gyroSubRef = useRef<Subscription | null>(null);
|
|
276
|
-
const lastGyroAtRef = useRef<number | null>(null);
|
|
277
131
|
const isRunningRef = useRef(false);
|
|
278
132
|
|
|
279
|
-
const stop = useCallback(() => {
|
|
280
|
-
if (gyroSubRef.current) {
|
|
281
|
-
gyroSubRef.current.unsubscribe();
|
|
282
|
-
gyroSubRef.current = null;
|
|
283
|
-
}
|
|
284
|
-
isRunningRef.current = false;
|
|
285
|
-
sharedYaw.value = 0;
|
|
286
|
-
sharedPitch.value = 0;
|
|
287
|
-
sharedRoll.value = 0;
|
|
288
|
-
sharedFrameCounter.value = 0;
|
|
289
|
-
lastGyroAtRef.current = null;
|
|
290
|
-
}, [sharedYaw, sharedPitch, sharedRoll, sharedFrameCounter]);
|
|
291
|
-
|
|
292
133
|
const start = useCallback(() => {
|
|
293
134
|
if (isRunningRef.current) return;
|
|
294
|
-
|
|
295
|
-
sharedPitch.value = 0;
|
|
296
|
-
sharedRoll.value = 0;
|
|
297
|
-
sharedFrameCounter.value = 0;
|
|
298
|
-
lastGyroAtRef.current = null;
|
|
135
|
+
stitcher.reset();
|
|
299
136
|
isRunningRef.current = true;
|
|
137
|
+
}, [stitcher]);
|
|
300
138
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// z = wrist-twist roll (about world-Z, normal to the screen)
|
|
307
|
-
// Right-hand-rule convention throughout — same signs the pre-v0.6
|
|
308
|
-
// `useIncrementalJSDriver` produced. If field captures show
|
|
309
|
-
// inverted roll, flip the sign on `z * dt` below.
|
|
310
|
-
setUpdateIntervalForType(SensorTypes.gyroscope, gyroIntervalMs);
|
|
311
|
-
gyroSubRef.current = gyroscope.subscribe({
|
|
312
|
-
next: ({ x, y, z }) => {
|
|
313
|
-
const now = Date.now();
|
|
314
|
-
if (lastGyroAtRef.current === null) {
|
|
315
|
-
lastGyroAtRef.current = now;
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
const dt = (now - lastGyroAtRef.current) / 1000.0;
|
|
319
|
-
lastGyroAtRef.current = now;
|
|
320
|
-
sharedYaw.value += y * dt;
|
|
321
|
-
sharedPitch.value += x * dt;
|
|
322
|
-
sharedRoll.value += z * dt;
|
|
323
|
-
},
|
|
324
|
-
error: (err) => {
|
|
325
|
-
// eslint-disable-next-line no-console
|
|
326
|
-
console.warn('[useFrameProcessorDriver] gyro error', err);
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
}, [gyroIntervalMs, sharedYaw, sharedPitch, sharedRoll, sharedFrameCounter]);
|
|
139
|
+
const stop = useCallback(() => {
|
|
140
|
+
if (!isRunningRef.current) return;
|
|
141
|
+
stitcher.reset();
|
|
142
|
+
isRunningRef.current = false;
|
|
143
|
+
}, [stitcher]);
|
|
330
144
|
|
|
331
|
-
// ── Worklet
|
|
145
|
+
// ── Worklet binding ─────────────────────────────────────────────
|
|
332
146
|
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
147
|
+
// `stitcher.call` is itself a worklet (see `useStitcherWorklet`),
|
|
148
|
+
// so we just forward each frame to it. Memoised on
|
|
149
|
+
// [stitcher.call] so the host's `<Camera>` doesn't see frame-
|
|
150
|
+
// processor identity churn on every render — only when the
|
|
151
|
+
// underlying plugin acquires (null → non-null).
|
|
338
152
|
const frameProcessor = useFrameProcessor((frame) => {
|
|
339
153
|
'worklet';
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
// Throttle: only every Nth frame. Counter increments first so
|
|
343
|
-
// frame #0 is "due" (N>=1 always divides 0). Cheaper than
|
|
344
|
-
// calling the plugin on rejected frames; saves the ~1 µs
|
|
345
|
-
// marshalling cost per skip.
|
|
346
|
-
sharedFrameCounter.value += 1;
|
|
347
|
-
const N = sharedEvalEveryN.value;
|
|
348
|
-
if (N > 1 && (sharedFrameCounter.value % N) !== 0) return;
|
|
154
|
+
stitcher.call(frame);
|
|
155
|
+
}, [stitcher.call]);
|
|
349
156
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// poses to the pre-F8.3-followup-roll behaviour. See the
|
|
355
|
-
// expanded math in the file header doc-comment.
|
|
356
|
-
const halfYaw = sharedYaw.value / 2;
|
|
357
|
-
const halfPitch = sharedPitch.value / 2;
|
|
358
|
-
const halfRoll = sharedRoll.value / 2;
|
|
359
|
-
const cy_ = Math.cos(halfYaw);
|
|
360
|
-
const sy_ = Math.sin(halfYaw);
|
|
361
|
-
const cp = Math.cos(halfPitch);
|
|
362
|
-
const sp = Math.sin(halfPitch);
|
|
363
|
-
const cr = Math.cos(halfRoll);
|
|
364
|
-
const sr = Math.sin(halfRoll);
|
|
365
|
-
const qx = cy_ * sp * cr + sy_ * cp * sr;
|
|
366
|
-
const qy = sy_ * cp * cr - cy_ * sp * sr;
|
|
367
|
-
const qz = cy_ * cp * sr - sy_ * sp * cr;
|
|
368
|
-
const qw = cy_ * cp * cr + sy_ * sp * sr;
|
|
369
|
-
|
|
370
|
-
// Intrinsics from FoV + actual frame dims.
|
|
371
|
-
// fx = w * (1 / (2 * tan(fovH/2))) (the parenthesised half
|
|
372
|
-
// is the precomputed `sharedFxNumerator` — see M1 fix).
|
|
373
|
-
const w = frame.width;
|
|
374
|
-
const h = frame.height;
|
|
375
|
-
const fx = w * sharedFxNumerator.value;
|
|
376
|
-
const fy = h * sharedFyNumerator.value;
|
|
377
|
-
|
|
378
|
-
plugin.call(frame, {
|
|
379
|
-
tx: 0, ty: 0, tz: 0,
|
|
380
|
-
qx, qy, qz, qw,
|
|
381
|
-
fx, fy,
|
|
382
|
-
cx: w / 2, cy: h / 2,
|
|
383
|
-
imageWidth: w, imageHeight: h,
|
|
384
|
-
timestampMs: 0,
|
|
385
|
-
// 2 == RNSARTrackingState.tracking — we always claim "good
|
|
386
|
-
// tracking" because there's no ARKit signal to differentiate.
|
|
387
|
-
// (Same contract as the pre-v0.6 useIncrementalJSDriver.)
|
|
388
|
-
trackingStateRaw: 2,
|
|
389
|
-
});
|
|
390
|
-
// Deps array intentionally minimal: only `plugin` actually
|
|
391
|
-
// requires worklet rebuild. All FoV / pose / counter / cadence
|
|
392
|
-
// values flow through stable shared-value refs that Reanimated
|
|
393
|
-
// wires through the producer-thread runtime independently of
|
|
394
|
-
// React's render cycle. (Adversarial-review M1.)
|
|
395
|
-
}, [plugin]);
|
|
396
|
-
|
|
397
|
-
// ── Return handle ───────────────────────────────────────────────
|
|
398
|
-
//
|
|
399
|
-
// Returns a getter for `isRunning` so callers always see the live
|
|
400
|
-
// state (the hook itself doesn't re-render on start/stop — that's
|
|
401
|
-
// intentional, avoids stale-Camera-prop churn).
|
|
157
|
+
// Match pre-v0.11.0 contract: return `null` for `frameProcessor`
|
|
158
|
+
// until the underlying JSI plugin has resolved. `<Camera>` falls
|
|
159
|
+
// back to `undefined` in the null window so vision-camera doesn't
|
|
160
|
+
// try to bind an unready worklet.
|
|
402
161
|
return useMemo<FrameProcessorDriverHandle>(() => ({
|
|
403
162
|
start,
|
|
404
163
|
stop,
|
|
405
|
-
frameProcessor:
|
|
164
|
+
frameProcessor: stitcher.isReady ? frameProcessor : null,
|
|
406
165
|
get isRunning() { return isRunningRef.current; },
|
|
407
|
-
}), [start, stop,
|
|
166
|
+
}), [start, stop, frameProcessor, stitcher.isReady]);
|
|
408
167
|
}
|