react-native-image-stitcher 0.14.2 → 0.15.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +35 -0
  3. package/RNImageStitcher.podspec +8 -7
  4. package/android/build.gradle +0 -16
  5. package/android/src/main/cpp/CMakeLists.txt +2 -63
  6. package/android/src/main/cpp/image_stitcher_jni.cpp +14 -0
  7. package/android/src/main/cpp/keyframe_gate_jni.cpp +13 -0
  8. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +285 -3
  9. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +180 -1162
  10. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +29 -0
  11. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +0 -4
  12. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +13 -64
  13. package/cpp/keyframe_gate.cpp +82 -23
  14. package/cpp/keyframe_gate.hpp +31 -2
  15. package/cpp/stitcher.cpp +208 -28
  16. package/cpp/tests/CMakeLists.txt +18 -12
  17. package/cpp/tests/keyframe_timebudget_test.cpp +65 -0
  18. package/cpp/tests/warp_guard_test.cpp +48 -0
  19. package/cpp/warp_guard.hpp +41 -0
  20. package/dist/camera/Camera.d.ts +31 -16
  21. package/dist/camera/Camera.js +10 -2
  22. package/dist/camera/CaptureStitchStatsToast.d.ts +15 -2
  23. package/dist/camera/CaptureStitchStatsToast.js +27 -7
  24. package/dist/camera/PanoramaSettings.d.ts +10 -223
  25. package/dist/camera/PanoramaSettings.js +6 -28
  26. package/dist/camera/PanoramaSettingsBridge.d.ts +1 -24
  27. package/dist/camera/PanoramaSettingsBridge.js +3 -102
  28. package/dist/camera/PanoramaSettingsModal.js +7 -1
  29. package/dist/camera/buildPanoramaInitialSettings.d.ts +11 -0
  30. package/dist/camera/buildPanoramaInitialSettings.js +4 -0
  31. package/dist/camera/cameraErrorMessages.d.ts +32 -0
  32. package/dist/camera/cameraErrorMessages.js +53 -0
  33. package/dist/camera/selectCaptureDevice.d.ts +5 -1
  34. package/dist/camera/selectCaptureDevice.js +22 -2
  35. package/dist/camera/useCapture.js +38 -0
  36. package/dist/index.d.ts +5 -8
  37. package/dist/index.js +11 -34
  38. package/dist/stitching/incremental.d.ts +1 -117
  39. package/dist/stitching/stitchVideo.d.ts +0 -35
  40. package/dist/types.d.ts +0 -87
  41. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +96 -674
  42. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +9 -12
  43. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +14 -0
  44. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +7 -0
  45. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +6 -0
  46. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +2 -2
  47. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +3 -3
  48. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +28 -60
  49. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +180 -921
  50. package/ios/Sources/RNImageStitcher/RNSARSession.swift +10 -35
  51. package/ios/Sources/RNImageStitcher/Stitcher.swift +84 -35
  52. package/ios/Sources/RNImageStitcher/StitcherBridge.m +13 -0
  53. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +132 -5
  54. package/package.json +3 -2
  55. package/src/camera/Camera.tsx +43 -22
  56. package/src/camera/CaptureStitchStatsToast.tsx +58 -14
  57. package/src/camera/PanoramaSettings.ts +16 -289
  58. package/src/camera/PanoramaSettingsBridge.ts +3 -114
  59. package/src/camera/PanoramaSettingsModal.tsx +14 -1
  60. package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +3 -188
  61. package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +41 -0
  62. package/src/camera/__tests__/cameraErrorMessages.test.ts +76 -0
  63. package/src/camera/__tests__/selectCaptureDevice.test.ts +33 -0
  64. package/src/camera/buildPanoramaInitialSettings.ts +17 -0
  65. package/src/camera/cameraErrorMessages.ts +84 -0
  66. package/src/camera/selectCaptureDevice.ts +28 -3
  67. package/src/camera/useCapture.ts +44 -1
  68. package/src/index.ts +11 -40
  69. package/src/stitching/incremental.ts +3 -140
  70. package/src/stitching/stitchVideo.ts +0 -26
  71. package/src/types.ts +0 -95
  72. package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +0 -227
  73. package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +0 -1081
  74. package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +0 -103
  75. package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +0 -256
  76. package/cpp/stitcher_frame_jsi.cpp +0 -214
  77. package/cpp/stitcher_frame_jsi.hpp +0 -108
  78. package/cpp/stitcher_proxy_jsi.cpp +0 -109
  79. package/cpp/stitcher_proxy_jsi.hpp +0 -46
  80. package/cpp/stitcher_worklet_dispatch.cpp +0 -103
  81. package/cpp/stitcher_worklet_dispatch.hpp +0 -71
  82. package/cpp/stitcher_worklet_registry.cpp +0 -91
  83. package/cpp/stitcher_worklet_registry.hpp +0 -146
  84. package/cpp/tests/stitcher_worklet_registry_test.cpp +0 -195
  85. package/dist/stitching/IncrementalStitcherView.d.ts +0 -41
  86. package/dist/stitching/IncrementalStitcherView.js +0 -157
  87. package/dist/stitching/StitcherWorkletRegistry.d.ts +0 -117
  88. package/dist/stitching/StitcherWorkletRegistry.js +0 -78
  89. package/dist/stitching/ensureStitcherProxyInstalled.d.ts +0 -8
  90. package/dist/stitching/ensureStitcherProxyInstalled.js +0 -81
  91. package/dist/stitching/useFrameProcessor.d.ts +0 -119
  92. package/dist/stitching/useFrameProcessor.js +0 -196
  93. package/dist/stitching/useFrameStream.d.ts +0 -34
  94. package/dist/stitching/useFrameStream.js +0 -234
  95. package/dist/stitching/useThrottledFrameProcessor.d.ts +0 -33
  96. package/dist/stitching/useThrottledFrameProcessor.js +0 -132
  97. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +0 -474
  98. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +0 -1328
  99. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +0 -103
  100. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +0 -3285
  101. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +0 -128
  102. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +0 -313
  103. package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +0 -185
  104. package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +0 -60
  105. package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +0 -214
  106. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +0 -42
  107. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +0 -160
  108. package/src/stitching/IncrementalStitcherView.tsx +0 -198
  109. package/src/stitching/StitcherWorkletRegistry.ts +0 -156
  110. package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +0 -176
  111. package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +0 -94
  112. package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +0 -178
  113. package/src/stitching/ensureStitcherProxyInstalled.ts +0 -141
  114. package/src/stitching/useFrameProcessor.ts +0 -226
  115. package/src/stitching/useFrameStream.ts +0 -271
  116. package/src/stitching/useThrottledFrameProcessor.ts +0 -145
@@ -1,119 +0,0 @@
1
- import { type DependencyList } from 'react';
2
- import { type DrawableFrameProcessor, type ReadonlyFrameProcessor } from 'react-native-vision-camera';
3
- import type { StitcherFrameProcessor } from './StitcherFrame';
4
- /**
5
- * v0.8.0 Phase 4a — public hook for hosts to attach a per-frame
6
- * worklet that runs in BOTH AR and non-AR capture modes.
7
- *
8
- * ## Quick start
9
- *
10
- * ```tsx
11
- * import { useFrameProcessor, type StitcherFrame } from 'react-native-image-stitcher';
12
- *
13
- * function MyOcrOverlay() {
14
- * const processor = useFrameProcessor((frame: StitcherFrame) => {
15
- * 'worklet';
16
- * // Pixel data is in `frame.toArrayBuffer()`.
17
- * // AR-only fields: `frame.arDepth`, `frame.arAnchors`, `frame.arTrackingState`.
18
- * // Discriminate via `frame.source === 'ar'` / `'vc'`.
19
- * }, []);
20
- * return <Camera frameProcessor={processor} ... />;
21
- * }
22
- * ```
23
- *
24
- * ## Two behaviours, depending on mode
25
- *
26
- * **Non-AR mode (today, fully working):** the worklet runs on
27
- * vision-camera's Frame Processor runtime. Same thread + same
28
- * cost envelope as a plain `useFrameProcessor` from
29
- * `react-native-vision-camera`. The lib's own first-party
30
- * stitching plugin runs alongside on the same producer-thread
31
- * runtime (composition is handled by vision-camera's own dispatch
32
- * order).
33
- *
34
- * Your worklet receives whatever vision-camera delivers — vc's raw
35
- * `Frame`. This is a structural subset of `StitcherFrame`: the
36
- * vc-shaped fields (`width`, `height`, `pixelFormat`, `orientation`,
37
- * `timestamp`, `toArrayBuffer`) are guaranteed; the
38
- * `StitcherFrame`-only fields (`source`, `pose`, `arDepth`,
39
- * `arAnchors`, `arTrackingState`) are **undefined** at runtime
40
- * because the lib does NOT wrap or augment vc's `Frame` in Phase 4a
41
- * (cross-worklet-boundary field injection is Phase 4b work).
42
- * Worklets that need to read `source` / `pose` MUST guard for
43
- * `undefined`:
44
- *
45
- * ```ts
46
- * if (frame.source === 'ar') { ... } // false in non-AR mode
47
- * if (frame.pose) { ... } // skipped in non-AR mode
48
- * ```
49
- *
50
- * **AR mode — iOS Phase 4b.i (this release):** the worklet is
51
- * installed into the native registry via
52
- * `globalThis.__stitcherProxy.install(workletFn)`, where
53
- * `__stitcherProxy` is a JSI host object installed at lib
54
- * bootstrap by the native `StitcherJsiInstaller` module. The
55
- * AR worklet runtime (`RNSARWorkletRuntime`) reads from the
56
- * native registry on each `dispatchFrame:pose:` call and fans
57
- * out invocations — your worklet fires alongside the lib's
58
- * first-party stitching path.
59
- *
60
- * **AR mode — Android Phase 4b.ii (deferred):** the native
61
- * installer + JNI bridge from `StitcherWorkletRuntime.kt`'s
62
- * `runFirstParty {...}` path to a parallel C++ registry land in
63
- * a follow-up release. Until then, on Android the hook falls
64
- * back to the JS-side `StitcherWorkletRegistry`; AR-mode host
65
- * worklets register but do not invoke. No regression vs.
66
- * Phase 4a; iOS gets the API first.
67
- *
68
- * ### When Phase 4b.ii lands (Android)
69
- *
70
- * The hook's call signature does NOT change. Android hosts that
71
- * write code today against this API will see their worklets
72
- * start firing in AR mode automatically when Phase 4b.ii is
73
- * merged. No migration required.
74
- *
75
- * ## Frame contract
76
- *
77
- * The worklet receives a {@link StitcherFrame} (see
78
- * `src/stitching/StitcherFrame.ts` for the full contract +
79
- * lifecycle). Highlights:
80
- *
81
- * - **`source`** discriminator: `'vc'` or `'ar'`. Branch on this
82
- * before reading `arDepth` / `arAnchors` / `arTrackingState`
83
- * so non-AR captures don't break.
84
- * - **`pose`** always present. `pose.translation` is `undefined`
85
- * in non-AR mode (gyro provides only rotation; no spatial
86
- * anchor).
87
- * - **Buffer lifetime**: pixel data is valid only for the
88
- * duration of the worklet call. Worklets that need to retain
89
- * data must `toArrayBuffer()` synchronously inside the
90
- * worklet body — returning a reference and reading it later
91
- * reads freed memory.
92
- *
93
- * ## Threading
94
- *
95
- * The worklet runs on the producer thread (vision-camera's
96
- * runtime in non-AR mode; the AR-session callback thread under
97
- * Phase 4b). Worklets MUST NOT block the producer thread for
98
- * more than a few ms — the next frame's processing is gated on
99
- * the previous frame returning. Long work belongs on a queue
100
- * crossed via Reanimated / worklets-core's `runOnJS`.
101
- *
102
- * @param worklet The host's frame processor function. Must be
103
- * `'worklet'`-prefixed at the call site. TS
104
- * cannot enforce the prefix; the runtime will
105
- * throw at attempt to invoke a non-worklet
106
- * function.
107
- * @param deps Standard React deps array. When `deps` change,
108
- * the previous registration is removed and the
109
- * new worklet is registered. Same semantics as
110
- * vision-camera's `useFrameProcessor`.
111
- *
112
- * @returns A vision-camera frame-processor object that
113
- * `<Camera frameProcessor={...}>` accepts. In non-AR
114
- * mode this is what drives the per-frame worklet
115
- * invocation; in AR mode it's currently a no-op (vc
116
- * isn't mounted in AR mode anyway).
117
- */
118
- export declare function useFrameProcessor(worklet: StitcherFrameProcessor, deps: DependencyList): ReadonlyFrameProcessor | DrawableFrameProcessor;
119
- //# sourceMappingURL=useFrameProcessor.d.ts.map
@@ -1,196 +0,0 @@
1
- "use strict";
2
- // SPDX-License-Identifier: Apache-2.0
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.useFrameProcessor = useFrameProcessor;
5
- const react_1 = require("react");
6
- const react_native_vision_camera_1 = require("react-native-vision-camera");
7
- const ensureStitcherProxyInstalled_1 = require("./ensureStitcherProxyInstalled");
8
- const StitcherWorkletRegistry_1 = require("./StitcherWorkletRegistry");
9
- /**
10
- * v0.8.0 Phase 4a — public hook for hosts to attach a per-frame
11
- * worklet that runs in BOTH AR and non-AR capture modes.
12
- *
13
- * ## Quick start
14
- *
15
- * ```tsx
16
- * import { useFrameProcessor, type StitcherFrame } from 'react-native-image-stitcher';
17
- *
18
- * function MyOcrOverlay() {
19
- * const processor = useFrameProcessor((frame: StitcherFrame) => {
20
- * 'worklet';
21
- * // Pixel data is in `frame.toArrayBuffer()`.
22
- * // AR-only fields: `frame.arDepth`, `frame.arAnchors`, `frame.arTrackingState`.
23
- * // Discriminate via `frame.source === 'ar'` / `'vc'`.
24
- * }, []);
25
- * return <Camera frameProcessor={processor} ... />;
26
- * }
27
- * ```
28
- *
29
- * ## Two behaviours, depending on mode
30
- *
31
- * **Non-AR mode (today, fully working):** the worklet runs on
32
- * vision-camera's Frame Processor runtime. Same thread + same
33
- * cost envelope as a plain `useFrameProcessor` from
34
- * `react-native-vision-camera`. The lib's own first-party
35
- * stitching plugin runs alongside on the same producer-thread
36
- * runtime (composition is handled by vision-camera's own dispatch
37
- * order).
38
- *
39
- * Your worklet receives whatever vision-camera delivers — vc's raw
40
- * `Frame`. This is a structural subset of `StitcherFrame`: the
41
- * vc-shaped fields (`width`, `height`, `pixelFormat`, `orientation`,
42
- * `timestamp`, `toArrayBuffer`) are guaranteed; the
43
- * `StitcherFrame`-only fields (`source`, `pose`, `arDepth`,
44
- * `arAnchors`, `arTrackingState`) are **undefined** at runtime
45
- * because the lib does NOT wrap or augment vc's `Frame` in Phase 4a
46
- * (cross-worklet-boundary field injection is Phase 4b work).
47
- * Worklets that need to read `source` / `pose` MUST guard for
48
- * `undefined`:
49
- *
50
- * ```ts
51
- * if (frame.source === 'ar') { ... } // false in non-AR mode
52
- * if (frame.pose) { ... } // skipped in non-AR mode
53
- * ```
54
- *
55
- * **AR mode — iOS Phase 4b.i (this release):** the worklet is
56
- * installed into the native registry via
57
- * `globalThis.__stitcherProxy.install(workletFn)`, where
58
- * `__stitcherProxy` is a JSI host object installed at lib
59
- * bootstrap by the native `StitcherJsiInstaller` module. The
60
- * AR worklet runtime (`RNSARWorkletRuntime`) reads from the
61
- * native registry on each `dispatchFrame:pose:` call and fans
62
- * out invocations — your worklet fires alongside the lib's
63
- * first-party stitching path.
64
- *
65
- * **AR mode — Android Phase 4b.ii (deferred):** the native
66
- * installer + JNI bridge from `StitcherWorkletRuntime.kt`'s
67
- * `runFirstParty {...}` path to a parallel C++ registry land in
68
- * a follow-up release. Until then, on Android the hook falls
69
- * back to the JS-side `StitcherWorkletRegistry`; AR-mode host
70
- * worklets register but do not invoke. No regression vs.
71
- * Phase 4a; iOS gets the API first.
72
- *
73
- * ### When Phase 4b.ii lands (Android)
74
- *
75
- * The hook's call signature does NOT change. Android hosts that
76
- * write code today against this API will see their worklets
77
- * start firing in AR mode automatically when Phase 4b.ii is
78
- * merged. No migration required.
79
- *
80
- * ## Frame contract
81
- *
82
- * The worklet receives a {@link StitcherFrame} (see
83
- * `src/stitching/StitcherFrame.ts` for the full contract +
84
- * lifecycle). Highlights:
85
- *
86
- * - **`source`** discriminator: `'vc'` or `'ar'`. Branch on this
87
- * before reading `arDepth` / `arAnchors` / `arTrackingState`
88
- * so non-AR captures don't break.
89
- * - **`pose`** always present. `pose.translation` is `undefined`
90
- * in non-AR mode (gyro provides only rotation; no spatial
91
- * anchor).
92
- * - **Buffer lifetime**: pixel data is valid only for the
93
- * duration of the worklet call. Worklets that need to retain
94
- * data must `toArrayBuffer()` synchronously inside the
95
- * worklet body — returning a reference and reading it later
96
- * reads freed memory.
97
- *
98
- * ## Threading
99
- *
100
- * The worklet runs on the producer thread (vision-camera's
101
- * runtime in non-AR mode; the AR-session callback thread under
102
- * Phase 4b). Worklets MUST NOT block the producer thread for
103
- * more than a few ms — the next frame's processing is gated on
104
- * the previous frame returning. Long work belongs on a queue
105
- * crossed via Reanimated / worklets-core's `runOnJS`.
106
- *
107
- * @param worklet The host's frame processor function. Must be
108
- * `'worklet'`-prefixed at the call site. TS
109
- * cannot enforce the prefix; the runtime will
110
- * throw at attempt to invoke a non-worklet
111
- * function.
112
- * @param deps Standard React deps array. When `deps` change,
113
- * the previous registration is removed and the
114
- * new worklet is registered. Same semantics as
115
- * vision-camera's `useFrameProcessor`.
116
- *
117
- * @returns A vision-camera frame-processor object that
118
- * `<Camera frameProcessor={...}>` accepts. In non-AR
119
- * mode this is what drives the per-frame worklet
120
- * invocation; in AR mode it's currently a no-op (vc
121
- * isn't mounted in AR mode anyway).
122
- */
123
- function useFrameProcessor(worklet, deps) {
124
- // Non-AR path: delegate to vision-camera's hook. The returned
125
- // processor object is what `<Camera>` hands to vc. Worklet
126
- // fires on vc's producer-thread runtime.
127
- //
128
- // Cast rationale: vc's hook expects `(frame: Frame) => void`.
129
- // Our worklet is typed `(frame: StitcherFrame) => void`.
130
- // `StitcherFrame` is a structural superset of `Frame` (it adds
131
- // required `source` + `pose` and the optional AR fields), so
132
- // assigning a function that consumes `StitcherFrame` to a
133
- // `Frame`-consuming slot is unsound at the type level — TS is
134
- // right to reject the direct assignment. At RUNTIME the worklet
135
- // will see vc's raw `Frame`; the `source` / `pose` / AR fields
136
- // are undefined (the hook's docstring above documents this and
137
- // tells hosts to guard). We double-cast through `unknown` to
138
- // suppress, accepting the explicit type-system gap as the price
139
- // of Phase 4a's pre-Phase-4b deferral on cross-runtime frame
140
- // wrapping.
141
- const vcProcessor = (0, react_native_vision_camera_1.useFrameProcessor)(worklet, deps);
142
- // AR path: install into the native registry if available (iOS
143
- // Phase 4b.i — and Android Phase 4b.ii once it lands). Falls
144
- // back to the JS-side `StitcherWorkletRegistry` when the native
145
- // installer isn't present (Android in 4b.i; remote debug mode;
146
- // unit tests). The fallback path matches Phase 4a's
147
- // register-but-not-invoke semantics.
148
- //
149
- // eslint-disable-next-line react-hooks/exhaustive-deps
150
- (0, react_1.useEffect)(() => {
151
- const nativeReady = (0, ensureStitcherProxyInstalled_1.ensureStitcherProxyInstalled)();
152
- if (nativeReady &&
153
- typeof globalThis.__stitcherProxy !== 'undefined') {
154
- // Native path — install through the JSI proxy. Errors here
155
- // most commonly mean the worklet doesn't have the
156
- // `'worklet'` directive at the call site (the worklets-core
157
- // babel plugin didn't transform it). Surface them via the
158
- // proxy's own throw with a host-side log so the failure is
159
- // obvious.
160
- let id;
161
- try {
162
- id = globalThis.__stitcherProxy.install(worklet);
163
- }
164
- catch (err) {
165
- // Guard `__DEV__` read so the hook works in any environment
166
- // that imports it without defining the flag (jest, SSR,
167
- // custom tooling).
168
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
169
- console.error('[react-native-image-stitcher] __stitcherProxy.install ' +
170
- 'threw — is the worklet function decorated with ' +
171
- "`'worklet';` and processed by react-native-worklets-core's " +
172
- 'babel plugin? Original error: ' +
173
- String(err));
174
- }
175
- return; // No cleanup needed — nothing was installed.
176
- }
177
- return () => {
178
- try {
179
- globalThis.__stitcherProxy.uninstall(id);
180
- }
181
- catch {
182
- // Uninstall is best-effort; an exception here means the
183
- // proxy was already gone (e.g., app reload mid-cleanup).
184
- }
185
- };
186
- }
187
- // Fallback — JS-side registry. Same as Phase 4a.
188
- const jsId = StitcherWorkletRegistry_1.StitcherWorkletRegistry.register({
189
- worklet,
190
- isFirstParty: false,
191
- });
192
- return () => StitcherWorkletRegistry_1.StitcherWorkletRegistry.unregister(jsId);
193
- }, deps);
194
- return vcProcessor;
195
- }
196
- //# sourceMappingURL=useFrameProcessor.js.map
@@ -1,34 +0,0 @@
1
- import { useThrottledFrameProcessor } from './useThrottledFrameProcessor';
2
- import type { FrameStreamOptions, SampledFrame } from '../types';
3
- /**
4
- * `useFrameStream` — Layer 3. See module docstring for the full
5
- * design + use-case mapping. Quick start:
6
- *
7
- * ```tsx
8
- * import { Camera, useFrameStream } from 'react-native-image-stitcher';
9
- *
10
- * function MyScreen() {
11
- * const fp = useFrameStream(
12
- * { sampleHz: 2, quality: 75 },
13
- * (sample) => {
14
- * setThumbnail(sample.jpegPath);
15
- * },
16
- * );
17
- * return <Camera frameProcessor={fp} ... />;
18
- * }
19
- * ```
20
- *
21
- * @param options `{ sampleHz, quality?, outputDir? }`. `sampleHz`
22
- * clamped to `[0.5, 10]`.
23
- * @param handler JS-thread callback fired per sample. Receives a
24
- * `SampledFrame`. May return a Promise; rejections
25
- * are caught + logged (not re-thrown) so one
26
- * misbehaving handler doesn't break the stream.
27
- *
28
- * @returns A `useFrameProcessor`-shaped processor object — pass to
29
- * `<Camera frameProcessor={...}>` for non-AR mode wiring.
30
- * (AR mode auto-registration via `__stitcherProxy` is
31
- * handled inside `useFrameProcessor`.)
32
- */
33
- export declare function useFrameStream(options: FrameStreamOptions, handler: (sample: SampledFrame) => void | Promise<void>): ReturnType<typeof useThrottledFrameProcessor>;
34
- //# sourceMappingURL=useFrameStream.d.ts.map
@@ -1,234 +0,0 @@
1
- "use strict";
2
- // SPDX-License-Identifier: Apache-2.0
3
- //
4
- // v0.9.0 Layer 3 — JS-thread sampled-frame stream over Layer 1 +
5
- // Layer 2.
6
- //
7
- // ## What this is
8
- //
9
- // A hook that:
10
- // 1. Throttles a worklet via `useThrottledFrameProcessor` (Layer 2)
11
- // to fire at `sampleHz` Hz.
12
- // 2. Inside the worklet, calls the `save_frame_as_jpeg` vc Frame
13
- // Processor plugin (Layer 1) to JPEG-encode the frame to a
14
- // bounded-rotation slot on disk.
15
- // 3. Bridges the resulting `SampledFrame` (file path + pose +
16
- // dims) to a JS-thread callback via `runOnJS`.
17
- //
18
- // The host gets a per-sample callback on the JS thread with a file
19
- // path they can pass to `<Image>`, an OCR RN module, a cloud-upload
20
- // library, etc. Zero worklet boilerplate.
21
- //
22
- // ## When to use this (vs alternatives)
23
- //
24
- // - **`useFrameStream`** (this hook) — JS-thread consumers. File-
25
- // path OCR libraries, cloud upload, thumbnail UI, sampled
26
- // server-side analysis.
27
- // - **`useThrottledFrameProcessor`** (Layer 2) — worklet-native
28
- // consumers. Native OCR (Vision.framework / ML Kit) wrapped as
29
- // vc plugins, TFLite ML inference, LiDAR depth processing.
30
- // Lower latency; no JPEG roundtrip.
31
- // - **`useFrameProcessor`** — every camera frame; full control.
32
- //
33
- // ## Slot reuse / disk usage
34
- //
35
- // JPEG files are written to `<outputDir>/stream-<N>.jpg` where N
36
- // cycles 0..3 based on `frame.timestamp / 1000`. At most 4 stale
37
- // JPEGs ever exist on disk; the same file is rewritten on each
38
- // rotation, so disk usage is bounded.
39
- //
40
- // Hosts that need long-term retention (e.g., archive each sample
41
- // for later upload) MUST copy the file synchronously inside the
42
- // handler — the slot may be overwritten by the next sample.
43
- //
44
- // ## Backpressure
45
- //
46
- // If the JS handler returns slower than `1/sampleHz`, subsequent
47
- // ticks DO still fire (the throttle is time-based, not handler-
48
- // completion-based). This means multiple handler invocations can
49
- // be in flight simultaneously. For most use cases that's fine
50
- // (the handlers are pure or commute). Hosts that need serialised
51
- // handling should track in-flight state themselves and early-return.
52
- //
53
- // ## AR vs non-AR
54
- //
55
- // Works in both modes because it composes over
56
- // `useThrottledFrameProcessor` → `useFrameProcessor`. In AR mode
57
- // the worklet auto-registers via `__stitcherProxy` (v0.8.0 Phase
58
- // 4b.i/iii); in non-AR mode the returned processor object is
59
- // passed to `<Camera frameProcessor={...}>`. The hook returns
60
- // the processor object so hosts can wire it up either way.
61
- Object.defineProperty(exports, "__esModule", { value: true });
62
- exports.useFrameStream = useFrameStream;
63
- const react_1 = require("react");
64
- const react_native_vision_camera_1 = require("react-native-vision-camera");
65
- const react_native_worklets_core_1 = require("react-native-worklets-core");
66
- const useThrottledFrameProcessor_1 = require("./useThrottledFrameProcessor");
67
- const files_1 = require("../utils/files");
68
- /**
69
- * `useFrameStream` — Layer 3. See module docstring for the full
70
- * design + use-case mapping. Quick start:
71
- *
72
- * ```tsx
73
- * import { Camera, useFrameStream } from 'react-native-image-stitcher';
74
- *
75
- * function MyScreen() {
76
- * const fp = useFrameStream(
77
- * { sampleHz: 2, quality: 75 },
78
- * (sample) => {
79
- * setThumbnail(sample.jpegPath);
80
- * },
81
- * );
82
- * return <Camera frameProcessor={fp} ... />;
83
- * }
84
- * ```
85
- *
86
- * @param options `{ sampleHz, quality?, outputDir? }`. `sampleHz`
87
- * clamped to `[0.5, 10]`.
88
- * @param handler JS-thread callback fired per sample. Receives a
89
- * `SampledFrame`. May return a Promise; rejections
90
- * are caught + logged (not re-thrown) so one
91
- * misbehaving handler doesn't break the stream.
92
- *
93
- * @returns A `useFrameProcessor`-shaped processor object — pass to
94
- * `<Camera frameProcessor={...}>` for non-AR mode wiring.
95
- * (AR mode auto-registration via `__stitcherProxy` is
96
- * handled inside `useFrameProcessor`.)
97
- */
98
- function useFrameStream(options, handler) {
99
- const sampleHz = Math.max(0.5, Math.min(10, options.sampleHz));
100
- const quality = options.quality ?? 75;
101
- // Default output dir: the lib's canonical capture dir resolved
102
- // via `FileBridge.defaultCaptureDir()`. Same dir the lib uses
103
- // for panorama JPEGs / keyframe JPEGs — guaranteed writable on
104
- // both platforms (iOS NSCachesDirectory + Android Context.cacheDir),
105
- // created if missing. Resolved async on first mount; until
106
- // resolution completes the worklet's `outputDir` is empty and
107
- // the plugin call no-ops silently (a few frames missed at most;
108
- // typical resolution time is <50ms).
109
- //
110
- // Hosts that want a specific path supply `options.outputDir`
111
- // and skip the resolution entirely.
112
- const [resolvedDefaultDir, setResolvedDefaultDir] = (0, react_1.useState)('');
113
- (0, react_1.useEffect)(() => {
114
- if (options.outputDir != null)
115
- return;
116
- let cancelled = false;
117
- (0, files_1.getDefaultCaptureDir)()
118
- .then((dir) => {
119
- if (!cancelled)
120
- setResolvedDefaultDir(dir);
121
- })
122
- .catch((err) => {
123
- // eslint-disable-next-line no-console
124
- console.warn('[useFrameStream] FileBridge.defaultCaptureDir() failed; ' +
125
- 'samples will not fire until `options.outputDir` is supplied. ' +
126
- String(err));
127
- });
128
- return () => {
129
- cancelled = true;
130
- };
131
- }, [options.outputDir]);
132
- const outputDir = options.outputDir ?? resolvedDefaultDir;
133
- // Stable JS-side handler reference for `runOnJS`. The hook re-
134
- // captures `handler` on every render but the ref keeps the
135
- // worklet closure pointing at the latest callback (avoid stale
136
- // captures).
137
- const handlerRef = (0, react_1.useRef)(handler);
138
- handlerRef.current = handler;
139
- const onSampleJS = (0, react_1.useCallback)((sample) => {
140
- const result = handlerRef.current(sample);
141
- if (result != null &&
142
- typeof result.catch === 'function') {
143
- result.catch((err) => {
144
- // eslint-disable-next-line no-console
145
- console.error('[useFrameStream] handler threw:', err);
146
- });
147
- }
148
- }, []);
149
- const onSampleOnJS = (0, react_1.useMemo)(() => react_native_worklets_core_1.Worklets.createRunOnJS(onSampleJS), [onSampleJS]);
150
- // ── Plugin acquisition (Layer 1) ─────────────────────────────────
151
- //
152
- // `initFrameProcessorPlugin` can return `undefined` if the native
153
- // registry hasn't initialised yet (rare race on app start). We
154
- // retry every 16ms (one display frame) until success — matches
155
- // the pattern in `useFrameProcessorDriver`.
156
- //
157
- // Use `useState` (not `useRef`) so the eventual non-null value
158
- // triggers a re-render — the worklet closure below captures
159
- // `plugin` by value at render time, so without state we'd
160
- // capture `null` forever.
161
- const [plugin, setPlugin] = (0, react_1.useState)(null);
162
- (0, react_1.useEffect)(() => {
163
- let cancelled = false;
164
- let timerId = null;
165
- let attempts = 0;
166
- const tryAcquire = () => {
167
- if (cancelled)
168
- return;
169
- attempts += 1;
170
- const p = react_native_vision_camera_1.VisionCameraProxy.initFrameProcessorPlugin('save_frame_as_jpeg', {});
171
- if (p != null) {
172
- setPlugin(p);
173
- return;
174
- }
175
- // After ~1s of failed retries, warn once — the plugin should
176
- // be registered by then; persistent failure means the host's
177
- // native bundle doesn't include `save_frame_as_jpeg`.
178
- if (attempts === 60) {
179
- // eslint-disable-next-line no-console
180
- console.warn('[useFrameStream] save_frame_as_jpeg plugin not found after 1s of retries. ' +
181
- 'Verify react-native-image-stitcher native module is installed in your host app.');
182
- }
183
- timerId = setTimeout(tryAcquire, 16);
184
- };
185
- tryAcquire();
186
- return () => {
187
- cancelled = true;
188
- if (timerId != null)
189
- clearTimeout(timerId);
190
- };
191
- }, []);
192
- return (0, useThrottledFrameProcessor_1.useThrottledFrameProcessor)((frame) => {
193
- 'worklet';
194
- if (plugin == null)
195
- return;
196
- // Async outputDir resolution may not have completed yet on
197
- // the first few frames after mount — bail until it does.
198
- if (outputDir === '')
199
- return;
200
- // Slot rotation: compute slot from frame timestamp. At
201
- // sampleHz=2 (500ms interval), the slot index changes every
202
- // ~1s, giving each slot ~2 samples before being overwritten.
203
- // That's overkill for the "stream-of-samples" use case but
204
- // matches the docstring's "at most 4 stale JPEGs" guarantee.
205
- const slot = Math.floor(frame.timestamp / 1000) % 4;
206
- const path = `${outputDir}/stream-${slot}.jpg`;
207
- // vc's `FrameProcessorPlugin.call` expects vc's `Frame` type.
208
- // `StitcherFrame` is structurally a superset (it adds `source`,
209
- // `pose`, AR-only fields). Cast through `unknown` — same
210
- // pattern v0.8.0's `useFrameProcessor` uses when handing a
211
- // StitcherFrame-typed worklet to vc.
212
- const result = plugin.call(frame, {
213
- path,
214
- quality,
215
- });
216
- if (result == null ||
217
- result.ok !== true) {
218
- // Native side reported an error (path not writable, format
219
- // wrong, etc.). Silently skip this sample — the next tick
220
- // will retry. The plugin already logs the specific reason
221
- // on the native side.
222
- return;
223
- }
224
- const r = result;
225
- onSampleOnJS({
226
- jpegPath: r.path,
227
- pose: frame.pose,
228
- timestamp: frame.timestamp,
229
- width: r.width,
230
- height: r.height,
231
- });
232
- }, { sampleHz }, [plugin, outputDir, quality, onSampleOnJS]);
233
- }
234
- //# sourceMappingURL=useFrameStream.js.map
@@ -1,33 +0,0 @@
1
- import type { DependencyList } from 'react';
2
- import { useFrameProcessor } from './useFrameProcessor';
3
- import type { StitcherFrameProcessor } from './StitcherFrame';
4
- import type { ThrottledFrameProcessorOptions } from '../types';
5
- /**
6
- * Throttled variant of `useFrameProcessor`. See the module
7
- * docstring for the full use-case mapping; quick version:
8
- *
9
- * ```tsx
10
- * const fp = useThrottledFrameProcessor(
11
- * (frame) => {
12
- * 'worklet';
13
- * // worklet-native OCR / ML / depth processing here
14
- * },
15
- * { sampleHz: 2 },
16
- * [],
17
- * );
18
- * return <Camera frameProcessor={fp} ... />;
19
- * ```
20
- *
21
- * @param worklet Host's frame-processor worklet. Must be
22
- * `'worklet'`-prefixed. Runs at most `sampleHz`
23
- * times per second.
24
- * @param options `{ sampleHz }` — clamped to `[0.5, 30]`.
25
- * @param deps Standard React deps array. Treated the same as
26
- * `useFrameProcessor`'s deps — when they change the
27
- * inner worklet is re-bound.
28
- *
29
- * @returns A `useFrameProcessor`-shaped processor object, pass it
30
- * to `<Camera frameProcessor={...}>`.
31
- */
32
- export declare function useThrottledFrameProcessor(worklet: StitcherFrameProcessor, options: ThrottledFrameProcessorOptions, deps: DependencyList): ReturnType<typeof useFrameProcessor>;
33
- //# sourceMappingURL=useThrottledFrameProcessor.d.ts.map