dist/plugins/mobile-camera-vision-camera/skills/mobile-camera-vision-camera/SKILL.md
VisionCamera v4+ - photo/video capture, QR/barcode scanning, real-time frame processors, zoom/focus/exposure, HDR, location metadata, format selection
npx skillsauth add agents-inc/skills mobile-camera-vision-cameraInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use VisionCamera for high-performance camera features in React Native. Control the camera lifecycle with
isActive(never unmount/remount). UseuseCameraDeviceto select back/front cameras,useCameraPermissionfor permissions. Capture photos withtakePhoto(), record video withstartRecording()/stopRecording(), scan codes withuseCodeScanner, and process frames in real time withuseFrameProcessorworklets. Frame processors run on a parallel JS thread via JSI -- keep them fast or userunAsync/runAtTargetFpsto avoid blocking the pipeline.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST set isActive based on screen focus AND app state -- camera must pause when backgrounded or navigated away)
(You MUST request permissions before rendering the Camera -- useCameraPermission returns hasPermission and requestPermission)
(You MUST include the 'worklet' directive as the first line of every frame processor function body)
(You MUST enable only the pipelines you need (photo, video, codeScanner, frameProcessor) -- unused pipelines waste resources)
(You MUST use useSharedValue (not useState) for data shared between frame processors and the React thread)
</critical_requirements>
Auto-detection: VisionCamera, react-native-vision-camera, useCameraDevice, useCameraDevices, useCameraPermission, useMicrophonePermission, useCodeScanner, useFrameProcessor, useSkiaFrameProcessor, useCameraFormat, takePhoto, takeSnapshot, startRecording, stopRecording, Camera component, frame processor, worklet, codeScanner, photoQualityBalance, enableLocation, videoHdr, photoHdr
When to use:
When NOT to use:
Key patterns covered:
isActive and screen/app stateuseCameraPermission, useMicrophonePermission)takePhoto, takeSnapshot) and video recordinguseCodeScannerrunAsync, and runAtTargetFpsDetailed Resources:
VisionCamera provides direct, high-performance camera access in React Native via JSI (JavaScript Interface). It bypasses the legacy bridge entirely, giving synchronous control over native camera hardware from JavaScript.
Core principles:
isActive prop controls the camera session. Toggle it instead of mounting/unmounting. Resuming is much faster than re-mounting.photo, video, codeScanner, frame processor). Each pipeline allocates resources.react-native-worklets-core. They execute synchronously in the video pipeline, so they must be fast.When to use VisionCamera:
When NOT to use:
The camera must be activated only when the screen is focused AND the app is in the foreground. Always check permissions before rendering.
import { useCameraDevice, useCameraPermission, Camera } from "react-native-vision-camera";
import { StyleSheet } from "react-native";
export function CameraScreen() {
const device = useCameraDevice("back");
const { hasPermission, requestPermission } = useCameraPermission();
// Request permission on mount if not granted
// Render permission UI or Camera based on hasPermission
if (!hasPermission) return <PermissionRequest onRequest={requestPermission} />;
if (device == null) return <NoCameraDeviceError />;
return <Camera style={StyleSheet.absoluteFill} device={device} isActive={isActive} />;
}
Why good: permission checked before render, device null-checked, isActive controls lifecycle without unmounting
The isActive prop should combine screen focus and app state:
const isFocused = useIsFocused(); // from navigation
const appState = useAppState(); // from community hooks
const isActive = isFocused && appState === "active";
See examples/core.md for the complete lifecycle pattern with permissions, device selection, and app state handling.
Use a Camera ref to call takePhoto(). Enable the photo pipeline on the Camera component. Use takeSnapshot() for fast preview-quality captures.
const camera = useRef<Camera>(null);
const handleCapture = async () => {
const photo = await camera.current?.takePhoto({
flash: "auto",
enableShutterSound: true,
});
// photo.path contains the temporary file path
};
<Camera ref={camera} device={device} isActive={isActive} photo={true} />
Why good: photo pipeline explicitly enabled, ref used for imperative capture, options typed
takePhoto() -- full-quality capture with AE/AF/AWB, supports flashtakeSnapshot() -- ~16ms capture from preview buffer, requires video enabled on iOSphotoQualityBalance prop: "speed" | "balanced" | "quality"See examples/core.md for full photo capture with error handling and format selection.
Use startRecording() with callbacks for completion and errors. stopRecording() triggers onRecordingFinished.
camera.current?.startRecording({
onRecordingFinished: (video) => {
// video.path contains the temporary file path
// video.duration contains the duration in seconds
},
onRecordingError: (error) => console.error(error),
});
// Later:
await camera.current?.stopRecording();
Why good: callback-based API handles async completion, error callback prevents silent failures
video={true} and audio={true} on the Camera componentpauseRecording() / resumeRecording() for pause supportcancelRecording() deletes the temp file and fires onRecordingErrorvideoBitRate: "low" | "normal" | "high" or custom Mbps numbervideoCodec: "h264" (default, wider compatibility) or "h265" (better compression)See examples/core.md for complete video recording with pause/resume.
Use the useCodeScanner hook. Runs on the native thread for instant scanning without UI freezing.
const codeScanner = useCodeScanner({
codeTypes: ["qr", "ean-13"],
onCodeScanned: (codes) => {
// codes[0].value contains the decoded string
// Fires many times per second -- debounce if updating state
},
});
<Camera device={device} isActive={isActive} codeScanner={codeScanner} />
Why good: native-thread scanning, specific code types listed (not all), callback provides decoded values
Android setup: Requires MLKit. Enable via VisionCamera_enableCodeScanner=true in gradle.properties (or Expo plugin config).
Gotcha: onCodeScanned fires many times per second. Debounce or guard with a ref to avoid processing the same code repeatedly.
See examples/scanning-and-processing.md for debounced scanning and supported code types.
Frame processors run JavaScript worklets on a parallel camera thread for real-time frame analysis. Requires react-native-worklets-core.
const frameProcessor = useFrameProcessor((frame) => {
"worklet";
// frame.width, frame.height, frame.pixelFormat
// frame.toArrayBuffer() for raw pixel data
const results = detectObjects(frame); // native plugin call
}, []);
<Camera device={device} isActive={isActive} frameProcessor={frameProcessor} />
Why good: worklet directive present, runs on parallel thread, native plugin for heavy work
runAsync(() => { ... }) -- offload heavy processing without blockingrunAtTargetFps(5, () => { ... }) -- process at lower FPS to save resourcesuseSharedValue -- share data between frame processor and React threadcreateRunOnJS(fn) -- call React functions from within a workletSee examples/scanning-and-processing.md for async processing, shared values, and performance patterns.
Choose camera devices by position and physical lenses. Select formats for resolution, FPS, and HDR support.
// Basic device selection
const device = useCameraDevice("back");
// Multi-camera with specific lenses
const device = useCameraDevice("back", {
physicalDevices: ["ultra-wide-angle-camera", "wide-angle-camera", "telephoto-camera"],
});
// Format selection (filters ordered by descending priority)
const format = useCameraFormat(device, [
{ videoAspectRatio: 16 / 9 },
{ videoResolution: { width: 1920, height: 1080 } },
{ fps: 30 },
]);
Why good: multi-camera support with fallback, format priorities explicitly ordered, resolution matched to actual need
See reference.md for the device and format decision framework.
Control camera optics via props and ref methods.
// Zoom: use device.minZoom, device.maxZoom, device.neutralZoom
<Camera zoom={device.neutralZoom} />
// Focus: tap-to-focus via ref
await camera.current?.focus({ x: tapX, y: tapY });
// Exposure: offset from auto-exposure (-2 to +2 typical range)
<Camera exposure={exposureOffset} />
interpolate() for linear gesture mappingenableZoomGesture={true} (no custom gesture needed)device.supportsFocus before calling focus()device.minExposure to device.maxExposureSee reference.md for animated zoom with Reanimated.
Enable HDR for enhanced dynamic range. Enable location for GPS EXIF/MP4 tags.
const format = useCameraFormat(device, [
{ videoHdr: true },
{ photoHdr: true },
]);
<Camera
format={format}
videoHdr={format?.supportsVideoHdr}
photoHdr={format?.supportsPhotoHdr}
enableLocation={true}
/>
useLocationPermission and platform-specific manifest entriesSee examples/core.md for HDR format selection and location permission flow.
</patterns><decision_framework>
What do you need to capture?
|
+-> Still image?
| +-> High quality (AE/AF/AWB) → takePhoto()
| +-> Fast preview capture (~16ms) → takeSnapshot() (requires video pipeline)
|
+-> Video?
| +-> Continuous recording → startRecording() / stopRecording()
| +-> Need pause/resume → pauseRecording() / resumeRecording()
| +-> User cancelled → cancelRecording()
|
+-> QR/barcode scanning?
| +-> useCodeScanner (native thread, no frame processor needed)
|
+-> Real-time frame analysis (ML, detection)?
+-> useFrameProcessor with native plugins
+-> Need Skia drawing on frames? → useSkiaFrameProcessor
How fast must your processor run?
|
+-> Every frame (30/60 FPS)?
| +-> Processing < 33ms? → Default synchronous processor
| +-> Processing > 33ms? → runAsync (offload to separate thread)
|
+-> Lower rate is fine (5-10 FPS)?
+-> runAtTargetFps(targetFps, () => { ... })
Which camera?
|
+-> Simple back/front → useCameraDevice("back") or useCameraDevice("front")
+-> Multi-lens (0.5x + 1x + 3x) → useCameraDevice("back", { physicalDevices: [...] })
+-> External USB camera → Filter useCameraDevices() for position === "external"
+-> Custom logic → useCameraDevices() + useMemo with your own filter
</decision_framework>
<red_flags>
High Priority Issues:
isActive -- wastes resources, slow resume'worklet' directive in frame processor function -- silently runs on wrong thread, crashesuseState to share data from frame processors -- causes thread context switching, use useSharedValuephoto, video, codeScanner, frame processor) when only one is needed -- wastes memory and batterycamera.current.takePhoto() before onInitialized fires -- method not ready, throws errorMedium Priority Issues:
onCodeScanned -- fires many times per second, causes excessive state updatesuseSkiaFrameProcessor when useFrameProcessor suffices -- Skia adds overheadvideoHdr without checking format.supportsVideoHdr -- crashes on unsupported formatsdevice.supportsFocus before calling focus() -- fails on devices without AFGotchas & Edge Cases:
interpolate() for linear gesture mapping.takeSnapshot() requires video={true} on iOS -- it captures from the video preview bufferdevice.neutralZoom may not be 1.0 on ultra-wide cameras -- always use it as the starting zoomVisionCamera_enableCodeScanner=true) -- without it, scanning silently does nothingcancelRecording() fires onRecordingError with capture/recording-canceled -- handle this error type gracefullyenableLocation requires platform manifest entries (iOS: NSLocationWhenInUseUsageDescription, Android: ACCESS_FINE_LOCATION)exposure prop is an offset from auto-exposure, not an absolute ISO value</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST set isActive based on screen focus AND app state -- camera must pause when backgrounded or navigated away)
(You MUST request permissions before rendering the Camera -- useCameraPermission returns hasPermission and requestPermission)
(You MUST include the 'worklet' directive as the first line of every frame processor function body)
(You MUST enable only the pipelines you need (photo, video, codeScanner, frameProcessor) -- unused pipelines waste resources)
(You MUST use useSharedValue (not useState) for data shared between frame processors and the React thread)
Failure to follow these rules will cause crashes, blank previews, wasted battery, and dropped frames.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events