Bootstrap
This commit is contained in:
69
src/ANIMATED.md
Normal file
69
src/ANIMATED.md
Normal file
@@ -0,0 +1,69 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th><a href="./README.md">README.md</a></th>
|
||||
<th>ANIMATED.md</th>
|
||||
<th><a href="./DEVICES.md">DEVICES.md</a></th>
|
||||
<th><a href="./FORMATS.md">FORMATS.md</a></th>
|
||||
<th><a href="./ERRORS.md">ERRORS.md</a></th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
## Animations
|
||||
|
||||
Often you'd want to animate specific props in the Camera. For example, if you'd want to create a custom zoom gesture, you can smoothly animate the Camera's `zoom` property.
|
||||
|
||||
The following example uses [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated) (v2) to animate the `zoom` property:
|
||||
|
||||
|
||||
```tsx
|
||||
import Reanimated, {
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
} from "react-native-reanimated";
|
||||
|
||||
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
||||
Reanimated.addWhitelistedNativeProps({
|
||||
zoom: true,
|
||||
});
|
||||
|
||||
export const App = () => {
|
||||
const zoom = useSharedValue(0);
|
||||
|
||||
const onRandomZoomPress = useCallback(() => {
|
||||
zoom.value = withSpring(Math.random());
|
||||
}, []);
|
||||
|
||||
const animatedProps = useAnimatedProps<Partial<CameraProps>>(
|
||||
() => ({ zoom: zoom.value }),
|
||||
[zoom]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReanimatedCamera
|
||||
style={StyleSheet.absoluteFill}
|
||||
device={device}
|
||||
isActive={true}
|
||||
{...{ animatedProps }}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={styles.zoomButton}
|
||||
onPress={onRandomZoomPress}>
|
||||
<Text>Zoom randomly!</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
1. The `Camera` is converted to a reanimated Camera using `Reanimated.createAnimatedComponent`
|
||||
2. The `zoom` property is added to the whitelisted native props to make it animatable.
|
||||
> Note that this might not be needed in the future, see: [reanimated#1409](https://github.com/software-mansion/react-native-reanimated/pull/1409)
|
||||
3. Using [`useSharedValue`](https://docs.swmansion.com/react-native-reanimated/docs/api/useSharedValue), we're creating a shared value that holds the `zoom` property.
|
||||
4. Using the [`useAnimatedProps`](https://docs.swmansion.com/react-native-reanimated/docs/api/useAnimatedProps) hook, we apply the shared value to the animated props.
|
||||
5. We apply the animated props to the `ReanimatedCamera` component using the spread syntax.
|
510
src/Camera.tsx
Normal file
510
src/Camera.tsx
Normal file
@@ -0,0 +1,510 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import React from "react";
|
||||
import {
|
||||
requireNativeComponent,
|
||||
NativeModules,
|
||||
ViewProps,
|
||||
NativeSyntheticEvent,
|
||||
findNodeHandle,
|
||||
NativeMethods,
|
||||
Platform,
|
||||
} from "react-native";
|
||||
import { CameraPhotoCodec, CameraVideoCodec } from "./CameraCodec";
|
||||
import { ColorSpace, CameraDeviceFormat, CameraDevice } from "./CameraDevice";
|
||||
import {
|
||||
CameraCaptureError,
|
||||
CameraRuntimeError,
|
||||
tryParseNativeCameraError,
|
||||
ErrorWithCause,
|
||||
isErrorWithCause,
|
||||
} from "./CameraError";
|
||||
import { CameraPreset } from "./CameraPreset";
|
||||
import { CodeType, Code } from "./Code";
|
||||
import { PhotoFile, TakePhotoOptions } from "./PhotoFile";
|
||||
import { Point } from "./Point";
|
||||
import { TakeSnapshotOptions } from "./Snapshot";
|
||||
import { RecordVideoOptions, VideoFile } from "./VideoFile";
|
||||
|
||||
//#region Types
|
||||
type Modify<T, R> = Omit<T, keyof R> & R;
|
||||
|
||||
type CameraFormatProps = {
|
||||
/**
|
||||
* Automatically selects a camera format which best matches the given preset
|
||||
*/
|
||||
preset?: CameraPreset;
|
||||
/**
|
||||
* Specify the frames per second this camera should use. Make sure the given `format` includes a frame rate range with the given `fps`.
|
||||
*/
|
||||
fps?: never;
|
||||
/**
|
||||
* Enables or disables HDR on this camera device. Make sure the given `format` supports HDR mode.
|
||||
*/
|
||||
hdr?: never;
|
||||
/**
|
||||
* Enables or disables low-light boost on this camera device. Make sure the given `format` supports low-light boost.
|
||||
*/
|
||||
lowLightBoost?: never;
|
||||
/**
|
||||
* Specifies the color space to use for this camera device. Make sure the given `format` contains the given `colorSpace`.
|
||||
*/
|
||||
colorSpace?: never;
|
||||
/**
|
||||
* Selects a given format.
|
||||
*/
|
||||
format?: never;
|
||||
};
|
||||
type CameraPresetProps = Modify<
|
||||
CameraFormatProps,
|
||||
{
|
||||
preset?: never;
|
||||
fps?: number;
|
||||
hdr?: boolean;
|
||||
lowLightBoost?: boolean;
|
||||
colorSpace?: ColorSpace;
|
||||
format?: CameraDeviceFormat;
|
||||
}
|
||||
>;
|
||||
|
||||
type CameraScannerPropsNever = {
|
||||
/**
|
||||
* Specify the code types this camera can scan.
|
||||
*/
|
||||
scannableCodes?: never;
|
||||
/**
|
||||
* Called when one or multiple codes have been scanned.
|
||||
*/
|
||||
onCodeScanned?: never;
|
||||
};
|
||||
export type CameraScannerProps = Modify<
|
||||
CameraScannerPropsNever,
|
||||
{
|
||||
scannableCodes: CodeType[];
|
||||
onCodeScanned: (codes: Code[]) => void;
|
||||
}
|
||||
>;
|
||||
|
||||
export type CameraDeviceProps = {
|
||||
// Properties
|
||||
/**
|
||||
* The Camera Device to use
|
||||
*/
|
||||
device: CameraDevice;
|
||||
/**
|
||||
* Also captures data from depth-perception sensors. (e.g. disparity maps)
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableDepthData?: boolean;
|
||||
/**
|
||||
* A boolean specifying whether the photo render pipeline is prepared for portrait effects matte delivery.
|
||||
*
|
||||
* When enabling this, you must also set `enableDepthData` to `true`.
|
||||
*
|
||||
* @platform iOS 12.0+
|
||||
* @default false
|
||||
*/
|
||||
enablePortraitEffectsMatteDelivery?: boolean;
|
||||
/**
|
||||
* Indicates whether the photo render pipeline should be configured to deliver high resolution still images
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableHighResolutionCapture?: boolean;
|
||||
};
|
||||
|
||||
export type CameraDynamicProps = {
|
||||
/**
|
||||
* `true` enables streaming from the camera's input stream, `false` "pauses" the camera input stream.
|
||||
*/
|
||||
isActive: boolean;
|
||||
/**
|
||||
* Set the current torch mode.
|
||||
*
|
||||
* Note: The torch is only available on `"back"` cameras, and isn't supported by every phone.
|
||||
*
|
||||
* @default "off"
|
||||
*/
|
||||
torch?: "off" | "on";
|
||||
/**
|
||||
* Specifies the zoom factor of the current camera, in percent. (`0.0` - `1.0`)
|
||||
*
|
||||
* @default 0.0
|
||||
*/
|
||||
zoom?: number;
|
||||
/**
|
||||
* Enables or disables the pinch to zoom gesture
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableZoomGesture?: boolean;
|
||||
};
|
||||
|
||||
export type CameraEventProps = {
|
||||
/**
|
||||
* Called when any kind of runtime error occured.
|
||||
*/
|
||||
onError?: (error: CameraRuntimeError) => void;
|
||||
/**
|
||||
* Called when the camera was successfully initialized.
|
||||
*/
|
||||
onInitialized?: () => void;
|
||||
};
|
||||
|
||||
export type CameraProps = (CameraPresetProps | CameraFormatProps) &
|
||||
(CameraScannerPropsNever | CameraScannerProps) &
|
||||
CameraDeviceProps &
|
||||
CameraDynamicProps &
|
||||
CameraEventProps &
|
||||
ViewProps;
|
||||
|
||||
export type CameraPermissionStatus =
|
||||
| "authorized"
|
||||
| "not-determined"
|
||||
| "denied"
|
||||
| "restricted";
|
||||
export type CameraPermissionRequestResult = "authorized" | "denied";
|
||||
|
||||
interface OnErrorEvent {
|
||||
code: string;
|
||||
message: string;
|
||||
cause?: ErrorWithCause;
|
||||
}
|
||||
interface OnCodeScannedEvent {
|
||||
codes: Code[];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
// NativeModules automatically resolves 'CameraView' to 'CameraViewModule'
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const CameraModule = NativeModules.CameraView;
|
||||
if (CameraModule == null) {
|
||||
console.error(
|
||||
`Camera: Native Module 'CameraView' was null! Did you run pod install?`
|
||||
);
|
||||
}
|
||||
|
||||
interface CameraState {
|
||||
/**
|
||||
* The actual native ID for the camera device.
|
||||
*/
|
||||
cameraId?: string;
|
||||
}
|
||||
|
||||
type RefType = React.Component<CameraProps> & Readonly<NativeMethods>;
|
||||
|
||||
/**
|
||||
* ### A powerful `<Camera>` component.
|
||||
*/
|
||||
export class Camera extends React.PureComponent<CameraProps, CameraState> {
|
||||
static displayName = "Camera";
|
||||
displayName = Camera.displayName;
|
||||
|
||||
private readonly ref: React.RefObject<RefType>;
|
||||
|
||||
constructor(props: CameraProps) {
|
||||
super(props);
|
||||
this.state = { cameraId: undefined };
|
||||
this.onInitialized = this.onInitialized.bind(this);
|
||||
this.onError = this.onError.bind(this);
|
||||
this.onCodeScanned = this.onCodeScanned.bind(this);
|
||||
this.ref = React.createRef<RefType>();
|
||||
}
|
||||
|
||||
private get handle(): number | null {
|
||||
const nodeHandle = findNodeHandle(this.ref.current);
|
||||
if (nodeHandle == null) {
|
||||
console.error(
|
||||
`Camera: findNodeHandle(ref) returned null! Does the Camera view exist in the native view tree?`
|
||||
);
|
||||
}
|
||||
return nodeHandle;
|
||||
}
|
||||
|
||||
//#region View-specific functions (UIViewManager)
|
||||
/**
|
||||
* Take a single photo and write it's content to a temporary file.
|
||||
*
|
||||
* @throws {CameraCaptureError} When any kind of error occured. Use the `CameraCaptureError.code` property to get the actual error
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This can be used as an alternative to `takePhoto()` if speed is more important than quality
|
||||
*
|
||||
* @platform Android
|
||||
*/
|
||||
public async takeSnapshot(options?: TakeSnapshotOptions): Promise<PhotoFile> {
|
||||
if (Platform.OS !== "android")
|
||||
throw new CameraCaptureError(
|
||||
"capture/capture-type-not-supported",
|
||||
`'takeSnapshot()' is not available on ${Platform.OS}!`
|
||||
);
|
||||
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.
|
||||
*
|
||||
* @throws {CameraCaptureError} When any kind of error occured. Use the `CameraCaptureError.code` property to get the actual error
|
||||
*
|
||||
* @example
|
||||
* camera.current.startRecording({
|
||||
* onRecordingFinished: (video) => console.log(video),
|
||||
* onRecordingError: (error) => console.error(error),
|
||||
* })
|
||||
* setTimeout(() => {
|
||||
* camera.current.stopRecording()
|
||||
* }, 5000)
|
||||
*/
|
||||
public startRecording(options: RecordVideoOptions): void {
|
||||
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
|
||||
) => {
|
||||
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 {
|
||||
CameraModule.startRecording(
|
||||
this.handle,
|
||||
passThroughOptions,
|
||||
onRecordCallback
|
||||
);
|
||||
} catch (e) {
|
||||
throw tryParseNativeCameraError(e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stop the current video recording.
|
||||
*
|
||||
* @example
|
||||
* await camera.current.startRecording()
|
||||
* setTimeout(async () => {
|
||||
* const video = await camera.current.stopRecording()
|
||||
* }, 5000)
|
||||
*/
|
||||
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.
|
||||
* @param point The point to focus to. This should be relative to the Camera view's coordinate system,
|
||||
* 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.
|
||||
*/
|
||||
public async focus(point: Point): Promise<void> {
|
||||
try {
|
||||
return await CameraModule.focus(this.handle, point);
|
||||
} catch (e) {
|
||||
throw tryParseNativeCameraError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of video codecs the current camera supports. Returned values are ordered by efficiency (descending).
|
||||
*
|
||||
* This function can only be called after the camera has been initialized,
|
||||
* so only use this after the `onInitialized` event has fired.
|
||||
*/
|
||||
public async getAvailableVideoCodecs(): Promise<CameraVideoCodec[]> {
|
||||
try {
|
||||
return await CameraModule.getAvailableVideoCodecs(this.handle);
|
||||
} catch (e) {
|
||||
throw tryParseNativeCameraError(e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get a list of photo codecs the current camera supports. Returned values are ordered by efficiency (descending).
|
||||
*
|
||||
* This function can only be called after the camera has been initialized,
|
||||
* so only use this after the `onInitialized` event has fired.
|
||||
*/
|
||||
public async getAvailablePhotoCodecs(): Promise<CameraPhotoCodec[]> {
|
||||
try {
|
||||
return await CameraModule.getAvailablePhotoCodecs(this.handle);
|
||||
} catch (e) {
|
||||
throw tryParseNativeCameraError(e);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Static Functions (NativeModule)
|
||||
/**
|
||||
* Get a list of all available camera devices on the current phone.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* To actually prompt the user for camera permission, use `Camera.requestCameraPermission()`.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* To actually prompt the user for microphone permission, use `Camera.requestMicrophonePermission()`.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public static async requestMicrophonePermission(): Promise<CameraPermissionRequestResult> {
|
||||
try {
|
||||
return await CameraModule.requestMicrophonePermission();
|
||||
} catch (e) {
|
||||
throw tryParseNativeCameraError(e);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Events (Wrapped to maintain reference equality)
|
||||
private onError(event?: NativeSyntheticEvent<OnErrorEvent>) {
|
||||
if (event == null) {
|
||||
throw new Error("onError() was invoked but event was null!");
|
||||
}
|
||||
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
|
||||
new CameraRuntimeError(error.code, error.message, cause)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private onInitialized() {
|
||||
this.props.onInitialized?.();
|
||||
}
|
||||
|
||||
private onCodeScanned(event?: NativeSyntheticEvent<OnCodeScannedEvent>) {
|
||||
if (event == null) {
|
||||
throw new Error("onCodeScanned() was invoked but event was null!");
|
||||
}
|
||||
if (this.props.onCodeScanned == null) {
|
||||
console.warn(
|
||||
"Camera: onCodeScanned event was invoked but no listeners attached! Did you forget to remove the `scannableCodes` property?"
|
||||
);
|
||||
} else {
|
||||
this.props.onCodeScanned(event.nativeEvent.codes);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: CameraProps,
|
||||
state: CameraState
|
||||
): CameraState | null {
|
||||
const newCameraId = props.device.id;
|
||||
if (state.cameraId !== newCameraId) {
|
||||
return { ...state, cameraId: newCameraId };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
// We remove the big `device` object from the props because we only need to pass `cameraId` to native.
|
||||
const { device: _, ...props } = this.props;
|
||||
return (
|
||||
<NativeCameraView
|
||||
{...props}
|
||||
cameraId={this.state.cameraId}
|
||||
ref={this.ref}
|
||||
onInitialized={this.onInitialized}
|
||||
// @ts-expect-error with our callback wrapping we have to extract NativeSyntheticEvent params
|
||||
onError={this.onError}
|
||||
// @ts-expect-error with our callback wrapping we have to extract NativeSyntheticEvent params
|
||||
onCodeScanned={this.onCodeScanned}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// requireNativeComponent automatically resolves 'CameraView' to 'CameraViewManager'
|
||||
const NativeCameraView = requireNativeComponent<CameraProps>(
|
||||
"CameraView",
|
||||
// @ts-expect-error because the type declarations are kinda wrong, no?
|
||||
Camera
|
||||
);
|
33
src/CameraCodec.ts
Normal file
33
src/CameraCodec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Available Video Codec types used for recording a video.
|
||||
*
|
||||
* * `"hevc"`: The HEVC video codec. _(iOS 11.0+)_
|
||||
* * `"h264"`: The H.264 (`avc1`) video codec. _(iOS 11.0+)_
|
||||
* * `"jpeg"`: The JPEG (`jpeg`) video codec. _(iOS 11.0+)_
|
||||
* * `"pro-res-4444"`: The Apple ProRes 4444 (`ap4h`) video codec. _(iOS 11.0+)_
|
||||
* * `"pro-res-422"`: The Apple ProRes 422 (`apcn`) video codec. _(iOS 11.0+)_
|
||||
* * `"pro-res-422-hq"`: The Apple ProRes 422 HQ (`apch`) video codec. _(iOS 13.0+)_
|
||||
* * `"pro-res-422-lt"`: The Apple ProRes 422 LT (`apcs`) video codec. _(iOS 13.0+)_
|
||||
* * `"pro-res-422-proxy"`: The Apple ProRes 422 Proxy (`apco`) video codec. _(iOS 13.0+)_
|
||||
* * `"hevc-alpha"`: The HEVC (`muxa`) video codec that supports an alpha channel. This constant is used to select the appropriate encoder, but is NOT used on the encoded content, which is backwards compatible and hence uses `"hvc1"` as its codec type. _(iOS 13.0+)_
|
||||
*/
|
||||
export type CameraVideoCodec =
|
||||
| "h264"
|
||||
| "hevc"
|
||||
| "hevc-alpha"
|
||||
| "jpeg"
|
||||
| "pro-res-4444"
|
||||
| "pro-res-422"
|
||||
| "pro-res-422-hq"
|
||||
| "pro-res-422-lt"
|
||||
| "pro-res-422-proxy";
|
||||
|
||||
// TODO: Support RAW photo codec
|
||||
/**
|
||||
* Available Photo Codec types used for taking a photo.
|
||||
*
|
||||
* * `"hevc"`: The HEVC video codec. _(iOS 11.0+)_
|
||||
* * `"jpeg"`: The JPEG (`jpeg`) video codec. _(iOS 11.0+)_
|
||||
* * `"hevc-alpha"`: The HEVC (`muxa`) video codec that supports an alpha channel. This constant is used to select the appropriate encoder, but is NOT used on the encoded content, which is backwards compatible and hence uses `"hvc1"` as its codec type. _(iOS 13.0+)_
|
||||
*/
|
||||
export type CameraPhotoCodec = "hevc" | "jpeg" | "hevc-alpha";
|
251
src/CameraDevice.ts
Normal file
251
src/CameraDevice.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { CameraPosition } from "./CameraPosition";
|
||||
|
||||
/**
|
||||
* Indentifiers for a physical camera (one that actually exists on the back/front of the device)
|
||||
*
|
||||
* * `"ultra-wide-angle-camera"`: A built-in camera with a shorter focal length than that of a wide-angle camera. (focal length between below 24mm)
|
||||
* * `"wide-angle-camera"`: A built-in wide-angle camera. (focal length between 24mm and 35mm)
|
||||
* * `"telephoto-camera"`: A built-in camera device with a longer focal length than a wide-angle camera. (focal length between above 85mm)
|
||||
*/
|
||||
export type PhysicalCameraDeviceType =
|
||||
| "ultra-wide-angle-camera"
|
||||
| "wide-angle-camera"
|
||||
| "telephoto-camera";
|
||||
|
||||
/**
|
||||
* Indentifiers for a logical camera (Combinations of multiple physical cameras to create a single logical camera).
|
||||
*
|
||||
* * `"dual-camera"`: A combination of wide-angle and telephoto cameras that creates a capture device.
|
||||
* * `"dual-wide-camera"`: A device that consists of two cameras of fixed focal length, one ultrawide angle and one wide angle.
|
||||
* * `"triple-camera"`: A device that consists of three cameras of fixed focal length, one ultrawide angle, one wide angle, and one telephoto.
|
||||
* * `"true-depth-camera"`: A combination of cameras and other sensors that creates a capture device capable of photo, video, and depth capture.
|
||||
*/
|
||||
export type LogicalCameraDeviceType =
|
||||
| "dual-camera"
|
||||
| "dual-wide-camera"
|
||||
| "triple-camera"
|
||||
| "true-depth-camera";
|
||||
|
||||
/**
|
||||
* Parses an array of physical device types into a single `PhysicalCameraDeviceType` or `LogicalCameraDeviceType`, depending what matches.
|
||||
*/
|
||||
export const parsePhysicalDeviceTypes = (
|
||||
physicalDeviceTypes: PhysicalCameraDeviceType[]
|
||||
): PhysicalCameraDeviceType | LogicalCameraDeviceType => {
|
||||
if (physicalDeviceTypes.length === 1) {
|
||||
return physicalDeviceTypes[0];
|
||||
}
|
||||
const hasWide = physicalDeviceTypes.includes("wide-angle-camera");
|
||||
const hasUltra = physicalDeviceTypes.includes("ultra-wide-angle-camera");
|
||||
const hasTele = physicalDeviceTypes.includes("telephoto-camera");
|
||||
if (hasTele && hasWide && hasUltra) {
|
||||
return "triple-camera";
|
||||
}
|
||||
if (hasWide && hasUltra) {
|
||||
return "dual-wide-camera";
|
||||
}
|
||||
if (hasWide && hasTele) {
|
||||
return "dual-camera";
|
||||
}
|
||||
throw new Error(
|
||||
`Invalid physical device type combination! ${physicalDeviceTypes.join(
|
||||
" + "
|
||||
)}`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates a format's color space.
|
||||
*
|
||||
* #### The following colorspaces are available on iOS:
|
||||
* * `"srgb"`: The sGRB color space (https://www.w3.org/Graphics/Color/srgb)
|
||||
* * `"p3-d65"`: The P3 D65 wide color space which uses Illuminant D65 as the white point
|
||||
* * `"hlg-bt2020"`: The BT2020 wide color space which uses Illuminant D65 as the white point and Hybrid Log-Gamma as the transfer function
|
||||
*
|
||||
* #### The following colorspaces are available on Android:
|
||||
* * `"yuv"`: The YCbCr color space.
|
||||
*/
|
||||
export type ColorSpace = "hlg-bt2020" | "p3-d65" | "srgb" | "yuv";
|
||||
|
||||
/**
|
||||
* Indicates a format's autofocus system.
|
||||
*
|
||||
* * `"none"`: Indicates that autofocus is not available
|
||||
* * `"contrast-detection"`: Indicates that autofocus is achieved by contrast detection. Contrast detection performs a focus scan to find the optimal position
|
||||
* * `"phase-detection"`: Indicates that autofocus is achieved by phase detection. Phase detection has the ability to achieve focus in many cases without a focus scan. Phase detection autofocus is typically less visually intrusive than contrast detection autofocus
|
||||
*/
|
||||
export type AutoFocusSystem = "contrast-detection" | "phase-detection" | "none";
|
||||
|
||||
/**
|
||||
* Indicates a format's supported video stabilization mode
|
||||
*
|
||||
* * `"off"`: Indicates that video should not be stabilized
|
||||
* * `"standard"`: Indicates that video should be stabilized using the standard video stabilization algorithm introduced with iOS 5.0. Standard video stabilization has a reduced field of view. Enabling video stabilization may introduce additional latency into the video capture pipeline
|
||||
* * `"cinematic"`: Indicates that video should be stabilized using the cinematic stabilization algorithm for more dramatic results. Cinematic video stabilization has a reduced field of view compared to standard video stabilization. Enabling cinematic video stabilization introduces much more latency into the video capture pipeline than standard video stabilization and consumes significantly more system memory. Use narrow or identical min and max frame durations in conjunction with this mode
|
||||
* * `"cinematic-extended"`: Indicates that the video should be stabilized using the extended cinematic stabilization algorithm. Enabling extended cinematic stabilization introduces longer latency into the video capture pipeline compared to the AVCaptureVideoStabilizationModeCinematic and consumes more memory, but yields improved stability. It is recommended to use identical or similar min and max frame durations in conjunction with this mode (iOS 13.0+)
|
||||
* * `"auto"`: Indicates that the most appropriate video stabilization mode for the device and format should be chosen automatically
|
||||
*/
|
||||
export type VideoStabilizationMode =
|
||||
| "off"
|
||||
| "standard"
|
||||
| "cinematic"
|
||||
| "cinematic-extended"
|
||||
| "auto";
|
||||
|
||||
export type FrameRateRange = Readonly<{
|
||||
minFrameRate: number;
|
||||
maxFrameRate: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* A Camera Device's video format. Do not create instances of this type yourself, only use `Camera.getAvailableCameraDevices(...)`.
|
||||
*/
|
||||
export type CameraDeviceFormat = Readonly<{
|
||||
/**
|
||||
* The height of the highest resolution a still image (photo) can be produced in
|
||||
*/
|
||||
photoHeight: number;
|
||||
/**
|
||||
* The width of the highest resolution a still image (photo) can be produced in
|
||||
*/
|
||||
photoWidth: number;
|
||||
/**
|
||||
* The video resolutions's height
|
||||
*
|
||||
* @platform iOS 13.0
|
||||
*/
|
||||
videoHeight?: number;
|
||||
/**
|
||||
* The video resolution's width
|
||||
*
|
||||
* @platform iOS 13.0
|
||||
*/
|
||||
videoWidth?: number;
|
||||
/**
|
||||
* A boolean value specifying whether this format supports the highest possible photo quality that can be delivered on the current platform.
|
||||
*
|
||||
* @platform iOS 13.0+
|
||||
*/
|
||||
isHighestPhotoQualitySupported?: boolean;
|
||||
/**
|
||||
* Maximum supported ISO value
|
||||
*/
|
||||
maxISO: number;
|
||||
/**
|
||||
* Minimum supported ISO value
|
||||
*/
|
||||
minISO: number;
|
||||
/**
|
||||
* The video field of view in degrees
|
||||
*/
|
||||
fieldOfView: number;
|
||||
/**
|
||||
* The maximum zoom factor
|
||||
*/
|
||||
maxZoom: number;
|
||||
/**
|
||||
* The available color spaces.
|
||||
*
|
||||
* Note: On Android, this will always be only `["yuv"]`
|
||||
*/
|
||||
colorSpaces: ColorSpace[];
|
||||
/**
|
||||
* Specifies whether this format supports HDR mode for video capture
|
||||
*/
|
||||
supportsVideoHDR: boolean;
|
||||
/**
|
||||
* Specifies whether this format supports HDR mode for photo capture
|
||||
*/
|
||||
supportsPhotoHDR: boolean;
|
||||
/**
|
||||
* All available frame rate ranges. You can query this to find the highest frame rate available
|
||||
*/
|
||||
frameRateRanges: FrameRateRange[];
|
||||
/**
|
||||
* Specifies this format's auto focus system.
|
||||
*/
|
||||
autoFocusSystem: AutoFocusSystem;
|
||||
/**
|
||||
* All supported video stabilization modes
|
||||
*/
|
||||
videoStabilizationModes: VideoStabilizationMode[];
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Represents a camera device discovered by the `Camera.getAvailableCameraDevices()` function
|
||||
*/
|
||||
export type CameraDevice = Readonly<{
|
||||
/**
|
||||
* The native ID of the camera device instance.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The physical devices this `CameraDevice` contains.
|
||||
*
|
||||
* * If this camera device is a **logical camera** (combination of multiple physical cameras), there are multiple cameras in this array.
|
||||
* * If this camera device is a **physical camera**, there is only a single element in this array.
|
||||
*
|
||||
* You can check if the camera is a logical multi-camera by using the `isMultiCam` property.
|
||||
*/
|
||||
devices: PhysicalCameraDeviceType[];
|
||||
/**
|
||||
* Specifies the physical position of this camera. (back or front)
|
||||
*/
|
||||
position: CameraPosition;
|
||||
/**
|
||||
* A friendly localized name describing the camera.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Specifies whether this camera supports enabling flash for photo capture.
|
||||
*/
|
||||
hasFlash: boolean;
|
||||
/**
|
||||
* Specifies whether this camera supports continuously enabling the flash to act like a torch (flash with video capture)
|
||||
*/
|
||||
hasTorch: boolean;
|
||||
/**
|
||||
* A property indicating whether the receiver is a logical camera consisting of multiple physical cameras.
|
||||
*
|
||||
* Examples:
|
||||
* * The Dual Camera, which supports seamlessly switching between a wide and telephoto camera while zooming and generating depth data from the disparities between the different points of view of the physical cameras.
|
||||
* * The TrueDepth Camera, which generates depth data from disparities between a YUV camera and an Infrared camera pointed in the same direction.
|
||||
*/
|
||||
isMultiCam: boolean;
|
||||
/**
|
||||
* Minimum available zoom factor
|
||||
*/
|
||||
minZoom: number;
|
||||
/**
|
||||
* Maximum available zoom factor
|
||||
*/
|
||||
maxZoom: number;
|
||||
/**
|
||||
* The zoom percentage (`0.0`-`1.0`) where the camera is "neutral".
|
||||
*
|
||||
* * For single-physical cameras this property is always `0.0`.
|
||||
* * For multi cameras this property is a value between `0.0` and `1.0`, where the camera is in wide-angle mode and hasn't switched to the ultra-wide (`0.5`x zoom) or telephoto camera yet.
|
||||
*
|
||||
* Use this value as an initial value for the zoom property if you implement custom zoom. (e.g. reanimated shared value should be initially set to this value)
|
||||
*/
|
||||
neutralZoom: number;
|
||||
/**
|
||||
* All available formats for this camera device. Use this to find the best format for your use case and set it to the Camera's `format` property.
|
||||
*/
|
||||
formats: CameraDeviceFormat[];
|
||||
/**
|
||||
* Whether this camera device supports low light boost.
|
||||
*/
|
||||
supportsLowLightBoost: boolean;
|
||||
|
||||
// TODO: supportsDepthCapture
|
||||
// /**
|
||||
// * Whether this camera supports taking photos with depth data
|
||||
// */
|
||||
// supportsDepthCapture: boolean;
|
||||
// TODO: supportsRawCapture
|
||||
// /**
|
||||
// * Whether this camera supports taking photos in RAW format
|
||||
// */
|
||||
// supportsRawCapture: boolean;
|
||||
}>;
|
174
src/CameraError.ts
Normal file
174
src/CameraError.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
export type PermissionError =
|
||||
| "permission/microphone-permission-denied"
|
||||
| "permission/camera-permission-denied";
|
||||
export type ParameterError =
|
||||
| "parameter/invalid-parameter"
|
||||
| "parameter/unsupported-os"
|
||||
| "parameter/unsupported-output"
|
||||
| "parameter/unsupported-input"
|
||||
| "parameter/invalid-combination";
|
||||
export type DeviceError =
|
||||
| "device/configuration-error"
|
||||
| "device/no-device"
|
||||
| "device/invalid-device"
|
||||
| "device/torch-unavailable"
|
||||
| "device/microphone-unavailable"
|
||||
| "device/low-light-boost-not-supported"
|
||||
| "device/focus-not-supported"
|
||||
| "device/camera-not-available-on-simulator";
|
||||
export type FormatError =
|
||||
| "format/invalid-fps"
|
||||
| "format/invalid-hdr"
|
||||
| "format/invalid-low-light-boost"
|
||||
| "format/invalid-format"
|
||||
| "format/invalid-preset";
|
||||
export type SessionError = "session/camera-not-ready";
|
||||
export type CaptureError =
|
||||
| "capture/invalid-photo-format"
|
||||
| "capture/encoder-error"
|
||||
| "capture/muxer-error"
|
||||
| "capture/recording-in-progress"
|
||||
| "capture/no-recording-in-progress"
|
||||
| "capture/file-io-error"
|
||||
| "capture/create-temp-file-error"
|
||||
| "capture/invalid-photo-codec"
|
||||
| "capture/not-bound-error"
|
||||
| "capture/capture-type-not-supported"
|
||||
| "capture/unknown";
|
||||
export type SystemError = "system/no-camera-manager";
|
||||
export type UnknownError = "unknown/unknown";
|
||||
|
||||
export interface ErrorWithCause {
|
||||
/**
|
||||
* The native error description (Localized on iOS)
|
||||
*
|
||||
* * iOS: `NSError.message`
|
||||
* * Android: `Throwable.message`
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* Optional additional details
|
||||
*
|
||||
* * iOS: `NSError.userInfo`
|
||||
* * Android: N/A
|
||||
*/
|
||||
details?: Record<string, unknown>;
|
||||
/**
|
||||
* Optional stacktrace
|
||||
*
|
||||
* * iOS: N/A
|
||||
* * Android: `Throwable.stacktrace.toString()`
|
||||
*/
|
||||
stacktrace?: string;
|
||||
/**
|
||||
* Optional additional cause for nested errors
|
||||
*
|
||||
* * iOS: N/A
|
||||
* * Android: `Throwable.cause`
|
||||
*/
|
||||
cause?: ErrorWithCause;
|
||||
}
|
||||
|
||||
type CameraErrorCode =
|
||||
| PermissionError
|
||||
| ParameterError
|
||||
| DeviceError
|
||||
| FormatError
|
||||
| SessionError
|
||||
| CaptureError
|
||||
| SystemError
|
||||
| UnknownError;
|
||||
|
||||
/**
|
||||
* Represents any kind of error that occured in the Camera View Module.
|
||||
*/
|
||||
class CameraError<TCode extends CameraErrorCode> extends Error {
|
||||
private readonly _code: TCode;
|
||||
private readonly _message: string;
|
||||
private readonly _cause?: ErrorWithCause;
|
||||
|
||||
public get code(): TCode {
|
||||
return this._code;
|
||||
}
|
||||
public get message(): string {
|
||||
return this._message;
|
||||
}
|
||||
public get cause(): ErrorWithCause | undefined {
|
||||
return this._cause;
|
||||
}
|
||||
|
||||
constructor(code: TCode, message: string, cause?: ErrorWithCause) {
|
||||
super(`[${code}]: ${message}${cause ? ` (Cause: ${cause.message})` : ""}`);
|
||||
this._code = code;
|
||||
this._message = message;
|
||||
this._cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents any kind of error that occured while trying to capture a video or photo.
|
||||
*/
|
||||
export class CameraCaptureError extends CameraError<CaptureError> {}
|
||||
|
||||
/**
|
||||
* Represents any kind of error that occured in the Camera View Module.
|
||||
*/
|
||||
export class CameraRuntimeError extends CameraError<
|
||||
| PermissionError
|
||||
| ParameterError
|
||||
| DeviceError
|
||||
| FormatError
|
||||
| SessionError
|
||||
| SystemError
|
||||
| UnknownError
|
||||
> {}
|
||||
|
||||
export const isErrorWithCause = (error: unknown): error is ErrorWithCause =>
|
||||
typeof error === "object" &&
|
||||
error != null &&
|
||||
// @ts-expect-error error is still unknown
|
||||
typeof error.message === "string" &&
|
||||
// @ts-expect-error error is still unknown
|
||||
(typeof error.stacktrace === "string" || error.stacktrace == null) &&
|
||||
// @ts-expect-error error is still unknown
|
||||
(isErrorWithCause(error.cause) || error.cause == null);
|
||||
|
||||
const isCameraErrorJson = (
|
||||
error: unknown
|
||||
): error is { code: string; message: string; cause?: ErrorWithCause } =>
|
||||
typeof error === "object" &&
|
||||
error != null &&
|
||||
// @ts-expect-error error is still unknown
|
||||
typeof error.code === "string" &&
|
||||
// @ts-expect-error error is still unknown
|
||||
typeof error.message === "string" &&
|
||||
// @ts-expect-error error is still unknown
|
||||
(typeof error.cause === "object" || error.cause == null);
|
||||
|
||||
/**
|
||||
* Tries to parse an error coming from native to a typed JS camera error.
|
||||
* @param nativeError The native error instance. This is a JSON in the legacy native module architecture.
|
||||
* @returns A `CameraRuntimeError` or `CameraCaptureError`, or the nativeError if it's not parsable
|
||||
*/
|
||||
export const tryParseNativeCameraError = <T>(
|
||||
nativeError: T
|
||||
): (CameraRuntimeError | CameraCaptureError) | T => {
|
||||
if (isCameraErrorJson(nativeError)) {
|
||||
if (nativeError.code.startsWith("capture")) {
|
||||
return new CameraCaptureError(
|
||||
nativeError.code as CaptureError,
|
||||
nativeError.message,
|
||||
nativeError.cause
|
||||
);
|
||||
} else {
|
||||
return new CameraRuntimeError(
|
||||
// @ts-expect-error the code is string, we narrow it down to TS union.
|
||||
nativeError.code,
|
||||
nativeError.message,
|
||||
nativeError.cause
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return nativeError;
|
||||
}
|
||||
};
|
13
src/CameraPosition.ts
Normal file
13
src/CameraPosition.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Represents the camera device position.
|
||||
*
|
||||
* * `"back"`: Indicates that the device is physically located on the back of the system hardware
|
||||
* * `"front"`: Indicates that the device is physically located on the front of the system hardware
|
||||
*
|
||||
* #### iOS only
|
||||
* * `"unspecified"`: Indicates that the device's position relative to the system hardware is unspecified
|
||||
*
|
||||
* #### Android only
|
||||
* * `"external"`: The camera device is an external camera, and has no fixed facing relative to the device's screen. (Android only)
|
||||
*/
|
||||
export type CameraPosition = "front" | "back" | "unspecified" | "external";
|
29
src/CameraPreset.ts
Normal file
29
src/CameraPreset.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Indicates the quality level or bit rate of the output.
|
||||
*
|
||||
* * `"cif-352x288"`: Specifies capture settings suitable for CIF quality (352 x 288 pixel) video output
|
||||
* * `"hd-1280x720"`: Specifies capture settings suitable for 720p quality (1280 x 720 pixel) video output.
|
||||
* * `"hd-1920x1080"`: Capture settings suitable for 1080p-quality (1920 x 1080 pixels) video output.
|
||||
* * `"hd-3840x2160"`: Capture settings suitable for 2160p-quality (3840 x 2160 pixels, "4k") video output.
|
||||
* * `"high"`: Specifies capture settings suitable for high-quality video and audio output.
|
||||
* * `"iframe-1280x720"`: Specifies capture settings to achieve 1280 x 720 quality iFrame H.264 video at about 40 Mbits/sec with AAC audio.
|
||||
* * `"iframe-960x540"`: Specifies capture settings to achieve 960 x 540 quality iFrame H.264 video at about 30 Mbits/sec with AAC audio.
|
||||
* * `"input-priority"`: Specifies that the capture session does not control audio and video output settings.
|
||||
* * `"low"`: Specifies capture settings suitable for output video and audio bit rates suitable for sharing over 3G.
|
||||
* * `"medium"`: Specifies capture settings suitable for output video and audio bit rates suitable for sharing over WiFi.
|
||||
* * `"photo"`: Specifies capture settings suitable for high-resolution photo quality output.
|
||||
* * `"vga-640x480"`: Specifies capture settings suitable for VGA quality (640 x 480 pixel) video output.
|
||||
*/
|
||||
export type CameraPreset =
|
||||
| "cif-352x288"
|
||||
| "hd-1280x720"
|
||||
| "hd-1920x1080"
|
||||
| "hd-3840x2160"
|
||||
| "high"
|
||||
| "iframe-1280x720"
|
||||
| "iframe-960x540"
|
||||
| "input-priority"
|
||||
| "low"
|
||||
| "medium"
|
||||
| "photo"
|
||||
| "vga-640x480";
|
65
src/Code.ts
Normal file
65
src/Code.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Available code types
|
||||
*/
|
||||
export type CodeType =
|
||||
| "cat-body"
|
||||
| "dog-body"
|
||||
| "human-body"
|
||||
| "salient-object"
|
||||
| "aztec"
|
||||
| "code-128"
|
||||
| "code-39"
|
||||
| "code-39-mod-43"
|
||||
| "code-93"
|
||||
| "data-matrix"
|
||||
| "ean-13"
|
||||
| "ean-8"
|
||||
| "face"
|
||||
| "interleaved-2-of-5"
|
||||
| "itf-14"
|
||||
| "pdf-417"
|
||||
| "qr"
|
||||
| "upce";
|
||||
|
||||
/**
|
||||
* Represents a File in the local filesystem.
|
||||
*/
|
||||
export type Code = Readonly<{
|
||||
/**
|
||||
* The decoded string representation of the code.
|
||||
*/
|
||||
code?: string;
|
||||
/**
|
||||
* The type of the code.
|
||||
*/
|
||||
type: CodeType;
|
||||
/**
|
||||
* The position of the code relative to the camera's bounds
|
||||
*/
|
||||
bounds: {
|
||||
/**
|
||||
* Returns the smallest value for the x-coordinate of the rectangle.
|
||||
*/
|
||||
minX: number;
|
||||
/**
|
||||
* Returns the smallest value for the y-coordinate of the rectangle.
|
||||
*/
|
||||
minY: number;
|
||||
/**
|
||||
* Returns the largest value of the x-coordinate for the rectangle.
|
||||
*/
|
||||
maxX: number;
|
||||
/**
|
||||
* Returns the largest value of the y-coordinate for the rectangle.
|
||||
*/
|
||||
maxY: number;
|
||||
/**
|
||||
* Returns the width of a rectangle.
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* Returns the height of a rectangle.
|
||||
*/
|
||||
height: number;
|
||||
};
|
||||
}>;
|
13
src/DEVICES.md
Normal file
13
src/DEVICES.md
Normal file
@@ -0,0 +1,13 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th><a href="./README.md">README.md</a></th>
|
||||
<th><a href="./ANIMATED.md">ANIMATED.md</a></th>
|
||||
<th>DEVICES.md</th>
|
||||
<th><a href="./FORMATS.md">FORMATS.md</a></th>
|
||||
<th><a href="./ERRORS.md">ERRORS.md</a></th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
TODO: Explanation on how to use `getAvailableCameraDevices()` and `Camera`'s `device={}` prop
|
13
src/ERRORS.md
Normal file
13
src/ERRORS.md
Normal file
@@ -0,0 +1,13 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th><a href="./README.md">README.md</a></th>
|
||||
<th><a href="./ANIMATED.md">ANIMATED.md</a></th>
|
||||
<th><a href="./DEVICES.md">DEVICES.md</a></th>
|
||||
<th><a href="./FORMATS.md">FORMATS.md</a></th>
|
||||
<th>ERRORS.md</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
TODO: Explanation on how errors in the camera library work and how to identify the cause of the problem
|
13
src/FORMATS.md
Normal file
13
src/FORMATS.md
Normal file
@@ -0,0 +1,13 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th><a href="./README.md">README.md</a></th>
|
||||
<th><a href="./ANIMATED.md">ANIMATED.md</a></th>
|
||||
<th><a href="./DEVICES.md">DEVICES.md</a></th>
|
||||
<th>FORMATS.md</th>
|
||||
<th><a href="./ERRORS.md">ERRORS.md</a></th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
TODO: Explanation on how to use `getAvailableCameraDevices()` and `Camera`'s `format={}`, `fps={}` and `hdr={}` props
|
138
src/PhotoFile.ts
Normal file
138
src/PhotoFile.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { CameraPhotoCodec } from "./CameraCodec";
|
||||
import { TemporaryFile } from "./TemporaryFile";
|
||||
|
||||
export interface TakePhotoOptions {
|
||||
/**
|
||||
* Specify the photo codec to use. To get a list of available photo codecs use the `getAvailablePhotoCodecs()` function.
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
photoCodec?: CameraPhotoCodec;
|
||||
/**
|
||||
* Indicates how photo quality should be prioritized against speed.
|
||||
*
|
||||
* * `"quality"` Indicates that speed of photo delivery is most important, even at the expense of quality
|
||||
* * `"balanced"` Indicates that photo quality and speed of delivery are balanced in priority
|
||||
* * `"speed"` Indicates that photo quality is paramount, even at the expense of shot-to-shot time
|
||||
*
|
||||
* @platform iOS 13.0+
|
||||
* @default "balanced"
|
||||
*/
|
||||
qualityPrioritization?: "quality" | "balanced" | "speed";
|
||||
/**
|
||||
* Whether the Flash should be enabled or disabled
|
||||
*
|
||||
* @default "auto"
|
||||
*/
|
||||
flash?: "on" | "off" | "auto";
|
||||
/**
|
||||
* Specifies whether red-eye reduction should be applied automatically on flash captures.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableAutoRedEyeReduction?: boolean;
|
||||
/**
|
||||
* Specifies whether a virtual multi-cam device should capture images from all containing physical cameras
|
||||
* to create a combined, higher quality image.
|
||||
*
|
||||
* @see [`isAutoVirtualDeviceFusionEnabled`](https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/3192192-isautovirtualdevicefusionenabled)
|
||||
*/
|
||||
enableVirtualDeviceFusion?: boolean;
|
||||
/**
|
||||
* Indicates whether still image stabilization will be employed when capturing the photo
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableAutoStabilization?: boolean;
|
||||
/**
|
||||
* Specifies whether the photo output should use content aware distortion correction on this photo request (at its discretion).
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableAutoDistortionCorrection?: boolean;
|
||||
/**
|
||||
* When set to `true`, metadata reading and mapping will be skipped. (`PhotoFile.metadata` will be null)
|
||||
*
|
||||
* This might result in a faster capture, as metadata reading and mapping requires File IO.
|
||||
*
|
||||
* @default false
|
||||
*
|
||||
* @platform Android
|
||||
*/
|
||||
skipMetadata?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Photo taken by the Camera written to the local filesystem.
|
||||
*/
|
||||
export type PhotoFile = Readonly<
|
||||
TemporaryFile & {
|
||||
width: number;
|
||||
height: number;
|
||||
isRawPhoto: boolean;
|
||||
thumbnail?: Record<string, unknown>;
|
||||
metadata: {
|
||||
Orientation: number;
|
||||
/**
|
||||
* @platform iOS
|
||||
*/
|
||||
DPIHeight: number;
|
||||
/**
|
||||
* @platform iOS
|
||||
*/
|
||||
DPIWidth: number;
|
||||
/**
|
||||
* Represents any data Apple cameras write to the metadata
|
||||
*
|
||||
* @platform iOS
|
||||
*/
|
||||
"{MakerApple}"?: Record<string, unknown>;
|
||||
"{TIFF}": {
|
||||
ResolutionUnit: number;
|
||||
Software: string;
|
||||
Make: string;
|
||||
DateTime: string;
|
||||
XResolution: number;
|
||||
/**
|
||||
* @platform iOS
|
||||
*/
|
||||
HostComputer?: string;
|
||||
Model: string;
|
||||
YResolution: number;
|
||||
};
|
||||
"{Exif}": {
|
||||
DateTimeOriginal: string;
|
||||
ExposureTime: number;
|
||||
FNumber: number;
|
||||
LensSpecification: number[];
|
||||
ExposureBiasValue: number;
|
||||
ColorSpace: number;
|
||||
FocalLenIn35mmFilm: number;
|
||||
BrightnessValue: number;
|
||||
ExposureMode: number;
|
||||
LensModel: string;
|
||||
SceneType: number;
|
||||
PixelXDimension: number;
|
||||
ShutterSpeedValue: number;
|
||||
SensingMethod: number;
|
||||
SubjectArea: number[];
|
||||
ApertureValue: number;
|
||||
SubsecTimeDigitized: string;
|
||||
FocalLength: number;
|
||||
LensMake: string;
|
||||
SubsecTimeOriginal: string;
|
||||
OffsetTimeDigitized: string;
|
||||
PixelYDimension: number;
|
||||
ISOSpeedRatings: number[];
|
||||
WhiteBalance: number;
|
||||
DateTimeDigitized: string;
|
||||
OffsetTimeOriginal: string;
|
||||
ExifVersion: string;
|
||||
OffsetTime: string;
|
||||
Flash: number;
|
||||
ExposureProgram: number;
|
||||
MeteringMode: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
>;
|
13
src/Point.ts
Normal file
13
src/Point.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Represents a Point in a 2 dimensional coordinate system.
|
||||
*/
|
||||
export interface Point {
|
||||
/**
|
||||
* The X coordinate of this Point. (double)
|
||||
*/
|
||||
x: number;
|
||||
/**
|
||||
* The Y coordinate of this Point. (double)
|
||||
*/
|
||||
y: number;
|
||||
}
|
44
src/README.md
Normal file
44
src/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>README.md</th>
|
||||
<th><a href="./ANIMATED.md">ANIMATED.md</a></th>
|
||||
<th><a href="./DEVICES.md">DEVICES.md</a></th>
|
||||
<th><a href="./FORMATS.md">FORMATS.md</a></th>
|
||||
<th><a href="./ERRORS.md">ERRORS.md</a></th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
<div align="center">
|
||||
<h1 align="center">Camera</h1>
|
||||
<img src="img/11.png" width="55%">
|
||||
<br />
|
||||
<br />
|
||||
<blockquote><h4>The most powerful Camera component for react-native.</h4></blockquote>
|
||||
<br />
|
||||
<a href='https://ko-fi.com/F1F8CLXG' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi2.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
<br />
|
||||
<a href="https://www.npmjs.com/package/react-native-vision-camera"><img src="https://img.shields.io/npm/v/react-native-vision-camera?color=%239ba298"</a>
|
||||
<br />
|
||||
<a href="https://www.npmjs.com/package/react-native-vision-camera"><img src="https://img.shields.io/npm/dt/react-native-vision-camera?color=%239ba298"</a>
|
||||
<br />
|
||||
<a href="https://github.com/mrousavy?tab=followers"><img src="https://img.shields.io/github/followers/mrousavy?label=Follow%20%40mrousavy&style=social"></a>
|
||||
<br />
|
||||
<a href="https://twitter.com/mrousavy"><img src="https://img.shields.io/twitter/follow/mrousavy?label=Follow%20%40mrousavy&style=social"></a>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
1. TODO: Better description
|
||||
2. TODO: Demo Screenshot from Cuvent
|
||||
|
||||
|
||||
### Install
|
||||
|
||||
```sh
|
||||
npm i react-native-vision-camera
|
||||
npx pod-install
|
||||
```
|
21
src/Snapshot.ts
Normal file
21
src/Snapshot.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export interface TakeSnapshotOptions {
|
||||
/**
|
||||
* Specifies the quality of the JPEG. (0-100, where 100 means best quality (no compression))
|
||||
*
|
||||
* It is recommended to set this to `90` or even `80`, since the user probably won't notice a difference between `90`/`80` and `100`.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
quality?: number;
|
||||
|
||||
/**
|
||||
* When set to `true`, metadata reading and mapping will be skipped. (`PhotoFile.metadata` will be null)
|
||||
*
|
||||
* This might result in a faster capture, as metadata reading and mapping requires File IO.
|
||||
*
|
||||
* @default false
|
||||
*
|
||||
* @platform Android
|
||||
*/
|
||||
skipMetadata?: boolean;
|
||||
}
|
11
src/TODO.md
Normal file
11
src/TODO.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# TODO
|
||||
|
||||
This is an internal TODO list which I am using to keep track of some of the features that are still missing.
|
||||
|
||||
* [ ] focus(x, y)
|
||||
* [ ] Mirror images from selfie cameras (iOS Done, Android WIP)
|
||||
* [ ] Allow camera switching (front <-> back) while recording and stich videos together
|
||||
* [ ] Make `startRecording()` async. Due to NativeModules limitations, we can only have either one callback or one promise in a native function. For `startRecording()` we need both, since you probably also want to catch any errors that occured during a `startRecording()` call (or wait until the recording has actually started, since this can also take some time)
|
||||
* [ ] Return a `jsi::Value` reference for images (`UIImage`/`Bitmap`) on `takePhoto()` and `takeSnapshot()`. This way, we skip the entire file writing and reading, making image capture _a lot_ faster.
|
||||
* [ ] Implement frame processors. The idea here is that the user passes a small JS function (reanimated worklet) to the `Camera::frameProcessor` prop which will then get called on every frame the camera previews. (I'd say we cap it to 30 times per second, even if the camera fps is higher) This can then be used to scan QR codes, detect faces, detect depth, render something ontop of the camera such as color filters, QR code boundaries or even dog filters, possibly even use AR - all from a single, small, and highly flexible JS function!
|
||||
* [ ] Create a custom MPEG4 encoder to allow for more customizability in `recordVideo()` (`bitRate`, `priority`, `minQuantizationParameter`, `allowFrameReordering`, `expectedFrameRate`, `realTime`, `minimizeMemoryUsage`)
|
9
src/TemporaryFile.ts
Normal file
9
src/TemporaryFile.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Represents a temporary file in the local filesystem.
|
||||
*/
|
||||
export type TemporaryFile = Readonly<{
|
||||
/**
|
||||
* The path of the file. This file might get deleted once the app closes because it lives in the temp directory.
|
||||
*/
|
||||
path: string;
|
||||
}>;
|
61
src/VideoFile.ts
Normal file
61
src/VideoFile.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// /**
|
||||
// * not yet implemented.
|
||||
// */
|
||||
// declare interface RecordVideoOptions<TCodec extends CameraVideoCodec> {
|
||||
// /**
|
||||
// * Specify the video codec to use. To get a list of available video codecs use the `getAvailableVideoCodecs()` function.
|
||||
// *
|
||||
// * @default undefined
|
||||
// */
|
||||
// videoCodec?: TCodec;
|
||||
// /**
|
||||
// * Specify the average video bitrate in bits per second. (H.264 only)
|
||||
// */
|
||||
// bitrate?: TCodec extends "h264" ? number : never;
|
||||
// /**
|
||||
// * Specify the video quality. (`0.0` - `1.0`, where `1.0` means 100% quality. JPEG, HEIC and Apple ProRAW only. With HEIC and Apple ProRAW, 1.0 indicates lossless compression)
|
||||
// */
|
||||
// quality?: TCodec extends "jpeg" | "hevc" | "hevc-alpha" ? number : never;
|
||||
// /**
|
||||
// * Maximum number of frames per interval, `1` specifies to only use key frames. (H.264 only)
|
||||
// */
|
||||
// maxKeyFrameInterval?: TCodec extends "h264" ? number : never;
|
||||
// /**
|
||||
// * Maximum duration of a key frame interval in seconds, where as `0.0` means no limit. (H.264 only)
|
||||
// */
|
||||
// maxKeyFrameIntervalDuration?: TCodec extends "h264" ? number : never;
|
||||
// }
|
||||
|
||||
import { CameraCaptureError } from "./CameraError";
|
||||
import { TemporaryFile } from "./TemporaryFile";
|
||||
|
||||
export interface RecordVideoOptions {
|
||||
/**
|
||||
* Set the video flash mode. Natively, this just enables the torch while recording.
|
||||
*/
|
||||
flash?: "on" | "off" | "auto";
|
||||
/**
|
||||
* Called when there was an unexpected runtime error while recording the video.
|
||||
*/
|
||||
onRecordingError: (error: CameraCaptureError) => void;
|
||||
/**
|
||||
* Called when the recording has been successfully saved to file.
|
||||
*/
|
||||
onRecordingFinished: (video: VideoFile) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Video taken by the Camera written to the local filesystem.
|
||||
*/
|
||||
export type VideoFile = Readonly<
|
||||
TemporaryFile & {
|
||||
/**
|
||||
* Represents the duration of the video, in seconds.
|
||||
*/
|
||||
duration: number;
|
||||
/**
|
||||
* Represents the file size of the recorded Video File, in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
>;
|
BIN
src/img/11.png
Normal file
BIN
src/img/11.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 160 KiB |
Reference in New Issue
Block a user