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.
- package/CHANGELOG.md +131 -0
- package/README.md +35 -0
- package/RNImageStitcher.podspec +8 -7
- package/android/build.gradle +0 -16
- package/android/src/main/cpp/CMakeLists.txt +2 -63
- package/android/src/main/cpp/image_stitcher_jni.cpp +14 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +13 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +285 -3
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +180 -1162
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +29 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +0 -4
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +13 -64
- package/cpp/keyframe_gate.cpp +82 -23
- package/cpp/keyframe_gate.hpp +31 -2
- package/cpp/stitcher.cpp +208 -28
- package/cpp/tests/CMakeLists.txt +18 -12
- package/cpp/tests/keyframe_timebudget_test.cpp +65 -0
- package/cpp/tests/warp_guard_test.cpp +48 -0
- package/cpp/warp_guard.hpp +41 -0
- package/dist/camera/Camera.d.ts +31 -16
- package/dist/camera/Camera.js +10 -2
- package/dist/camera/CaptureStitchStatsToast.d.ts +15 -2
- package/dist/camera/CaptureStitchStatsToast.js +27 -7
- package/dist/camera/PanoramaSettings.d.ts +10 -223
- package/dist/camera/PanoramaSettings.js +6 -28
- package/dist/camera/PanoramaSettingsBridge.d.ts +1 -24
- package/dist/camera/PanoramaSettingsBridge.js +3 -102
- package/dist/camera/PanoramaSettingsModal.js +7 -1
- package/dist/camera/buildPanoramaInitialSettings.d.ts +11 -0
- package/dist/camera/buildPanoramaInitialSettings.js +4 -0
- package/dist/camera/cameraErrorMessages.d.ts +32 -0
- package/dist/camera/cameraErrorMessages.js +53 -0
- package/dist/camera/selectCaptureDevice.d.ts +5 -1
- package/dist/camera/selectCaptureDevice.js +22 -2
- package/dist/camera/useCapture.js +38 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +11 -34
- package/dist/stitching/incremental.d.ts +1 -117
- package/dist/stitching/stitchVideo.d.ts +0 -35
- package/dist/types.d.ts +0 -87
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +96 -674
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +9 -12
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +14 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +7 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +6 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +2 -2
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +3 -3
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +28 -60
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +180 -921
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +10 -35
- package/ios/Sources/RNImageStitcher/Stitcher.swift +84 -35
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +13 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +132 -5
- package/package.json +3 -2
- package/src/camera/Camera.tsx +43 -22
- package/src/camera/CaptureStitchStatsToast.tsx +58 -14
- package/src/camera/PanoramaSettings.ts +16 -289
- package/src/camera/PanoramaSettingsBridge.ts +3 -114
- package/src/camera/PanoramaSettingsModal.tsx +14 -1
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +3 -188
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +41 -0
- package/src/camera/__tests__/cameraErrorMessages.test.ts +76 -0
- package/src/camera/__tests__/selectCaptureDevice.test.ts +33 -0
- package/src/camera/buildPanoramaInitialSettings.ts +17 -0
- package/src/camera/cameraErrorMessages.ts +84 -0
- package/src/camera/selectCaptureDevice.ts +28 -3
- package/src/camera/useCapture.ts +44 -1
- package/src/index.ts +11 -40
- package/src/stitching/incremental.ts +3 -140
- package/src/stitching/stitchVideo.ts +0 -26
- package/src/types.ts +0 -95
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +0 -227
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +0 -1081
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +0 -103
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +0 -256
- package/cpp/stitcher_frame_jsi.cpp +0 -214
- package/cpp/stitcher_frame_jsi.hpp +0 -108
- package/cpp/stitcher_proxy_jsi.cpp +0 -109
- package/cpp/stitcher_proxy_jsi.hpp +0 -46
- package/cpp/stitcher_worklet_dispatch.cpp +0 -103
- package/cpp/stitcher_worklet_dispatch.hpp +0 -71
- package/cpp/stitcher_worklet_registry.cpp +0 -91
- package/cpp/stitcher_worklet_registry.hpp +0 -146
- package/cpp/tests/stitcher_worklet_registry_test.cpp +0 -195
- package/dist/stitching/IncrementalStitcherView.d.ts +0 -41
- package/dist/stitching/IncrementalStitcherView.js +0 -157
- package/dist/stitching/StitcherWorkletRegistry.d.ts +0 -117
- package/dist/stitching/StitcherWorkletRegistry.js +0 -78
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +0 -8
- package/dist/stitching/ensureStitcherProxyInstalled.js +0 -81
- package/dist/stitching/useFrameProcessor.d.ts +0 -119
- package/dist/stitching/useFrameProcessor.js +0 -196
- package/dist/stitching/useFrameStream.d.ts +0 -34
- package/dist/stitching/useFrameStream.js +0 -234
- package/dist/stitching/useThrottledFrameProcessor.d.ts +0 -33
- package/dist/stitching/useThrottledFrameProcessor.js +0 -132
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +0 -474
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +0 -1328
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +0 -103
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +0 -3285
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +0 -128
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +0 -313
- package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +0 -185
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +0 -60
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +0 -214
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +0 -42
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +0 -160
- package/src/stitching/IncrementalStitcherView.tsx +0 -198
- package/src/stitching/StitcherWorkletRegistry.ts +0 -156
- package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +0 -176
- package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +0 -94
- package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +0 -178
- package/src/stitching/ensureStitcherProxyInstalled.ts +0 -141
- package/src/stitching/useFrameProcessor.ts +0 -226
- package/src/stitching/useFrameStream.ts +0 -271
- 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
|