/** * ARCameraView — AR-backed alternative to ```` for * audits that need pose-aware capture (panorama mode, packet * detection). Renders the ARKit camera feed via the native * `RNSARCameraView` UIView; the underlying ARSession is the * SDK singleton (`RNSARSession.shared`), shared between the * preview and the pose log that feeds Phase 5 stitching + Phase 6 * measurement. * * Why a separate component (vs. a polymorphic CameraView)? * 1. **Different imperative API.** The vision-camera-backed * CameraView exposes `takePhoto / startRecording` via its ref * (Phase 5 will add equivalents to this component, but they * route through ARFrame.capturedImage + AVAssetWriter rather * than vision-camera's APIs). * 2. **Camera-access conflict.** ARKit and AVCaptureSession * can't share the camera. Forcing the host to pick one * component over the other (instead of toggling a prop on a * shared component) makes the conflict impossible to misuse — * you can't accidentally mount both at the same time. * 3. **Lifecycle clarity.** The native side starts the AR * session in `didMoveToWindow`. Mount = start, unmount = * stop. No flag-twiddling. * * This component is preview-only in Phase 4.4. Photo + video * capture come in Phase 5 (Step 5 of the AR design plan). Until * then, the host's panorama capture flow continues to use * vision-camera; ARCameraView is opt-in via a settings flag for * developer verification. */ import React from 'react'; import { type ViewStyle } from 'react-native'; import type { CameraFrameProcessor } from '../stitching/CameraFrame'; import type { ARFrameMeta, ARPluginResult } from '../stitching/ARFrameMeta'; import type { AROverlay } from '../stitching/AROverlay'; import type { FramePose } from '../ar/useARSession'; import { type AROverlayMethods } from './arOverlayController'; export interface ARCameraViewProps { /** Layout style, typically `StyleSheet.absoluteFill` or `flex: 1`. */ style?: ViewStyle; /** * Optional themed guidance banner shown over the preview at the * top, mirrors the `` prop so host apps can swap * components without rewriting their guidance text plumbing. */ guidance?: string; /** * Optional host worklet invoked once per AR frame, ALONGSIDE the * lib's first-party stitching (composition, not replacement). The * worklet receives a `CameraFrame` enriched with AR metadata — * `source: 'ar'`, world-space `pose` (rotation + translation), * `arTrackingState`, and (when supported) `arDepth` / `arAnchors`. * * Must be a `'worklet'`-prefixed function. Registration installs the * native `__stitcherProxy` JSI host object on first use and fans the * worklet out from the AR session's per-frame dispatch. If the * native install is unavailable (e.g. remote debugging), the worklet * silently never fires — no crash. * * The non-AR equivalent is vision-camera's own `useFrameProcessor` * passed via ``; the two modes run on * different runtimes with different frame shapes, hence the separate * prop. */ arFrameProcessor?: CameraFrameProcessor; /** * Opt in to per-frame AR depth extraction (`CameraFrame.arDepth`). * Default `false` — depth is the costliest field (a per-frame buffer * copy), so it stays off until a worklet needs it. */ enableDepth?: boolean; /** * Opt in to the SLAM feature-point cloud in AR plugin contexts. Default * `false`. Available on ALL AR-capable devices — no LiDAR required. * Consumed natively by AR plugins only; does not appear in * {@link ARFrameMeta} or `CameraFrame`. * * - iOS → ARKit `rawFeaturePoints` in `RNISARFrameContext.featurePoints` * as world-space `[simd_float3]` (bare `x, y, z`). * - Android → ARCore `Frame.acquirePointCloud()` in * `ARFrameContext.featurePoints` as a flat stride-4 * `[x, y, z, confidence]` world-space `FloatArray` (the extra * per-point confidence lets native plugins filter ARCore's * sparser cloud). */ enableFeaturePoints?: boolean; /** * Opt in to high-resolution photo capture (iOS 16+). When `true`, the AR * session runs on the smallest video format that supports * `captureHighResolutionFrame`, so `takePhoto()` returns a true full-res * still (for document OCR / detail capture). Default `false` — the live * stream stays as small as possible (cheapest for the panorama-stitch * path, whose keyframes are downscaled to a fixed budget regardless). * No-op on Android (no equivalent high-res capture API). */ highResCapture?: boolean; /** * Opt in to PANORAMA-QUALITY keyframes (Android). Picks a larger ARCore * CPU-image config (largest long-edge ≤ 1920 — e.g. the A35's 1920×1080 * over its tiny 640×480 sole-4:3 config) and lifts the keyframe encode * budget 640 → 1280, so stitches stop being assembled from 0.3 MP tiles. * Costs stitch memory (~4× pixels per keyframe) — pano flows only; DT / * liveness sessions must not set it. Default `false`. No-op on iOS * (keyframes are already saved at native resolution there) and on * binaries older than the feature (optional-chained native call). */ keyframeQualityCapture?: boolean; /** * Opt in to per-frame AR anchor extraction (`CameraFrame.arAnchors` — * detected planes / augmented images). Default `false`. */ enableAnchors?: boolean; /** * Opt in to scene-reconstruction mesh anchors (`type: 'mesh'` entries * in `arAnchors`, carrying `meshGeometry`). Default `false`. iOS * enables ARKit `sceneReconstruction` (LiDAR devices); Android * reconstructs a rough mesh from the depth map. Expensive — only on * when needed. Implies depth on Android. */ enableMesh?: boolean; /** * Which plane orientations to surface in `arAnchors` (requires * `enableAnchors`). Default `'vertical'` — the orientation the * plane-projected stitch path has always used, so existing callers * see no change. * * - `'vertical'` — walls / doors / fixtures (the default) * - `'horizontal'` — floors / tables / seats * - `'both'` — surface every detected plane * * Platform notes: iOS changes ARKit `planeDetection` to match (a * live session reconfigure). Android always detects both planes * (ARCore needs horizontal planes to bootstrap tracking) and simply * FILTERS which orientations reach `arAnchors`, so the JS-observable * set is identical on both platforms. */ planeDetection?: 'vertical' | 'horizontal' | 'both'; /** * v0.18.0 — LIGHT per-frame AR metadata callback, invoked on the JS * MAIN thread (NOT a worklet). When provided, the native AR session * builds an {@link ARFrameMeta} per frame and emits it as a device * event; this component subscribes and calls the handler. Worklet-free * — this is the recommended way to read AR pose / tracking / anchor / * intrinsics / depth-dims / mesh-counts data (the `arFrameProcessor` * worklet can only safely surface a shared value; see `ARFrameMeta`). * * Costly fields are gated: `depth` only when `enableDepth`, `mesh` only * when `enableMesh`, `anchors` only when `enableAnchors`; * `intrinsics` / `pose` / `trackingState` are always present. Emission * is throttled to {@link arFrameMetaInterval} ms. */ onArFrame?: (meta: ARFrameMeta) => void; /** * v0.18.0 — throttle interval (ms) for {@link onArFrame}. Default `100` * (≈ 10 Hz). No effect unless `onArFrame` is provided. */ arFrameMetaInterval?: number; /** * v0.19.0 — ASYNCHRONOUS AR-plugin result callback, invoked on the JS MAIN * thread (NOT a worklet). Part of the AR plugin framework: host-registered * native plugins (see `RNISARPluginRegistry` / `RNSARPluginRegistry`) can * offload heavy per-frame work to their own queue and later push a result * via `registry.emit(name, result)`. The SDK routes that to JS as a * `RNImageStitcherARPluginResult` device event; when this prop is provided, * this component subscribes and invokes the handler with * `{ plugin, result }`. * * SYNCHRONOUS plugin results (computed inline on the AR thread) instead ride * the throttled {@link onArFrame} event on {@link ARFrameMeta.plugins} — * read them there. This callback is ONLY for the out-of-band async channel. * * The subscription is independent of {@link onArFrame}: a host can read * sync results via `onArFrame` and async results via `onArPluginResult`, * either, or both. Wiring mirrors `onArFrame` exactly (latest handler held * in a ref so the subscription effect depends only on whether a handler is * present; cleanup on unmount / when the handler is removed). */ onArPluginResult?: (e: ARPluginResult) => void; /** * v0.20.0 — AR OVERLAY / ANNOTATION renderer. A declarative array of 2D * shapes the native overlay layer draws ON TOP of the AR camera preview, * each anchored to WORLD positions and REPROJECTED to screen on every AR * frame from the current camera pose + intrinsics (smooth, display-rate * tracking; no 3D engine). * * State-driven: pass a React-state array and update it as your world points * change. The set is diffed against the current overlays BY `id` (add / * update / remove), so re-passing the same ids is cheap. Each render pushes * the resolved array to native via `RNSARSession.setOverlays`. * * For zero-render-latency / fire-and-forget mutations use the imperative ref * methods instead ({@link ARCameraViewHandle.setOverlays} etc.) — both paths * funnel through the same native channel and stay consistent. JS-set * overlays are merged on the native side with any overlays a registered AR * plugin placed directly (`RNISARPluginRegistry.setOverlays` / * `RNSARPluginRegistry.setOverlays`); the two sets are namespaced so neither * clobbers the other. * * See {@link AROverlay} for the shape (single world point + size, or explicit * world quad; `outline` / `box`; optional label + colour; `mode:'3d'` is a * documented scaffold this release and renders as `'2d'`). */ overlays?: AROverlay[]; } /** * Imperative handle exposed via the ref — shape mirrors the subset * of vision-camera's `Camera` ref methods that the host's * `useCapture` / `useVideoCapture` hooks call. Hosts can pass the * SAME ref to those hooks as they do for the vision-camera path, * with no branching required. * * Note we do NOT exhaustively mirror vision-camera's API surface — * only the methods the panorama capture flow uses today. As the * SDK grows AR-aware features, methods are added here. * * v0.20.0 — also exposes the imperative AR-overlay methods * ({@link AROverlayMethods}: `setOverlays` / `addOverlay` / `updateOverlay` / * `removeOverlay` / `clearOverlays`) so a host can drive overlays without a * render (the declarative `overlays` prop is the React-state alternative). */ export interface ARCameraViewHandle extends AROverlayMethods { /** * Capture the latest ARFrame as a JPEG. Resolves with a * vision-camera-compatible PhotoFile (`{ path, width, height, * isMirrored, isRawPhoto }`). Native generates a temp path — * caller does NOT need to construct one. */ takePhoto: (options?: { quality?: number; /** * v0.12.0 — device orientation at capture time, used to bake * correct rotation into the saved JPEG. Pass the value from * `useDeviceOrientation()`. Defaults to `'portrait'` on the * native side if omitted (preserves pre-v0.12 behavior). * Without this, AR-mode photos taken in landscape come out * sideways because the native side previously hardcoded the * rotate-to-portrait assumption. */ orientation?: 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right'; /** * Photo-capture plugin passthrough: EVERY extra key is forwarded * verbatim to the native takePhoto options, where registered * `RNSPhotoCapturePlugin`s receive the full dictionary. Lets a host * route per-call flags to its own native plugin without a library * change. With no plugin registered, extra keys are never read. */ [pluginOption: string]: unknown; }) => Promise<{ path: string; width: number; height: number; isMirrored: boolean; isRawPhoto: boolean; /** * AR camera pose of the EXACT frame whose pixels became the photo — * the same shape (full intrinsics included) as the per-frame pose * ledger (`getFramePoses`), built by one shared native builder. * Intrinsics/dims describe the AR camera's native (unoriented) frame, * not the oriented JPEG dims above. Absent only when the native pose * read failed (a failed pose never blocks the photo). */ pose?: FramePose; /** * Registered photo-capture plugins may merge additional fields into * the result (the library's own keys always win). Typed open so a * host can read its plugin's fields without casting through `any`. */ [pluginField: string]: unknown; }>; /** * Begin recording AR frames into an mp4. Mirrors vision-camera's * callback-based API: takes `onRecordingFinished` / * `onRecordingError` handlers; the actual VideoFile is delivered * via `onRecordingFinished` AFTER the host calls `stopRecording`. * * Synchronous return (void) — useVideoCapture wraps it in a * Promise on top of the callbacks. */ startRecording: (options: { onRecordingFinished?: (video: { path: string; duration: number; size: number; width: number; height: number; }) => void; onRecordingError?: (err: Error) => void; }) => void; /** Finalise the in-progress recording. */ stopRecording: () => Promise; } export declare const ARCameraView: React.ForwardRefExoticComponent>; //# sourceMappingURL=ARCameraView.d.ts.map