react-native-image-stitcher 0.10.0 → 0.11.1

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 CHANGED
@@ -16,6 +16,143 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [0.11.1] — 2026-05-28
20
+
21
+ ### Fixed — AR-mode composed worklets silently throw
22
+
23
+ `useStitcherWorklet`'s `call(frame)` was invoking the vision-camera
24
+ Frame Processor plugin on every frame regardless of mode. In AR
25
+ mode the frame is a `StitcherFrameHostObject` (no `__frame` JSI
26
+ marker), so the vc plugin threw `getPropertyAsObject: property
27
+ '__frame' is undefined`. The throw was caught silently by
28
+ `RNSARWorkletRuntime`'s per-worklet error isolation (logged to
29
+ `os_log`, not surfaced to JS), causing any host code AFTER
30
+ `stitcher.call(frame)` in the composed worklet body —
31
+ `runOnJS` callbacks, `Worklets.createRunOnJS` dispatches, further
32
+ host worklet logic — to silently never execute in AR mode.
33
+
34
+ The hook's module docstring already promised AR mode would no-op
35
+ ("AR mode is unaffected — the AR-session dispatch path already
36
+ composes natively"), but the code didn't enforce it. v0.11.1 adds
37
+ an early-return on `frame.source === 'ar'` in `useStitcherWorklet`'s
38
+ worklet body. AR stitching continues to run natively via
39
+ `RNSARSession.swift`'s first-party callback path
40
+ (`consumer.consumeFrame(arFrame, pose)` at line 510-511), which is
41
+ the architectural contract for AR-mode stitching since v0.8.0.
42
+
43
+ This bug was latent in v0.11.0 — surfaced by Test 2 of
44
+ `docs/v0.11.0-manual-verification-checklist.md` on Ram's iPhone.
45
+
46
+ Also added: `StitcherJsiInstaller::install` now eagerly initializes
47
+ the worklets-core default `JsiWorkletContext` singleton during JSI
48
+ bootstrap. This is defense-in-depth — worklets-core's own `Worklets`
49
+ module also initializes the default, but eager init from our
50
+ installer makes `runOnJS` from AR-mode worklets robust to host-app
51
+ import order (no dependency on worklets-core's `Worklets` module
52
+ loading before our AR runtime constructs its context).
53
+
54
+ ### Added — Jest test for AR-source short-circuit
55
+
56
+ New test file `src/stitching/__tests__/useStitcherWorklet.test.ts`
57
+ pins the AR no-op contract. 5 new tests; full suite now 74/74 pass
58
+ (was 69/69 in v0.11.0).
59
+
60
+ ## [0.11.0] — 2026-05-28
61
+
62
+ ### Added — `useStitcherWorklet` for non-AR composition
63
+
64
+ Closes the v0.8.0 Phase 5 either-or constraint: hosts that want to
65
+ write their OWN `useFrameProcessor` worklet body can now COMPOSE
66
+ first-party stitching back in with a single `stitcher.call(frame)`
67
+ call, instead of having to choose between their worklet and the
68
+ lib's stitching.
69
+
70
+ ```tsx
71
+ import {
72
+ Camera, useFrameProcessor, useStitcherWorklet,
73
+ type StitcherFrame,
74
+ } from 'react-native-image-stitcher';
75
+
76
+ function MyScreen() {
77
+ const stitcher = useStitcherWorklet();
78
+ const fp = useFrameProcessor((frame: StitcherFrame) => {
79
+ 'worklet';
80
+ hostPreLogic(frame);
81
+ stitcher.call(frame); // ← first-party stitching
82
+ hostPostLogic(frame);
83
+ }, [stitcher.call]);
84
+ return <Camera frameProcessor={fp} ... />;
85
+ }
86
+ ```
87
+
88
+ Migrating from v0.10.x is a one-line diff:
89
+
90
+ ```diff
91
+ + const stitcher = useStitcherWorklet();
92
+ const fp = useFrameProcessor((frame: StitcherFrame) => {
93
+ 'worklet';
94
+ + stitcher.call(frame); // ← first-party stitching back in
95
+ hostLogic(frame);
96
+ - }, [hostLogic]);
97
+ + }, [stitcher.call, hostLogic]);
98
+ ```
99
+
100
+ ### Changed
101
+
102
+ - `useFrameProcessorDriver` is now a thin wrapper around
103
+ `useStitcherWorklet`. Public API (`start` / `stop` /
104
+ `isRunning` / `frameProcessor`) is unchanged. Pose-reset
105
+ semantics preserved via the new `stitcher.reset()` method which
106
+ the driver calls internally from `start()` and `stop()`.
107
+ - The gyro subscription that powers pose tracking now lives in
108
+ `useStitcherWorklet` and runs for the lifetime of the hook
109
+ (mount → unmount) rather than being tied to the driver's
110
+ `start()` / `stop()`. In practice this matches all observed
111
+ host integrations (capture screens mount `<Camera>` for the
112
+ duration of capture; idle screens don't). Battery delta is
113
+ small (≪1% CPU at 33 ms gyro sampling).
114
+ - `<Camera frameProcessor>` JSDoc rewritten: the "Non-AR mode
115
+ tradeoff (HONEST)" section is replaced by a "Non-AR mode
116
+ composition" section that shows the v0.11.0 composition
117
+ pattern. The runtime `console.info` text is softened from
118
+ "your worklet REPLACES first-party stitching, panorama capture
119
+ will not produce stitched output" to "if you want first-party
120
+ stitching alongside, call `useStitcherWorklet()` from your
121
+ worklet body".
122
+ - Example app (`example/App.tsx`) now demonstrates the
123
+ composition pattern end-to-end: one
124
+ `useFrameProcessor` body that calls both `stitcher.call(frame)`
125
+ and the existing 1 Hz host tick log. `<Camera>` mounts with
126
+ `frameProcessor={exampleFrameProcessor}` (previously left
127
+ unwired with an "intentionally unused" comment block).
128
+ - `docs/frame-access-tiers.md` adds a `useStitcherWorklet`
129
+ reference section + 1-line migration diff. Softens the
130
+ "either-or" language in the Tier 3 + AR-vs-non-AR sections.
131
+
132
+ ### Files changed
133
+ - NEW: `src/stitching/useStitcherWorklet.ts`
134
+ - `src/stitching/useFrameProcessorDriver.ts` (refactored thin wrapper)
135
+ - `src/index.ts` (export new hook + types)
136
+ - `src/camera/Camera.tsx` (docstring + console.info softened)
137
+ - `example/App.tsx` (composition demo)
138
+ - `docs/frame-access-tiers.md` (new section + softened wording)
139
+ - `docs/v0.11.0-manual-verification-checklist.md` (Phase 4 human-loop checklist)
140
+
141
+ ### Not touched
142
+ - All native code (`ios/Sources/`, `android/src/main/cpp/`,
143
+ `android/src/main/java/io/imagestitcher/rn/`) — pure TS refactor.
144
+ - AR-mode dispatch path — already composes natively.
145
+ - `useFrameProcessor` (v0.8.0 public hook) — unchanged.
146
+
147
+ ### Verified
148
+ - JS Jest: **69 / 69 pass**
149
+ - C++ Gtest: **17 / 17 pass**
150
+ - Android JUnit: **6 / 6 pass**
151
+ - iOS build (Debug, generic iOS device): clean
152
+ - Android `:app:assembleDebug`: clean
153
+ - Real-device panorama capture verification deferred to the
154
+ human-in-the-loop checklist (`docs/v0.11.0-manual-verification-checklist.md`).
155
+
19
156
  ## [0.10.0] — 2026-05-28
20
157
 
21
158
  ### Added — v0.10.0 PR A: host-side test infrastructure (`#9A` + `#11A`)
@@ -221,26 +221,42 @@ export interface CameraProps {
221
221
  * }
222
222
  * ```
223
223
  *
224
- * ## Non-AR mode tradeoff (HONEST)
224
+ * ## Non-AR mode composition (v0.11.0+)
225
225
  *
226
226
  * vision-camera's `<Camera>` accepts ONLY ONE frame processor.
227
227
  * The lib's internal `useFrameProcessorDriver` produces the
228
228
  * processor that drives first-party panorama stitching in non-AR
229
- * mode. If you supply your own via this prop, **the lib's
230
- * first-party stitching is replaced**panorama capture in
231
- * non-AR mode will not produce stitched output until you remove
232
- * the prop or fork the SDK to compose both worklets manually.
229
+ * mode. If you supply your own via this prop, the lib's
230
+ * default processor is REPLACEDbut as of v0.11.0 you can
231
+ * COMPOSE first-party stitching back into your worklet body
232
+ * using `useStitcherWorklet`:
233
233
  *
234
- * For the common case (host wants worklet + lib wants stitching
235
- * concurrently), prefer AR mode: the AR-mode path natively fans
236
- * out to both the lib's first-party stitching AND every
237
- * registered host worklet on every frame, with per-worklet
238
- * failure isolation.
234
+ * ```tsx
235
+ * import {
236
+ * Camera, useFrameProcessor, useStitcherWorklet,
237
+ * type StitcherFrame,
238
+ * } from 'react-native-image-stitcher';
239
+ *
240
+ * function MyScreen() {
241
+ * const stitcher = useStitcherWorklet();
242
+ * const fp = useFrameProcessor((frame: StitcherFrame) => {
243
+ * 'worklet';
244
+ * hostPreLogic(frame);
245
+ * stitcher.call(frame); // ← first-party stitching
246
+ * hostPostLogic(frame);
247
+ * }, [stitcher.call]);
248
+ * return <Camera frameProcessor={fp} ... />;
249
+ * }
250
+ * ```
239
251
  *
240
- * Composition for non-AR mode (lib stitching + host worklet on
241
- * the same vc processor) is tracked as a v0.9+ follow-up;
242
- * needs the lib's first-party logic exposed as a vc Frame
243
- * Processor plugin the host's worklet can call.
252
+ * Hosts that DON'T call `useStitcherWorklet` from their worklet
253
+ * body replace first-party stitching for non-AR captures (a
254
+ * one-shot console.info documents this when the prop is first
255
+ * supplied). AR mode is unaffected either way — the AR-mode
256
+ * dispatch path (v0.8.0 Phase 4b.i / 4b.iii) natively fans out
257
+ * to both the lib's first-party stitching AND every registered
258
+ * host worklet on every frame, with per-worklet failure
259
+ * isolation.
244
260
  *
245
261
  * ## AR mode behaviour
246
262
  *
@@ -435,36 +435,36 @@ function Camera(props) {
435
435
  // Safety: stop the driver if the component unmounts mid-recording.
436
436
  // eslint-disable-next-line react-hooks/exhaustive-deps
437
437
  (0, react_1.useEffect)(() => () => { fpDriver.stop(); }, []);
438
- // v0.8.0 Phase 5 — frameProcessor prop semantics:
438
+ // v0.8.0 Phase 5 / v0.11.0 — frameProcessor prop semantics:
439
439
  //
440
- // - Host supplied? → use host's processor; lib's first-party
441
- // stitching is DISABLED in non-AR mode (vc accepts only one
442
- // processor). One-shot console.info documents the tradeoff
443
- // so the host isn't surprised by "panorama capture stopped
444
- // producing output" in non-AR mode. AR-mode capture is
445
- // unaffected the AR-session dispatch path fans out to BOTH
446
- // first-party and host worklets independently.
440
+ // - Host supplied? → use host's processor. The host's worklet
441
+ // body controls whether first-party stitching also fires:
442
+ // call `stitcher.call(frame)` (from `useStitcherWorklet`)
443
+ // inside the body to compose; omit to replace. One-shot
444
+ // console.info documents the choice so the host can spot a
445
+ // missing `useStitcherWorklet` call before they go hunting
446
+ // for "why is non-AR panorama capture not producing output".
447
+ // AR-mode capture is unaffected either way — the AR-session
448
+ // dispatch path fans out to BOTH first-party stitching AND
449
+ // every host worklet independently.
447
450
  //
448
451
  // - No host processor? → use `fpDriver.frameProcessor` which is
449
452
  // the lib's internal worklet driving first-party stitching
450
453
  // via `useFrameProcessorDriver`. Default behaviour for the
451
454
  // common "I just want panorama capture" case.
452
- //
453
- // The pre-v0.8.0 behaviour (host's prop silently ignored with
454
- // a warning) is gone — Phase 5 plumbs the prop through. The
455
- // tradeoff is honestly documented in the CameraProps docstring.
456
455
  const hostFrameProcessorAcceptedWarnedRef = (0, react_1.useRef)(false);
457
456
  if (hostFrameProcessor != null
458
457
  && !hostFrameProcessorAcceptedWarnedRef.current) {
459
458
  hostFrameProcessorAcceptedWarnedRef.current = true;
460
459
  // eslint-disable-next-line no-console
461
460
  console.info('[react-native-image-stitcher] Host frameProcessor supplied — '
462
- + 'non-AR mode will run YOUR worklet instead of the lib\'s '
463
- + 'first-party stitching plugin (vc accepts only one frame '
464
- + 'processor). Non-AR panorama capture will not produce '
465
- + 'stitched output until this prop is removed. AR-mode '
466
- + 'capture is unaffected (AR-session dispatch fans out to '
467
- + 'both first-party and host worklets independently).');
461
+ + 'non-AR mode will run YOUR composed worklet. If you want '
462
+ + 'first-party panorama stitching alongside your own logic, '
463
+ + 'call `useStitcherWorklet()` and invoke `stitcher.call(frame)` '
464
+ + 'from your worklet body (see `<Camera>` `frameProcessor` '
465
+ + 'JSDoc for the composition pattern). AR-mode capture is '
466
+ + 'unaffected (AR-session dispatch fans out to both '
467
+ + 'first-party and host worklets independently).');
468
468
  }
469
469
  // The Frame Processor worklet bound to vision-camera's Camera.
470
470
  // Host's wins if supplied; lib's internal driver otherwise.
package/dist/index.d.ts CHANGED
@@ -73,5 +73,7 @@ export { useFrameStream } from './stitching/useFrameStream';
73
73
  export type { FrameStreamOptions, SampledFrame } from './types';
74
74
  export { useFrameProcessorDriver } from './stitching/useFrameProcessorDriver';
75
75
  export type { UseFrameProcessorDriverOptions, FrameProcessorDriverHandle, } from './stitching/useFrameProcessorDriver';
76
+ export { useStitcherWorklet } from './stitching/useStitcherWorklet';
77
+ export type { UseStitcherWorkletOptions, StitcherWorkletHandle, StitcherWorkletInput, } from './stitching/useStitcherWorklet';
76
78
  export { stitchVideo } from './stitching/stitchVideo';
77
79
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@
22
22
  * adds RetaiLens-specific features on top.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.stitchVideo = exports.useFrameProcessorDriver = exports.useFrameStream = exports.useThrottledFrameProcessor = exports.useFrameProcessor = exports.useKeyframeStream = exports.useIncrementalStitcher = exports.cleanupOldKeyframes = exports.getIncrementalNativeModule = exports.subscribeIncrementalState = exports.incrementalStitcherIsAvailable = exports.IncrementalOutcome = exports.useDeviceOrientation = exports.useVideoCapture = exports.useCapture = exports.ViewportCropOverlay = exports.hybridSettingsToNativeConfig = exports.slitscanSettingsToNativeConfig = exports.panoramaSettingsToNativeConfig = exports.DEFAULT_HYBRID_SETTINGS = exports.DEFAULT_SLITSCAN_SETTINGS = exports.DEFAULT_FLOW_GATE_SETTINGS = exports.DEFAULT_PANORAMA_SETTINGS = exports.PanoramaSettingsModal = exports.PanoramaGuidance = exports.PanoramaBandOverlay = exports.IncrementalPanGuide = exports.CaptureThumbnailStrip = exports.useStitchStatsToast = exports.CaptureStitchStatsToast = exports.CaptureOrientationPill = exports.CaptureKeyframePill = exports.CaptureMemoryPill = exports.CaptureDebugOverlay = exports.CaptureStatusOverlay = exports.CapturePreview = exports.CaptureControlsBar = exports.CaptureHeader = exports.CameraView = exports.ARCameraView = exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.CameraError = exports.Camera = void 0;
25
+ exports.stitchVideo = exports.useStitcherWorklet = exports.useFrameProcessorDriver = exports.useFrameStream = exports.useThrottledFrameProcessor = exports.useFrameProcessor = exports.useKeyframeStream = exports.useIncrementalStitcher = exports.cleanupOldKeyframes = exports.getIncrementalNativeModule = exports.subscribeIncrementalState = exports.incrementalStitcherIsAvailable = exports.IncrementalOutcome = exports.useDeviceOrientation = exports.useVideoCapture = exports.useCapture = exports.ViewportCropOverlay = exports.hybridSettingsToNativeConfig = exports.slitscanSettingsToNativeConfig = exports.panoramaSettingsToNativeConfig = exports.DEFAULT_HYBRID_SETTINGS = exports.DEFAULT_SLITSCAN_SETTINGS = exports.DEFAULT_FLOW_GATE_SETTINGS = exports.DEFAULT_PANORAMA_SETTINGS = exports.PanoramaSettingsModal = exports.PanoramaGuidance = exports.PanoramaBandOverlay = exports.IncrementalPanGuide = exports.CaptureThumbnailStrip = exports.useStitchStatsToast = exports.CaptureStitchStatsToast = exports.CaptureOrientationPill = exports.CaptureKeyframePill = exports.CaptureMemoryPill = exports.CaptureDebugOverlay = exports.CaptureStatusOverlay = exports.CapturePreview = exports.CaptureControlsBar = exports.CaptureHeader = exports.CameraView = exports.ARCameraView = exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.CameraError = exports.Camera = void 0;
26
26
  // ─────────────────────────────────────────────────────────────────────
27
27
  // Layer 1 — the high-level <Camera> component
28
28
  // ─────────────────────────────────────────────────────────────────────
@@ -179,6 +179,14 @@ Object.defineProperty(exports, "useFrameStream", { enumerable: true, get: functi
179
179
  // `useIncrementalJSDriver` was removed; was deprecated in v0.5).
180
180
  var useFrameProcessorDriver_1 = require("./stitching/useFrameProcessorDriver");
181
181
  Object.defineProperty(exports, "useFrameProcessorDriver", { enumerable: true, get: function () { return useFrameProcessorDriver_1.useFrameProcessorDriver; } });
182
+ // v0.11.0 — composable first-party stitching as a worklet function.
183
+ // Hosts that want to COMPOSE their own per-frame logic with the
184
+ // lib's stitching (instead of REPLACING it via the <Camera>
185
+ // `frameProcessor` prop) call this hook + invoke `stitcher.call`
186
+ // inside their own `useFrameProcessor` body. See
187
+ // `docs/host-app-integration.md` § Tier 3 for the full pattern.
188
+ var useStitcherWorklet_1 = require("./stitching/useStitcherWorklet");
189
+ Object.defineProperty(exports, "useStitcherWorklet", { enumerable: true, get: function () { return useStitcherWorklet_1.useStitcherWorklet; } });
182
190
  // ── Batch stitching ───────────────────────────────────────────────────
183
191
  // Feed a video file straight to OpenCV's cv::Stitcher, bypassing the
184
192
  // incremental pipeline. Useful when you have content captured
@@ -4,99 +4,54 @@
4
4
  * from v0.6 onward (replaced the deprecated `useIncrementalJSDriver`
5
5
  * hook, which was removed in v0.6).
6
6
  *
7
- * Why this exists (vs the pre-v0.6 JS-driver predecessor)
8
- *
9
- * The old JS driver took a JPEG snapshot every ~250 ms and fed the
10
- * path to `IncrementalStitcher.processFrameAtPath` (both removed in
11
- * v0.6). That path had three costs:
12
- *
13
- * 1. JPEG encode (`takeSnapshot` ≈ 30–80 ms on iPhone 16 Pro)
14
- * 2. Disk write of the JPEG
15
- * 3. JPEG decode + cv::Mat alloc inside the engine
16
- *
17
- * Per-frame round-trip ~80 ms means ~4 Hz max throughput, and
18
- * ~80 ms latency between "this is the moment to accept" and "this
19
- * frame is in the engine". Both numbers caused operator-felt lag
20
- * on long shelf pans.
21
- *
22
- * This hook uses vision-camera's Frame Processor instead. The
23
- * worklet runs on the camera producer thread at the native frame
24
- * rate (30 fps on iOS). Each frame goes through a JSI plugin
25
- * (`cv_flow_gate_process_frame`) directly into
26
- * `IncrementalStitcher.consumeFrame` — the SAME entry point AR
27
- * mode uses, with the engine's existing KeyframeGate making the
28
- * accept/reject decision. Rejected frames cost ~3–8 ms; accepted
29
- * frames take the same deep-copy + workQueue path AR mode takes.
30
- *
31
- * Net wins: no JPEG round-trip on rejected frames, no disk thrash
32
- * during recording, lower latency to accept, full 30 fps gate
33
- * evaluation budget.
34
- *
35
- * Pose synthesis
36
- *
37
- * Non-AR mode has no ARKit pose. We integrate the gyroscope on
38
- * the JS thread (`react-native-sensors`), accumulate yaw + pitch,
39
- * and publish them via Reanimated `useSharedValue` so the worklet
40
- * can read them WITHOUT a thread hop. Translation is reported as
41
- * zero (no IMU translation; this is a known limitation we share
42
- * with the legacy driver drift ~1–2°/min over a 30 s capture is
43
- * below the gate's overlap threshold and rarely matters).
44
- *
45
- * Quaternion synthesis (q = q_yaw * q_pitch * q_roll, Tait-Bryan
46
- * YPR order to match the legacy driver's body-frame intent):
47
- * q_yaw = (0, sin(yaw/2), 0, cos(yaw/2))
48
- * q_pitch = (sin(pitch/2), 0, 0, cos(pitch/2))
49
- * q_roll = (0, 0, sin(roll/2), cos(roll/2))
50
- *
51
- * Expanded (cy, sy = cos/sin(yaw/2); analogous for cp/sp, cr/sr):
52
- * qx = cy*sp*cr + sy*cp*sr
53
- * qy = sy*cp*cr - cy*sp*sr
54
- * qz = cy*cp*sr - sy*sp*cr
55
- * qw = cy*cp*cr + sy*sp*sr
56
- *
57
- * When roll=0 this collapses to the 2-axis form
58
- * `(cy*sp, sy*cp, -sy*sp, cy*cp)` the legacy driver used, so
59
- * captures held perfectly level produce identical poses to the
60
- * pre-roll behaviour.
61
- *
62
- * Intrinsics are synthesised from the actual frame dimensions
63
- * (`frame.width`, `frame.height`) plus the host-provided
64
- * horizontal/vertical FoV defaults. The stitcher derives its FoV-
65
- * overlap window from these, so the assumed FoV matters for the
66
- * gate's overlap math but not for the panorama itself (the
67
- * stitcher feature-matches + RANSACs the final alignment).
68
- *
69
- * Throttling
70
- *
71
- * `evalEveryNFrames` controls how often the worklet calls the
72
- * plugin. Default 1 (every frame). Set higher to amortise the
73
- * plugin call + consumeFrame's gate evaluation across multiple
74
- * producer-thread frames on lower-end devices. Independent of —
75
- * and stacks on top of — the stitcher's own internal
76
- * `flowEvalEveryNFrames` (see `KeyframeGate.swift`); both
77
- * throttles can be active simultaneously and the effective cadence
78
- * is `evalEveryNFrames * flowEvalEveryNFrames`.
79
- *
80
- * Lifecycle
81
- *
82
- * `start()` subscribes to the gyro and resets pose accumulators.
83
- * `stop()` unsubscribes and resets. The returned `frameProcessor`
84
- * is meant to be passed to `<Camera frameProcessor={...} />` —
85
- * it's stable as long as the plugin reference and the FoV props
86
- * haven't changed. Returns `null` when the plugin isn't loaded
87
- * yet; pass `null`-or-fallback to the Camera in that case.
88
- *
89
- * Pairing with `IncrementalStitcher.start({frameSourceMode})`
90
- *
91
- * The plugin's per-frame call into `consumeFrameFromPlugin` is
92
- * gated by `IncrementalStitcher.frameProcessorIngestEnabled`,
93
- * which is TRUE only when the stitcher was started with
94
- * `frameSourceMode === 'frameProcessor'`. Hosts MUST call
95
- * `incrementalStitcher.start({ frameSourceMode: 'frameProcessor',
96
- * ... })` to actually get frames into the engine — otherwise the
97
- * worklet runs to completion but the wrapper drops the call.
98
- * `Camera.tsx` does this wiring automatically when the host opts
99
- * into this driver.
7
+ * v0.11.0 refactored to a thin wrapper around `useStitcherWorklet`.
8
+ * The plugin acquisition + shared-value declarations + gyro
9
+ * subscription + worklet body all live in `useStitcherWorklet` now;
10
+ * this hook just binds the returned worklet via vision-camera's
11
+ * `useFrameProcessor` and exposes the legacy `start` / `stop` /
12
+ * `isRunning` API for backwards compatibility with v0.10.x.
13
+ *
14
+ * ## Why the v0.11.0 split
15
+ *
16
+ * vision-camera v4 allows ONE frame processor per `<Camera>` mount.
17
+ * Pre-v0.11.0, hosts that wanted to compose their own worklet with
18
+ * the lib's first-party stitching couldn't passing a host
19
+ * `frameProcessor` REPLACED the lib's processor. v0.11.0 closes
20
+ * this gap by exposing the worklet body via `useStitcherWorklet`
21
+ * so hosts can write:
22
+ *
23
+ * const stitcher = useStitcherWorklet();
24
+ * const fp = useFrameProcessor((frame) => {
25
+ * 'worklet';
26
+ * hostPreLogic(frame);
27
+ * stitcher.call(frame); // first-party stitching
28
+ * hostPostLogic(frame);
29
+ * }, [stitcher.call]);
30
+ *
31
+ * `useFrameProcessorDriver` keeps the legacy default-integration
32
+ * shape (start / stop / isRunning) for the `<Camera>` component's
33
+ * built-in non-AR path and for any host still using the v0.10.x API
34
+ * directly. No behavioural change for those callers.
35
+ *
36
+ * ## start / stop behaviour
37
+ *
38
+ * - `start()` calls `stitcher.reset()` to zero the accumulated
39
+ * pose (preserves the pre-v0.11.0 "each capture starts with
40
+ * pose = (0, 0, 0)" contract).
41
+ * - `stop()` also resets the pose (idempotent; matches the
42
+ * pre-v0.11.0 stop() side effect of zeroing yaw / pitch / roll).
43
+ * - The gyro subscription itself is owned by `useStitcherWorklet`
44
+ * and runs for the lifetime of the hook. In the default
45
+ * `<Camera>` integration this means gyro is on while the camera
46
+ * screen is mounted same practical scope as pre-v0.11.0 in
47
+ * all observed host integrations (capture screens mount
48
+ * `<Camera>` for the duration of capture; idle screens don't).
49
+ *
50
+ * ## Pose synthesis / intrinsics / throttling
51
+ *
52
+ * Owned by `useStitcherWorklet`. See that file's header for the
53
+ * quaternion math, FoV-to-intrinsics derivation, throttle gate, and
54
+ * pairing-with-IncrementalStitcher.start docs.
100
55
  */
101
56
  import type { ReadonlyFrameProcessor } from 'react-native-vision-camera';
102
57
  export interface UseFrameProcessorDriverOptions {
@@ -131,9 +86,9 @@ export interface UseFrameProcessorDriverOptions {
131
86
  evalEveryNFrames?: number;
132
87
  }
133
88
  export interface FrameProcessorDriverHandle {
134
- /** Subscribe to the gyro + reset pose accumulators. Idempotent. */
89
+ /** Reset pose accumulators + mark the driver as running. Idempotent. */
135
90
  start: () => void;
136
- /** Unsubscribe + reset pose. */
91
+ /** Reset pose + mark the driver as stopped. Idempotent. */
137
92
  stop: () => void;
138
93
  /**
139
94
  * Pass this to `<Camera frameProcessor={...} />`. `null` until