import React, { forwardRef, type ForwardedRef, useMemo } from 'react';
import { Platform } from 'react-native';
import * as VisionCameraModule from 'react-native-vision-camera';
import {
createTextRecognitionPlugin,
type TextRecognitionHandle,
} from './scanText';
import { createTranslatorPlugin, type TranslatorHandle } from './translateText';
import { runOnJS } from 'react-native-worklets';
import type {
CameraTypes,
Text,
Frame,
ReadonlyFrameProcessor,
TextRecognitionOptions,
TranslatorOptions,
} from './types';
/**
* A drop-in replacement for VisionCamera's `` that automatically
* runs OCR or translation on every frame and fires a `callback` with the result.
*
* @example — Recognize text
* ```tsx
* console.log((data as Text).resultText)}
* />
* ```
*
* @example — Translate text
* ```tsx
* console.log(translated as string)}
* />
* ```
*/
export const Camera = forwardRef(function Camera(
props: CameraTypes,
ref: ForwardedRef
) {
const NativeCamera = (VisionCameraModule as any).Camera;
const useFrameProcessor = (VisionCameraModule as any).useFrameProcessor as (
processor: (frame: Frame) => void,
deps?: ReadonlyArray
) => ReadonlyFrameProcessor;
const { device, callback, options, mode, ...p } = props;
const recognizerHandle = useTextRecognition(
mode === 'recognize' ? (options as TextRecognitionOptions) : undefined
);
const translatorHandle = useTranslate(
mode === 'translate' ? (options as TranslatorOptions) : undefined
);
// Pull the raw Nitro HybridObjects out so the worklet compiler can capture
// them directly. Nitro HybridObjects are JSI HostObjects — they are worklet-
// safe and can be called synchronously on the UI thread without any wrapper.
const recognizer =
mode === 'translate'
? translatorHandle.recognizer
: recognizerHandle.recognizer;
const translator = translatorHandle.translator;
const fromLang = translatorHandle.from;
const toLang = translatorHandle.to;
// JS-thread handler for recognize mode
const runData = useMemo(
() => runOnJS((data: Text) => callback(data)),
[callback]
);
// JS-thread handler for translate mode
const runTranslate = useMemo(() => {
const translationState = {
inFlight: false,
lastRequestedText: '',
requestId: 0,
};
return runOnJS((text: string) => {
if (!text) return;
if (
translationState.inFlight ||
text === translationState.lastRequestedText
)
return;
translationState.inFlight = true;
translationState.lastRequestedText = text;
const requestId = ++translationState.requestId;
(translator as any)
.translate(text, fromLang, toLang)
.then((translated: string) => {
if (requestId === translationState.requestId) {
translationState.lastRequestedText = text;
callback(translated);
}
})
.catch((error: unknown) => {
if (requestId === translationState.requestId) {
translationState.lastRequestedText = '';
}
console.warn(
'[react-native-vision-camera-ocr-plus] Translation failed',
error
);
})
.finally(() => {
if (requestId === translationState.requestId) {
translationState.inFlight = false;
}
});
});
}, [translator, fromLang, toLang, callback]);
const processFrame = (frame: Frame): void => {
'worklet';
// Call the Nitro HybridObject directly — no wrapper function involved.
const nb = (frame as any).getNativeBuffer() as {
pointer: bigint;
release: () => void;
};
const orientation: string = (frame as any).orientation ?? 'up';
let ocrResult: Text | undefined | null;
try {
ocrResult = (recognizer as any).scanFrame(nb.pointer, orientation) as
| Text
| undefined
| null;
} finally {
nb.release();
}
const result: Text = ocrResult ?? { resultText: '', blocks: [] };
if (mode === 'translate') {
if (result.resultText) {
runTranslate(result.resultText);
}
} else {
runData(result);
}
(frame as any).dispose?.();
};
const frameProcessor = useFrameProcessor(processFrame, [
recognizer,
translator,
fromLang,
toLang,
runData,
runTranslate,
mode,
]);
return (
<>
{!!device && (
)}
>
);
});
/**
* React hook that creates and memoizes a text recognition handle.
*/
export function useTextRecognition(
options?: TextRecognitionOptions
): TextRecognitionHandle {
return useMemo(() => createTextRecognitionPlugin(options), [options]);
}
/**
* React hook that creates and memoizes a translator handle.
*/
export function useTranslate(options?: TranslatorOptions): TranslatorHandle {
return useMemo(() => createTranslatorPlugin(options), [options]);
}