2021-02-19 08:23:54 -07:00
|
|
|
import React from 'react';
|
2023-03-13 07:21:08 -06:00
|
|
|
import { requireNativeComponent, NativeSyntheticEvent, findNodeHandle, NativeMethods, Platform } from 'react-native';
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
import type { VideoFileType } from '.';
|
2021-03-23 10:15:09 -06:00
|
|
|
import type { CameraDevice } from './CameraDevice';
|
2021-02-19 08:23:54 -07:00
|
|
|
import type { ErrorWithCause } from './CameraError';
|
|
|
|
import { CameraCaptureError, CameraRuntimeError, tryParseNativeCameraError, isErrorWithCause } from './CameraError';
|
2023-07-20 07:30:04 -06:00
|
|
|
import type { CameraProps, FrameProcessor } from './CameraProps';
|
2023-03-21 09:10:09 -06:00
|
|
|
import { assertFrameProcessorsAvailable, assertJSIAvailable } from './JSIHelper';
|
2023-03-13 07:21:08 -06:00
|
|
|
import { CameraModule } from './NativeCameraModule';
|
2021-02-19 08:23:54 -07:00
|
|
|
import type { PhotoFile, TakePhotoOptions } from './PhotoFile';
|
|
|
|
import type { Point } from './Point';
|
|
|
|
import type { TakeSnapshotOptions } from './Snapshot';
|
2021-12-30 02:47:23 -07:00
|
|
|
import type { CameraVideoCodec, RecordVideoOptions, VideoFile } from './VideoFile';
|
2021-02-19 08:07:53 -07:00
|
|
|
|
|
|
|
//#region Types
|
2021-02-19 08:23:54 -07:00
|
|
|
export type CameraPermissionStatus = 'authorized' | 'not-determined' | 'denied' | 'restricted';
|
|
|
|
export type CameraPermissionRequestResult = 'authorized' | 'denied';
|
2021-02-19 08:07:53 -07:00
|
|
|
|
|
|
|
interface OnErrorEvent {
|
|
|
|
code: string;
|
|
|
|
message: string;
|
|
|
|
cause?: ErrorWithCause;
|
|
|
|
}
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onError' | 'frameProcessor'> & {
|
2021-03-12 05:21:46 -07:00
|
|
|
cameraId: string;
|
2021-07-12 07:16:03 -06:00
|
|
|
enableFrameProcessor: boolean;
|
2023-07-20 07:30:04 -06:00
|
|
|
previewType: 'native' | 'skia';
|
2021-03-12 05:21:46 -07:00
|
|
|
onInitialized?: (event: NativeSyntheticEvent<void>) => void;
|
|
|
|
onError?: (event: NativeSyntheticEvent<OnErrorEvent>) => void;
|
2021-10-11 10:27:23 -06:00
|
|
|
onViewReady: () => void;
|
2021-03-12 05:21:46 -07:00
|
|
|
};
|
|
|
|
type RefType = React.Component<NativeCameraViewProps> & Readonly<NativeMethods>;
|
2021-02-19 08:07:53 -07:00
|
|
|
//#endregion
|
|
|
|
|
2021-03-23 10:15:09 -06:00
|
|
|
//#region Camera Component
|
2021-02-19 08:07:53 -07:00
|
|
|
/**
|
|
|
|
* ### A powerful `<Camera>` component.
|
2021-03-03 04:37:43 -07:00
|
|
|
*
|
2021-06-21 14:42:46 -06:00
|
|
|
* Read the [VisionCamera documentation](https://mrousavy.github.io/react-native-vision-camera/) for more information.
|
2021-03-08 10:51:53 -07:00
|
|
|
*
|
2021-03-03 04:37:43 -07:00
|
|
|
* The `<Camera>` component's most important (and therefore _required_) properties are:
|
|
|
|
*
|
2021-03-12 06:19:22 -07:00
|
|
|
* * {@linkcode CameraProps.device | device}: Specifies the {@linkcode CameraDevice} to use. Get a {@linkcode CameraDevice} by using the {@linkcode useCameraDevices | useCameraDevices()} hook, or manually by using the {@linkcode Camera.getAvailableCameraDevices Camera.getAvailableCameraDevices()} function.
|
|
|
|
* * {@linkcode CameraProps.isActive | isActive}: A boolean value that specifies whether the Camera should actively stream video frames or not. This can be compared to a Video component, where `isActive` specifies whether the video is paused or not. If you fully unmount the `<Camera>` component instead of using `isActive={false}`, the Camera will take a bit longer to start again.
|
2021-03-03 04:37:43 -07:00
|
|
|
*
|
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```tsx
|
2021-03-03 04:37:43 -07:00
|
|
|
* function App() {
|
|
|
|
* const devices = useCameraDevices('wide-angle-camera')
|
|
|
|
* const device = devices.back
|
|
|
|
*
|
|
|
|
* if (device == null) return <LoadingView />
|
|
|
|
* return (
|
|
|
|
* <Camera
|
|
|
|
* style={StyleSheet.absoluteFill}
|
|
|
|
* device={device}
|
|
|
|
* isActive={true}
|
|
|
|
* />
|
|
|
|
* )
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* @component
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
2021-05-06 08:48:02 -06:00
|
|
|
export class Camera extends React.PureComponent<CameraProps> {
|
2021-07-12 07:16:03 -06:00
|
|
|
/** @internal */
|
2021-02-19 08:23:54 -07:00
|
|
|
static displayName = 'Camera';
|
2021-07-12 07:16:03 -06:00
|
|
|
/** @internal */
|
2021-02-19 08:07:53 -07:00
|
|
|
displayName = Camera.displayName;
|
2023-07-20 07:30:04 -06:00
|
|
|
private lastFrameProcessor: FrameProcessor | undefined;
|
2021-07-12 07:16:03 -06:00
|
|
|
private isNativeViewMounted = false;
|
2021-02-19 08:07:53 -07:00
|
|
|
|
|
|
|
private readonly ref: React.RefObject<RefType>;
|
|
|
|
|
2021-07-12 07:16:03 -06:00
|
|
|
/** @internal */
|
2021-02-19 08:07:53 -07:00
|
|
|
constructor(props: CameraProps) {
|
|
|
|
super(props);
|
2021-10-11 10:27:23 -06:00
|
|
|
this.onViewReady = this.onViewReady.bind(this);
|
2021-02-19 08:07:53 -07:00
|
|
|
this.onInitialized = this.onInitialized.bind(this);
|
|
|
|
this.onError = this.onError.bind(this);
|
|
|
|
this.ref = React.createRef<RefType>();
|
2021-05-06 06:11:55 -06:00
|
|
|
this.lastFrameProcessor = undefined;
|
2021-02-19 08:07:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private get handle(): number | null {
|
|
|
|
const nodeHandle = findNodeHandle(this.ref.current);
|
2021-09-27 06:35:35 -06:00
|
|
|
if (nodeHandle == null || nodeHandle === -1) {
|
|
|
|
throw new CameraRuntimeError(
|
|
|
|
'system/view-not-found',
|
|
|
|
"Could not get the Camera's native view tag! Does the Camera View exist in the native view-tree?",
|
|
|
|
);
|
|
|
|
}
|
2021-02-19 08:23:54 -07:00
|
|
|
|
2021-02-19 08:07:53 -07:00
|
|
|
return nodeHandle;
|
|
|
|
}
|
|
|
|
|
|
|
|
//#region View-specific functions (UIViewManager)
|
|
|
|
/**
|
|
|
|
* Take a single photo and write it's content to a temporary file.
|
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraCaptureError} When any kind of error occured while capturing the photo. Use the {@linkcode CameraCaptureError.code | code} property to get the actual error
|
2021-03-08 10:30:23 -07:00
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```ts
|
2021-03-08 10:30:23 -07:00
|
|
|
* const photo = await camera.current.takePhoto({
|
|
|
|
* qualityPrioritization: 'quality',
|
|
|
|
* flash: 'on',
|
|
|
|
* enableAutoRedEyeReduction: true
|
|
|
|
* })
|
|
|
|
* ```
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public async takePhoto(options?: TakePhotoOptions): Promise<PhotoFile> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.takePhoto(this.handle, options ?? {});
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Take a snapshot of the current preview view.
|
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* This can be used as an alternative to {@linkcode Camera.takePhoto | takePhoto()} if speed is more important than quality
|
|
|
|
*
|
|
|
|
* @throws {@linkcode CameraCaptureError} When any kind of error occured while taking a snapshot. Use the {@linkcode CameraCaptureError.code | code} property to get the actual error
|
2021-02-19 08:07:53 -07:00
|
|
|
*
|
|
|
|
* @platform Android
|
2021-03-08 10:30:23 -07:00
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```ts
|
2021-03-08 10:30:23 -07:00
|
|
|
* const photo = await camera.current.takeSnapshot({
|
|
|
|
* quality: 85,
|
|
|
|
* skipMetadata: true
|
|
|
|
* })
|
|
|
|
* ```
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public async takeSnapshot(options?: TakeSnapshotOptions): Promise<PhotoFile> {
|
2021-02-19 08:23:54 -07:00
|
|
|
if (Platform.OS !== 'android')
|
|
|
|
throw new CameraCaptureError('capture/capture-type-not-supported', `'takeSnapshot()' is not available on ${Platform.OS}!`);
|
|
|
|
|
2021-02-19 08:07:53 -07:00
|
|
|
try {
|
|
|
|
return await CameraModule.takeSnapshot(this.handle, options ?? {});
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start a new video recording.
|
|
|
|
*
|
|
|
|
* Records in the following formats:
|
|
|
|
* * **iOS**: QuickTime (`.mov`)
|
|
|
|
* * **Android**: MPEG4 (`.mp4`)
|
|
|
|
*
|
|
|
|
* @blocking This function is synchronized/blocking.
|
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraCaptureError} When any kind of error occured while starting the video recording. Use the {@linkcode CameraCaptureError.code | code} property to get the actual error
|
2021-02-19 08:07:53 -07:00
|
|
|
*
|
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```ts
|
2021-02-19 08:07:53 -07:00
|
|
|
* camera.current.startRecording({
|
|
|
|
* onRecordingFinished: (video) => console.log(video),
|
|
|
|
* onRecordingError: (error) => console.error(error),
|
|
|
|
* })
|
|
|
|
* setTimeout(() => {
|
|
|
|
* camera.current.stopRecording()
|
|
|
|
* }, 5000)
|
2021-03-03 04:37:43 -07:00
|
|
|
* ```
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public startRecording(options: RecordVideoOptions): void {
|
2021-02-19 08:23:54 -07:00
|
|
|
const { onRecordingError, onRecordingFinished, ...passThroughOptions } = options;
|
|
|
|
if (typeof onRecordingError !== 'function' || typeof onRecordingFinished !== 'function')
|
|
|
|
throw new CameraRuntimeError('parameter/invalid-parameter', 'The onRecordingError or onRecordingFinished functions were not set!');
|
|
|
|
|
|
|
|
const onRecordCallback = (video?: VideoFile, error?: CameraCaptureError): void => {
|
2021-02-19 08:07:53 -07:00
|
|
|
if (error != null) return onRecordingError(error);
|
|
|
|
if (video != null) return onRecordingFinished(video);
|
|
|
|
};
|
|
|
|
// TODO: Use TurboModules to either make this a sync invokation, or make it async.
|
|
|
|
try {
|
2021-02-19 08:23:54 -07:00
|
|
|
CameraModule.startRecording(this.handle, passThroughOptions, onRecordCallback);
|
2021-02-19 08:07:53 -07:00
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
2022-03-22 03:44:58 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Pauses the current video recording.
|
|
|
|
*
|
|
|
|
* @throws {@linkcode CameraCaptureError} When any kind of error occured while pausing the video recording. Use the {@linkcode CameraCaptureError.code | code} property to get the actual error
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* ```ts
|
|
|
|
* // Start
|
|
|
|
* await camera.current.startRecording()
|
|
|
|
* await timeout(1000)
|
|
|
|
* // Pause
|
|
|
|
* await camera.current.pauseRecording()
|
|
|
|
* await timeout(500)
|
|
|
|
* // Resume
|
|
|
|
* await camera.current.resumeRecording()
|
|
|
|
* await timeout(2000)
|
|
|
|
* // Stop
|
|
|
|
* const video = await camera.current.stopRecording()
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
public async pauseRecording(): Promise<void> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.pauseRecording(this.handle);
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resumes a currently paused video recording.
|
|
|
|
*
|
|
|
|
* @throws {@linkcode CameraCaptureError} When any kind of error occured while resuming the video recording. Use the {@linkcode CameraCaptureError.code | code} property to get the actual error
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* ```ts
|
|
|
|
* // Start
|
|
|
|
* await camera.current.startRecording()
|
|
|
|
* await timeout(1000)
|
|
|
|
* // Pause
|
|
|
|
* await camera.current.pauseRecording()
|
|
|
|
* await timeout(500)
|
|
|
|
* // Resume
|
|
|
|
* await camera.current.resumeRecording()
|
|
|
|
* await timeout(2000)
|
|
|
|
* // Stop
|
|
|
|
* const video = await camera.current.stopRecording()
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
public async resumeRecording(): Promise<void> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.resumeRecording(this.handle);
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-19 08:07:53 -07:00
|
|
|
/**
|
|
|
|
* Stop the current video recording.
|
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraCaptureError} When any kind of error occured while stopping the video recording. Use the {@linkcode CameraCaptureError.code | code} property to get the actual error
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-02-19 08:07:53 -07:00
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```ts
|
2021-02-19 08:07:53 -07:00
|
|
|
* await camera.current.startRecording()
|
|
|
|
* setTimeout(async () => {
|
|
|
|
* const video = await camera.current.stopRecording()
|
|
|
|
* }, 5000)
|
2021-03-03 04:37:43 -07:00
|
|
|
* ```
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public async stopRecording(): Promise<void> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.stopRecording(this.handle);
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Focus the camera to a specific point in the coordinate system.
|
2021-03-03 04:37:43 -07:00
|
|
|
* @param {Point} point The point to focus to. This should be relative to the Camera view's coordinate system,
|
2021-02-19 08:07:53 -07:00
|
|
|
* and expressed in Pixel on iOS and Points on Android.
|
|
|
|
* * `(0, 0)` means **top left**.
|
|
|
|
* * `(CameraView.width, CameraView.height)` means **bottom right**.
|
|
|
|
*
|
|
|
|
* Make sure the value doesn't exceed the CameraView's dimensions.
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while focussing. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
|
2021-03-08 10:30:23 -07:00
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```ts
|
2021-03-08 10:30:23 -07:00
|
|
|
* await camera.current.focus({
|
|
|
|
* x: tapEvent.x,
|
|
|
|
* y: tapEvent.y
|
|
|
|
* })
|
|
|
|
* ```
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public async focus(point: Point): Promise<void> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.focus(this.handle, point);
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//#endregion
|
|
|
|
|
2021-12-30 02:47:23 -07:00
|
|
|
/**
|
|
|
|
* Get a list of video codecs the current camera supports for a given file type. Returned values are ordered by efficiency (descending).
|
|
|
|
* @example
|
|
|
|
* ```ts
|
|
|
|
* const codecs = await camera.current.getAvailableVideoCodecs("mp4")
|
|
|
|
* ```
|
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting available video codecs. Use the {@linkcode ParameterError.code | code} property to get the actual error
|
2021-12-31 04:33:21 -07:00
|
|
|
* @platform iOS
|
2021-12-30 02:47:23 -07:00
|
|
|
*/
|
|
|
|
public async getAvailableVideoCodecs(fileType?: VideoFileType): Promise<CameraVideoCodec[]> {
|
2021-12-31 04:33:21 -07:00
|
|
|
if (Platform.OS !== 'ios') return []; // no video codecs supported on other platforms.
|
|
|
|
|
2021-12-30 02:47:23 -07:00
|
|
|
try {
|
|
|
|
return await CameraModule.getAvailableVideoCodecs(this.handle, fileType);
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-19 08:07:53 -07:00
|
|
|
//#region Static Functions (NativeModule)
|
2023-02-13 07:22:45 -07:00
|
|
|
/**
|
|
|
|
* Install JSI Bindings for Frame Processors
|
|
|
|
*/
|
|
|
|
public static installFrameProcessorBindings(): void {
|
2023-03-21 09:10:09 -06:00
|
|
|
assertJSIAvailable();
|
2023-02-13 07:22:45 -07:00
|
|
|
const result = CameraModule.installFrameProcessorBindings() as unknown;
|
|
|
|
if (result !== true) throw new Error('Failed to install Frame Processor JSI bindings!');
|
|
|
|
}
|
|
|
|
|
2021-02-19 08:07:53 -07:00
|
|
|
/**
|
|
|
|
* Get a list of all available camera devices on the current phone.
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting all available camera devices. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
|
2021-03-08 10:30:23 -07:00
|
|
|
* @example
|
2021-03-09 04:02:10 -07:00
|
|
|
* ```ts
|
2021-03-08 10:30:23 -07:00
|
|
|
* const devices = await Camera.getAvailableCameraDevices()
|
|
|
|
* const filtered = devices.filter((d) => matchesMyExpectations(d))
|
|
|
|
* const sorted = devices.sort(sortDevicesByAmountOfCameras)
|
|
|
|
* return {
|
|
|
|
* back: sorted.find((d) => d.position === "back"),
|
|
|
|
* front: sorted.find((d) => d.position === "front")
|
|
|
|
* }
|
|
|
|
* ```
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public static async getAvailableCameraDevices(): Promise<CameraDevice[]> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.getAvailableCameraDevices();
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Gets the current Camera Permission Status. Check this before mounting the Camera to ensure
|
|
|
|
* the user has permitted the app to use the camera.
|
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* To actually prompt the user for camera permission, use {@linkcode Camera.requestCameraPermission | requestCameraPermission()}.
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting the current permission status. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public static async getCameraPermissionStatus(): Promise<CameraPermissionStatus> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.getCameraPermissionStatus();
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Gets the current Microphone-Recording Permission Status. Check this before mounting the Camera to ensure
|
|
|
|
* the user has permitted the app to use the microphone.
|
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* To actually prompt the user for microphone permission, use {@linkcode Camera.requestMicrophonePermission | requestMicrophonePermission()}.
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting the current permission status. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public static async getMicrophonePermissionStatus(): Promise<CameraPermissionStatus> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.getMicrophonePermissionStatus();
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Shows a "request permission" alert to the user, and resolves with the new camera permission status.
|
|
|
|
*
|
|
|
|
* If the user has previously blocked the app from using the camera, the alert will not be shown
|
|
|
|
* and `"denied"` will be returned.
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while requesting permission. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public static async requestCameraPermission(): Promise<CameraPermissionRequestResult> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.requestCameraPermission();
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Shows a "request permission" alert to the user, and resolves with the new microphone permission status.
|
|
|
|
*
|
|
|
|
* If the user has previously blocked the app from using the microphone, the alert will not be shown
|
|
|
|
* and `"denied"` will be returned.
|
2021-03-08 10:16:45 -07:00
|
|
|
*
|
2021-03-08 10:51:53 -07:00
|
|
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while requesting permission. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
|
2021-02-19 08:07:53 -07:00
|
|
|
*/
|
|
|
|
public static async requestMicrophonePermission(): Promise<CameraPermissionRequestResult> {
|
|
|
|
try {
|
|
|
|
return await CameraModule.requestMicrophonePermission();
|
|
|
|
} catch (e) {
|
|
|
|
throw tryParseNativeCameraError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
//#region Events (Wrapped to maintain reference equality)
|
2021-03-12 05:21:46 -07:00
|
|
|
private onError(event: NativeSyntheticEvent<OnErrorEvent>): void {
|
2021-02-19 08:07:53 -07:00
|
|
|
if (this.props.onError != null) {
|
|
|
|
const error = event.nativeEvent;
|
|
|
|
const cause = isErrorWithCause(error.cause) ? error.cause : undefined;
|
|
|
|
this.props.onError(
|
|
|
|
// @ts-expect-error We're casting from unknown bridge types to TS unions, I expect it to hopefully work
|
2021-02-19 08:23:54 -07:00
|
|
|
new CameraRuntimeError(error.code, error.message, cause),
|
2021-02-19 08:07:53 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-19 08:23:54 -07:00
|
|
|
private onInitialized(): void {
|
2021-02-19 08:07:53 -07:00
|
|
|
this.props.onInitialized?.();
|
|
|
|
}
|
|
|
|
//#endregion
|
|
|
|
|
2021-06-28 10:18:35 -06:00
|
|
|
//#region Lifecycle
|
2023-07-20 07:30:04 -06:00
|
|
|
private setFrameProcessor(frameProcessor: FrameProcessor): void {
|
2023-03-13 07:21:08 -06:00
|
|
|
assertFrameProcessorsAvailable();
|
2021-06-28 10:18:35 -06:00
|
|
|
// @ts-expect-error JSI functions aren't typed
|
|
|
|
global.setFrameProcessor(this.handle, frameProcessor);
|
|
|
|
}
|
|
|
|
|
|
|
|
private unsetFrameProcessor(): void {
|
2023-03-13 07:21:08 -06:00
|
|
|
assertFrameProcessorsAvailable();
|
2021-06-28 10:18:35 -06:00
|
|
|
// @ts-expect-error JSI functions aren't typed
|
2021-06-29 01:05:18 -06:00
|
|
|
global.unsetFrameProcessor(this.handle);
|
2021-06-28 10:18:35 -06:00
|
|
|
}
|
|
|
|
|
2021-10-11 10:27:23 -06:00
|
|
|
private onViewReady(): void {
|
|
|
|
this.isNativeViewMounted = true;
|
|
|
|
if (this.props.frameProcessor != null) {
|
|
|
|
// user passed a `frameProcessor` but we didn't set it yet because the native view was not mounted yet. set it now.
|
|
|
|
this.setFrameProcessor(this.props.frameProcessor);
|
|
|
|
this.lastFrameProcessor = this.props.frameProcessor;
|
|
|
|
}
|
2021-05-06 06:11:55 -06:00
|
|
|
}
|
|
|
|
|
2021-07-12 07:16:03 -06:00
|
|
|
/** @internal */
|
2021-05-06 06:11:55 -06:00
|
|
|
componentDidUpdate(): void {
|
2021-07-12 07:16:03 -06:00
|
|
|
if (!this.isNativeViewMounted) return;
|
|
|
|
const frameProcessor = this.props.frameProcessor;
|
|
|
|
if (frameProcessor !== this.lastFrameProcessor) {
|
2021-06-28 10:18:35 -06:00
|
|
|
// frameProcessor argument identity changed. Update native to reflect the change.
|
2021-07-12 07:16:03 -06:00
|
|
|
if (frameProcessor != null) this.setFrameProcessor(frameProcessor);
|
2021-06-28 10:18:35 -06:00
|
|
|
else this.unsetFrameProcessor();
|
|
|
|
|
2021-07-12 07:16:03 -06:00
|
|
|
this.lastFrameProcessor = frameProcessor;
|
|
|
|
}
|
|
|
|
}
|
2021-06-28 10:18:35 -06:00
|
|
|
//#endregion
|
2021-03-12 05:21:46 -07:00
|
|
|
|
2021-07-12 07:16:03 -06:00
|
|
|
/** @internal */
|
2021-05-06 06:11:55 -06:00
|
|
|
public render(): React.ReactNode {
|
2021-02-19 08:07:53 -07:00
|
|
|
// We remove the big `device` object from the props because we only need to pass `cameraId` to native.
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
const { device, frameProcessor, ...props } = this.props;
|
2023-02-23 06:57:57 -07:00
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
|
|
if (device == null) {
|
|
|
|
throw new Error(
|
|
|
|
'Camera: `device` is null! Select a valid Camera device. See: https://mrousavy.com/react-native-vision-camera/docs/guides/devices',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-27 04:37:54 -06:00
|
|
|
return (
|
|
|
|
<NativeCameraView
|
|
|
|
{...props}
|
|
|
|
cameraId={device.id}
|
|
|
|
ref={this.ref}
|
2021-10-11 10:27:23 -06:00
|
|
|
onViewReady={this.onViewReady}
|
2021-06-27 04:37:54 -06:00
|
|
|
onInitialized={this.onInitialized}
|
|
|
|
onError={this.onError}
|
2021-07-12 07:16:03 -06:00
|
|
|
enableFrameProcessor={frameProcessor != null}
|
2023-07-20 07:30:04 -06:00
|
|
|
previewType={frameProcessor?.type === 'skia-frame-processor' ? 'skia' : 'native'}
|
2021-06-27 04:37:54 -06:00
|
|
|
/>
|
|
|
|
);
|
2021-02-19 08:07:53 -07:00
|
|
|
}
|
|
|
|
}
|
2021-03-23 10:15:09 -06:00
|
|
|
//#endregion
|
2021-02-19 08:07:53 -07:00
|
|
|
|
|
|
|
// requireNativeComponent automatically resolves 'CameraView' to 'CameraViewManager'
|
2021-03-12 05:21:46 -07:00
|
|
|
const NativeCameraView = requireNativeComponent<NativeCameraViewProps>(
|
2021-02-19 08:23:54 -07:00
|
|
|
'CameraView',
|
2021-02-19 08:07:53 -07:00
|
|
|
// @ts-expect-error because the type declarations are kinda wrong, no?
|
2021-02-19 08:23:54 -07:00
|
|
|
Camera,
|
2021-02-19 08:07:53 -07:00
|
|
|
);
|