react-native-image-stitcher 0.18.0 → 0.19.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 +30 -0
- package/android/src/main/java/io/imagestitcher/rn/ARFrameContext.kt +89 -0
- package/android/src/main/java/io/imagestitcher/rn/ARFramePlugin.kt +57 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +183 -14
- package/android/src/main/java/io/imagestitcher/rn/RNSARPluginRegistry.kt +109 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +28 -0
- package/dist/camera/ARCameraView.d.ts +22 -1
- package/dist/camera/ARCameraView.js +36 -1
- package/dist/camera/Camera.d.ts +20 -1
- package/dist/camera/Camera.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/stitching/ARFrameMeta.d.ts +49 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +36 -1
- package/ios/Sources/RNImageStitcher/CameraFrameHostObject.h +25 -0
- package/ios/Sources/RNImageStitcher/CameraFrameHostObject.mm +66 -54
- package/ios/Sources/RNImageStitcher/RNISARFramePlugin.swift +247 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +127 -1
- package/package.json +1 -1
- package/src/camera/ARCameraView.tsx +66 -1
- package/src/camera/Camera.tsx +23 -1
- package/src/index.ts +6 -1
- package/src/stitching/ARFrameMeta.ts +50 -0
|
@@ -492,6 +492,24 @@ public final class RNSARSession: NSObject, ARSessionDelegate {
|
|
|
492
492
|
private var lastArFrameMetaEmit: TimeInterval = 0
|
|
493
493
|
private let arFrameMetaLock = NSLock()
|
|
494
494
|
|
|
495
|
+
// ──────────────────────────────────────────────────────────────
|
|
496
|
+
// v0.19.0 — native AR plugin framework (RNISARPluginRegistry)
|
|
497
|
+
// ──────────────────────────────────────────────────────────────
|
|
498
|
+
//
|
|
499
|
+
// While the plugin registry is NON-EMPTY, the per-frame path builds a
|
|
500
|
+
// `RNISARFrameContext` once and calls each registered plugin's
|
|
501
|
+
// `process(_:)` on the AR (delegate) thread (see `invokeArPlugins`).
|
|
502
|
+
// Non-nil SYNC results are cached here so the throttled `onArFrame`
|
|
503
|
+
// meta build can fold them in under `plugins: { [name]: result }`
|
|
504
|
+
// without re-running plugins. Zero-plugin apps skip the whole path
|
|
505
|
+
// (the registry's `isEmpty` gate), so they pay nothing.
|
|
506
|
+
//
|
|
507
|
+
// Written on the AR thread (per-frame) and read on the same thread
|
|
508
|
+
// (the meta build runs inline in `session(_:didUpdate:)`), but guarded
|
|
509
|
+
// anyway for defensiveness against any future off-thread reader.
|
|
510
|
+
private var latestPluginSyncResults: [String: Any] = [:]
|
|
511
|
+
private let pluginSyncResultsLock = NSLock()
|
|
512
|
+
|
|
495
513
|
private override init() {
|
|
496
514
|
super.init()
|
|
497
515
|
arSession.delegate = self
|
|
@@ -594,6 +612,12 @@ public final class RNSARSession: NSObject, ARSessionDelegate {
|
|
|
594
612
|
detectedPlaneTransformInternal = nil
|
|
595
613
|
bestRejectedAlignment = -1.0
|
|
596
614
|
planeLatchLock.unlock()
|
|
615
|
+
// v0.19.0 — drop any cached SYNC plugin results so the next
|
|
616
|
+
// capture's `onArFrame` meta doesn't surface stale plugin output
|
|
617
|
+
// before the first frame of the new session runs the plugins.
|
|
618
|
+
pluginSyncResultsLock.lock()
|
|
619
|
+
latestPluginSyncResults = [:]
|
|
620
|
+
pluginSyncResultsLock.unlock()
|
|
597
621
|
}
|
|
598
622
|
|
|
599
623
|
/// Build the `ARWorldTrackingConfiguration` shared by `start()` and
|
|
@@ -846,6 +870,16 @@ public final class RNSARSession: NSObject, ARSessionDelegate {
|
|
|
846
870
|
// double-consuming would ingest each frame twice.
|
|
847
871
|
RNSARWorkletRuntime.shared().dispatchFrame(frame, pose: pose)
|
|
848
872
|
|
|
873
|
+
// v0.19.0 — native AR plugin framework. When the registry is
|
|
874
|
+
// non-empty, build the per-frame `RNISARFrameContext` once and run
|
|
875
|
+
// every registered plugin's `process(_:)` SYNCHRONOUSLY on this AR
|
|
876
|
+
// thread (so the live pixel/depth buffers are valid for the call).
|
|
877
|
+
// Caches non-nil SYNC results for the meta build below. Cheap
|
|
878
|
+
// no-op when no plugins are registered (the common case). Runs
|
|
879
|
+
// BEFORE `maybeEmitArFrameMeta` so the throttled `onArFrame` meta
|
|
880
|
+
// can fold in this frame's freshest plugin results.
|
|
881
|
+
invokeArPlugins(frame, pose: pose)
|
|
882
|
+
|
|
849
883
|
// v0.18.0 — `onArFrame` LIGHT-metadata channel. Gated +
|
|
850
884
|
// throttled; builds the ARFrameMeta dictionary and posts it for
|
|
851
885
|
// the bridge to re-emit. Cheap no-op when disabled (the common
|
|
@@ -1433,13 +1467,105 @@ public final class RNSARSession: NSObject, ARSessionDelegate {
|
|
|
1433
1467
|
// Obj-C++ extraction helpers + the shared C++ extraction-config
|
|
1434
1468
|
// gating (depth/anchors/mesh ⇐ enableDepth/enableAnchors/enableMesh).
|
|
1435
1469
|
let meta = CameraFrameHostObject.lightArFrameMeta(from: frame, pose: pose)
|
|
1470
|
+
|
|
1471
|
+
// v0.19.0 — fold in any SYNC plugin results captured by
|
|
1472
|
+
// `invokeArPlugins` for the freshest frames. Only attach the
|
|
1473
|
+
// `plugins` key when there's at least one result, so the common
|
|
1474
|
+
// (no-plugin) meta shape is unchanged. Snapshot under the lock,
|
|
1475
|
+
// then bridge into a fresh dictionary copy.
|
|
1476
|
+
pluginSyncResultsLock.lock()
|
|
1477
|
+
let pluginResults = latestPluginSyncResults
|
|
1478
|
+
pluginSyncResultsLock.unlock()
|
|
1479
|
+
let userInfo: [AnyHashable: Any]
|
|
1480
|
+
if pluginResults.isEmpty {
|
|
1481
|
+
userInfo = meta
|
|
1482
|
+
} else {
|
|
1483
|
+
var withPlugins = meta
|
|
1484
|
+
withPlugins["plugins"] = pluginResults
|
|
1485
|
+
userInfo = withPlugins
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1436
1488
|
NotificationCenter.default.post(
|
|
1437
1489
|
name: .retailensARFrameMeta,
|
|
1438
1490
|
object: nil,
|
|
1439
|
-
userInfo:
|
|
1491
|
+
userInfo: userInfo
|
|
1440
1492
|
)
|
|
1441
1493
|
}
|
|
1442
1494
|
|
|
1495
|
+
/// v0.19.0 — run all registered native AR plugins for this frame.
|
|
1496
|
+
/// Gated on the registry being NON-EMPTY (the cheap `isEmpty` check) so
|
|
1497
|
+
/// zero-plugin apps skip the context build entirely. When plugins are
|
|
1498
|
+
/// present, builds ONE `RNISARFrameContext` (zero-copy view of the
|
|
1499
|
+
/// frame's live buffers + the already-built anchor dicts) and calls
|
|
1500
|
+
/// each plugin's `process(_:)` SYNCHRONOUSLY on this AR (delegate)
|
|
1501
|
+
/// thread — so the live `pixelBuffer` / `depthBuffer` are valid for the
|
|
1502
|
+
/// call (the plugin must copy before offloading; see the protocol
|
|
1503
|
+
/// docstring). Non-nil SYNC results are cached in
|
|
1504
|
+
/// `latestPluginSyncResults` for the throttled `onArFrame` meta to fold
|
|
1505
|
+
/// in; ASYNC results arrive later via `RNISARPluginRegistry.emit`.
|
|
1506
|
+
private func invokeArPlugins(_ frame: ARFrame, pose: RNSARFramePose) {
|
|
1507
|
+
let registry = RNISARPluginRegistry.shared
|
|
1508
|
+
guard !registry.isEmpty else { return }
|
|
1509
|
+
let plugins = registry.plugins()
|
|
1510
|
+
guard !plugins.isEmpty else { return }
|
|
1511
|
+
|
|
1512
|
+
// depthBuffer: expose the live sceneDepth map ONLY when the
|
|
1513
|
+
// `<Camera enableDepth>` prop is on (gating read in Obj-C++ so the
|
|
1514
|
+
// C++ extraction-config header stays out of Swift). Prefer
|
|
1515
|
+
// `sceneDepth`, fall back to `smoothedSceneDepth` — same precedence
|
|
1516
|
+
// as the full extraction path.
|
|
1517
|
+
var depthBuffer: CVPixelBuffer? = nil
|
|
1518
|
+
if CameraFrameHostObject.arExtractionDepthEnabled() {
|
|
1519
|
+
if let dd = frame.sceneDepth ?? frame.smoothedSceneDepth {
|
|
1520
|
+
depthBuffer = dd.depthMap
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// anchors: reuse the EXACT light dicts the `onArFrame` meta builds
|
|
1525
|
+
// (gated on `enableAnchors`; empty otherwise) — DRY single source.
|
|
1526
|
+
let anchorDicts = CameraFrameHostObject.arAnchorDicts(from: frame)
|
|
1527
|
+
let anchors = anchorDicts as? [[String: Any]] ?? []
|
|
1528
|
+
|
|
1529
|
+
let context = RNISARFrameContext(
|
|
1530
|
+
pixelBuffer: frame.capturedImage,
|
|
1531
|
+
timestampNs: frame.timestamp * 1e9,
|
|
1532
|
+
fx: pose.fx, fy: pose.fy, cx: pose.cx, cy: pose.cy,
|
|
1533
|
+
imageWidth: pose.imageWidth, imageHeight: pose.imageHeight,
|
|
1534
|
+
poseRotation: [pose.qx, pose.qy, pose.qz, pose.qw],
|
|
1535
|
+
poseTranslation: [pose.tx, pose.ty, pose.tz],
|
|
1536
|
+
trackingState: Self.trackingStateString(pose.trackingState),
|
|
1537
|
+
depthBuffer: depthBuffer,
|
|
1538
|
+
anchors: anchors
|
|
1539
|
+
)
|
|
1540
|
+
|
|
1541
|
+
var syncResults: [String: Any] = [:]
|
|
1542
|
+
for plugin in plugins {
|
|
1543
|
+
// Defensive: a plugin throwing/crashing in `process` would take
|
|
1544
|
+
// down the AR thread, but Swift has no try/catch for non-Error
|
|
1545
|
+
// crashes — the contract is that plugins are well-behaved. We
|
|
1546
|
+
// simply collect non-nil results keyed by the plugin's name.
|
|
1547
|
+
if let result = plugin.process(context) {
|
|
1548
|
+
syncResults[plugin.name()] = result
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
pluginSyncResultsLock.lock()
|
|
1553
|
+
latestPluginSyncResults = syncResults
|
|
1554
|
+
pluginSyncResultsLock.unlock()
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/// Map the SDK's `RNSARTrackingState` to the same string the
|
|
1558
|
+
/// `onArFrame` meta + `CameraFrame.trackingState` use, so plugins see a
|
|
1559
|
+
/// consistent vocabulary.
|
|
1560
|
+
private static func trackingStateString(_ s: RNSARTrackingState) -> String {
|
|
1561
|
+
switch s {
|
|
1562
|
+
case .tracking: return "normal"
|
|
1563
|
+
case .limited: return "limited"
|
|
1564
|
+
case .initialising: return "limited"
|
|
1565
|
+
case .notAvailable: return "notAvailable"
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1443
1569
|
private func makePose(from frame: ARFrame) -> RNSARFramePose {
|
|
1444
1570
|
// ARKit's transform is a 4x4 matrix; extract translation
|
|
1445
1571
|
// (last column) and rotation (top-left 3x3 → quaternion).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-image-stitcher",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Pose-aware panorama capture + stitching for React Native. One <Camera> component, both tap-to-photo and hold-to-pan modes, both AR-backed and IMU-fallback capture paths.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
|
|
45
45
|
import { ensureStitcherProxyInstalled } from '../stitching/ensureStitcherProxyInstalled';
|
|
46
46
|
import type { CameraFrameProcessor } from '../stitching/CameraFrame';
|
|
47
|
-
import type { ARFrameMeta } from '../stitching/ARFrameMeta';
|
|
47
|
+
import type { ARFrameMeta, ARPluginResult } from '../stitching/ARFrameMeta';
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
// React Native looks up the component by its NATIVE name.
|
|
@@ -145,6 +145,28 @@ export interface ARCameraViewProps {
|
|
|
145
145
|
* (≈ 10 Hz). No effect unless `onArFrame` is provided.
|
|
146
146
|
*/
|
|
147
147
|
arFrameMetaInterval?: number;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* v0.19.0 — ASYNCHRONOUS AR-plugin result callback, invoked on the JS MAIN
|
|
151
|
+
* thread (NOT a worklet). Part of the AR plugin framework: host-registered
|
|
152
|
+
* native plugins (see `RNISARPluginRegistry` / `RNSARPluginRegistry`) can
|
|
153
|
+
* offload heavy per-frame work to their own queue and later push a result
|
|
154
|
+
* via `registry.emit(name, result)`. The SDK routes that to JS as a
|
|
155
|
+
* `RNImageStitcherARPluginResult` device event; when this prop is provided,
|
|
156
|
+
* this component subscribes and invokes the handler with
|
|
157
|
+
* `{ plugin, result }`.
|
|
158
|
+
*
|
|
159
|
+
* SYNCHRONOUS plugin results (computed inline on the AR thread) instead ride
|
|
160
|
+
* the throttled {@link onArFrame} event on {@link ARFrameMeta.plugins} —
|
|
161
|
+
* read them there. This callback is ONLY for the out-of-band async channel.
|
|
162
|
+
*
|
|
163
|
+
* The subscription is independent of {@link onArFrame}: a host can read
|
|
164
|
+
* sync results via `onArFrame` and async results via `onArPluginResult`,
|
|
165
|
+
* either, or both. Wiring mirrors `onArFrame` exactly (latest handler held
|
|
166
|
+
* in a ref so the subscription effect depends only on whether a handler is
|
|
167
|
+
* present; cleanup on unmount / when the handler is removed).
|
|
168
|
+
*/
|
|
169
|
+
onArPluginResult?: (e: ARPluginResult) => void;
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
|
|
@@ -237,6 +259,7 @@ export const ARCameraView = forwardRef<ARCameraViewHandle, ARCameraViewProps>(
|
|
|
237
259
|
planeDetection,
|
|
238
260
|
onArFrame,
|
|
239
261
|
arFrameMetaInterval,
|
|
262
|
+
onArPluginResult,
|
|
240
263
|
},
|
|
241
264
|
ref,
|
|
242
265
|
): React.JSX.Element {
|
|
@@ -362,6 +385,48 @@ export const ARCameraView = forwardRef<ARCameraViewHandle, ARCameraViewProps>(
|
|
|
362
385
|
};
|
|
363
386
|
}, [arFrameEnabled, arFrameMetaInterval]);
|
|
364
387
|
|
|
388
|
+
// v0.19.0 — onArPluginResult device-event wiring (worklet-free, main
|
|
389
|
+
// thread). Mirrors the onArFrame subscription above: the latest handler
|
|
390
|
+
// is held in a ref so the subscription effect depends only on WHETHER a
|
|
391
|
+
// handler is present, not its (per-render-changing) identity — so the
|
|
392
|
+
// native event subscription isn't torn down + re-established every render.
|
|
393
|
+
//
|
|
394
|
+
// This is a PURELY-JS subscription: unlike onArFrame there's no native
|
|
395
|
+
// "enable" toggle to flip. Native emits `RNImageStitcherARPluginResult`
|
|
396
|
+
// whenever a registered plugin calls `registry.emit(...)`; the registry is
|
|
397
|
+
// empty unless the host registered plugins, so an app with no plugins
|
|
398
|
+
// never sees an event even if this prop is wired.
|
|
399
|
+
const onArPluginResultRef = useRef<
|
|
400
|
+
((e: ARPluginResult) => void) | undefined
|
|
401
|
+
>(onArPluginResult);
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
onArPluginResultRef.current = onArPluginResult;
|
|
404
|
+
}, [onArPluginResult]);
|
|
405
|
+
|
|
406
|
+
const arPluginResultEnabled = onArPluginResult != null;
|
|
407
|
+
useEffect(() => {
|
|
408
|
+
if (!arPluginResultEnabled) {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
const native = (NativeModules as Record<string, unknown>)
|
|
412
|
+
.RNSARSession;
|
|
413
|
+
if (native == null) {
|
|
414
|
+
// Native module unavailable (e.g. web, or a native build predating
|
|
415
|
+
// the plugin event channel): no-op, no crash.
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
const emitter = new NativeEventEmitter(native as never);
|
|
419
|
+
const sub = emitter.addListener(
|
|
420
|
+
'RNImageStitcherARPluginResult',
|
|
421
|
+
(e: ARPluginResult) => {
|
|
422
|
+
onArPluginResultRef.current?.(e);
|
|
423
|
+
},
|
|
424
|
+
);
|
|
425
|
+
return () => {
|
|
426
|
+
sub.remove();
|
|
427
|
+
};
|
|
428
|
+
}, [arPluginResultEnabled]);
|
|
429
|
+
|
|
365
430
|
useImperativeHandle(ref, () => ({
|
|
366
431
|
takePhoto: async (options = {}) => {
|
|
367
432
|
const native: any =
|
package/src/camera/Camera.tsx
CHANGED
|
@@ -67,7 +67,7 @@ import type {
|
|
|
67
67
|
|
|
68
68
|
import { useARSession } from '../ar/useARSession';
|
|
69
69
|
import type { CameraFrameProcessor } from '../stitching/CameraFrame';
|
|
70
|
-
import type { ARFrameMeta } from '../stitching/ARFrameMeta';
|
|
70
|
+
import type { ARFrameMeta, ARPluginResult } from '../stitching/ARFrameMeta';
|
|
71
71
|
import { ARCameraView, type ARCameraViewHandle } from './ARCameraView';
|
|
72
72
|
import { CameraShutter } from './CameraShutter';
|
|
73
73
|
import { CameraView } from './CameraView';
|
|
@@ -814,6 +814,26 @@ export interface CameraProps {
|
|
|
814
814
|
*/
|
|
815
815
|
arFrameMetaInterval?: number;
|
|
816
816
|
|
|
817
|
+
/**
|
|
818
|
+
* v0.19.0 — ASYNCHRONOUS AR-plugin result callback (the AR plugin
|
|
819
|
+
* framework), invoked on the JS MAIN thread (NOT a worklet). Only fires in
|
|
820
|
+
* AR capture (`captureSource === 'ar'`). Host-registered native plugins
|
|
821
|
+
* (see `RNISARPluginRegistry` / `RNSARPluginRegistry`) that offload heavy
|
|
822
|
+
* per-frame work to their own queue push results via
|
|
823
|
+
* `registry.emit(name, result)`; `<Camera>` threads this handler to
|
|
824
|
+
* `<ARCameraView>`, which subscribes to the `RNImageStitcherARPluginResult`
|
|
825
|
+
* device event and invokes it with `{ plugin, result }`.
|
|
826
|
+
*
|
|
827
|
+
* SYNCHRONOUS plugin results (computed inline on the AR thread) instead ride
|
|
828
|
+
* the throttled {@link onArFrame} event on {@link ARFrameMeta.plugins}.
|
|
829
|
+
* Use `onArFrame` for the in-band sync channel and `onArPluginResult` for
|
|
830
|
+
* the out-of-band async channel — a host can wire either or both.
|
|
831
|
+
*
|
|
832
|
+
* The SDK ships ONLY the generic plugin framework; there are no built-in
|
|
833
|
+
* plugins, so this never fires unless the host registers native plugins.
|
|
834
|
+
*/
|
|
835
|
+
onArPluginResult?: (e: ARPluginResult) => void;
|
|
836
|
+
|
|
817
837
|
// ── Panorama GUIDANCE (feature/pano-ux-guidance) ──────────────────
|
|
818
838
|
/**
|
|
819
839
|
* Which device holds the non-AR panorama capture accepts.
|
|
@@ -1227,6 +1247,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1227
1247
|
planeDetection,
|
|
1228
1248
|
onArFrame,
|
|
1229
1249
|
arFrameMetaInterval,
|
|
1250
|
+
onArPluginResult,
|
|
1230
1251
|
engine = 'batch-keyframe',
|
|
1231
1252
|
// ── Panorama GUIDANCE (feature/pano-ux-guidance) ──────────────
|
|
1232
1253
|
panMode = 'vertical',
|
|
@@ -2497,6 +2518,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
2497
2518
|
planeDetection={planeDetection}
|
|
2498
2519
|
onArFrame={onArFrame}
|
|
2499
2520
|
arFrameMetaInterval={arFrameMetaInterval}
|
|
2521
|
+
onArPluginResult={onArPluginResult}
|
|
2500
2522
|
/>
|
|
2501
2523
|
) : (
|
|
2502
2524
|
<CameraView
|
package/src/index.ts
CHANGED
|
@@ -265,8 +265,13 @@ export type {
|
|
|
265
265
|
} from './stitching/CameraFrame';
|
|
266
266
|
// v0.18.0 — LIGHT per-frame AR metadata delivered via the `onArFrame`
|
|
267
267
|
// callback (main-thread, worklet-free). See the type's docstring for why
|
|
268
|
-
// it bypasses the worklet path.
|
|
268
|
+
// it bypasses the worklet path. v0.19.0 adds `plugins` (sync results from
|
|
269
|
+
// host-registered AR plugins ride this same throttled event).
|
|
269
270
|
export type { ARFrameMeta } from './stitching/ARFrameMeta';
|
|
271
|
+
// v0.19.0 — the AR plugin framework's ASYNC result type, delivered via the
|
|
272
|
+
// `onArPluginResult` callback (a plugin's out-of-band `registry.emit(...)`
|
|
273
|
+
// result). The SDK ships only the generic framework — no built-in plugins.
|
|
274
|
+
export type { ARPluginResult } from './stitching/ARFrameMeta';
|
|
270
275
|
// NOTE: the host-worklet / frame-stream hooks `useFrameProcessor`,
|
|
271
276
|
// `useThrottledFrameProcessor` and `useFrameStream` (v0.8–v0.9) were
|
|
272
277
|
// archived in the batch-keyframe cleanup — they drove the third-party
|
|
@@ -104,4 +104,54 @@ export interface ARFrameMeta {
|
|
|
104
104
|
vertexCount: number;
|
|
105
105
|
faceCount: number;
|
|
106
106
|
} | null;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* v0.19.0 — SYNCHRONOUS results from host-registered AR frame plugins
|
|
110
|
+
* (the AR plugin framework). Keyed by each plugin's `name()`; the value
|
|
111
|
+
* is the light JSON result the plugin's `process(ctx)` returned on the AR
|
|
112
|
+
* thread (`nil`/`null`-returning plugins are omitted). Only present when
|
|
113
|
+
* the native plugin registry is non-empty AND at least one plugin returned
|
|
114
|
+
* a sync result for this frame; otherwise omitted entirely (zero-plugin
|
|
115
|
+
* apps pay nothing — native skips building the context).
|
|
116
|
+
*
|
|
117
|
+
* The SDK ships ONLY the generic framework — there are no built-in
|
|
118
|
+
* plugins. Hosts register native plugins via `RNISARPluginRegistry`
|
|
119
|
+
* (iOS) / `RNSARPluginRegistry` (Android) at startup; each plugin is
|
|
120
|
+
* called once per AR frame while the registry is non-empty. Result
|
|
121
|
+
* values are `unknown` because each plugin defines its own shape — cast
|
|
122
|
+
* after reading the entry you care about (e.g.
|
|
123
|
+
* `meta.plugins?.brightness as number`).
|
|
124
|
+
*
|
|
125
|
+
* ## Sync vs async results
|
|
126
|
+
*
|
|
127
|
+
* This field carries only the LIGHT, in-band SYNC results (computed fast
|
|
128
|
+
* enough to ride the throttled `onArFrame` event). Plugins that offload
|
|
129
|
+
* heavy work to their own queue deliver results out-of-band via
|
|
130
|
+
* `registry.emit(name, result)`, which surfaces through the separate
|
|
131
|
+
* `onArPluginResult` callback (the `RNImageStitcherARPluginResult`
|
|
132
|
+
* event) — NOT here.
|
|
133
|
+
*/
|
|
134
|
+
plugins?: { [name: string]: unknown };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* v0.19.0 — an ASYNCHRONOUS result from a host-registered AR frame plugin,
|
|
140
|
+
* delivered via the `onArPluginResult` callback.
|
|
141
|
+
*
|
|
142
|
+
* Unlike the in-band SYNC results carried on {@link ARFrameMeta.plugins}
|
|
143
|
+
* (which ride the throttled `onArFrame` event), a plugin produces an async
|
|
144
|
+
* result by offloading heavy work to its own queue and later calling
|
|
145
|
+
* `registry.emit(name, result)` on the native side. The SDK routes that to
|
|
146
|
+
* JS as a `RNImageStitcherARPluginResult` device event; `<ARCameraView>`
|
|
147
|
+
* subscribes and invokes `onArPluginResult` on the JS MAIN thread.
|
|
148
|
+
*
|
|
149
|
+
* `result` is `unknown` because each plugin defines its own result shape —
|
|
150
|
+
* branch on `plugin` (the emitting plugin's `name()`) and cast accordingly.
|
|
151
|
+
*/
|
|
152
|
+
export interface ARPluginResult {
|
|
153
|
+
/** The `name()` of the plugin that emitted this result. */
|
|
154
|
+
plugin: string;
|
|
155
|
+
/** The plugin-defined result payload (cast after branching on `plugin`). */
|
|
156
|
+
result: unknown;
|
|
107
157
|
}
|