feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,459 @@
import { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions } from 'expo-modules-core';
import { Ref } from 'react';
import type { ViewProps } from 'react-native';
export type CameraType = 'front' | 'back';
export type FlashMode = 'off' | 'on' | 'auto';
export type ImageType = 'png' | 'jpg';
export type CameraMode = 'picture' | 'video';
export type CameraRatio = '4:3' | '16:9' | '1:1';
/**
* This option specifies the mode of focus on the device.
* - `on` - Indicates that the device should autofocus once and then lock the focus.
* - `off` - Indicates that the device should automatically focus when needed.
* @default off
*/
export type FocusMode = 'on' | 'off';
/**
* This option specifies what codec to use when recording a video.
* @platform ios
*/
export type VideoCodec = 'avc1' | 'hvc1' | 'jpeg' | 'apcn' | 'ap4h';
/**
* This option specifies the stabilization mode to use when recording a video.
* @platform ios
*/
export type VideoStabilization = 'off' | 'standard' | 'cinematic' | 'auto';
export type VideoQuality = '2160p' | '1080p' | '720p' | '480p' | '4:3';
export type CameraOrientation = 'portrait' | 'portraitUpsideDown' | 'landscapeLeft' | 'landscapeRight';
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type ImageSize = {
width: number;
height: number;
};
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type WebCameraSettings = {
autoFocus?: string;
flashMode?: string;
whiteBalance?: string;
exposureCompensation?: number;
colorTemperature?: number;
iso?: number;
brightness?: number;
contrast?: number;
saturation?: number;
sharpness?: number;
focusDistance?: number;
zoom?: number;
};
export type CameraCapturedPicture = {
/**
* Captured image width.
*/
width: number;
/**
* Captured image height.
*/
height: number;
/**
* On web, the value of `uri` is the same as `base64` because file system URLs are not supported in the browser.
*/
uri: string;
/**
* A Base64 representation of the image.
*/
base64?: string;
/**
* On Android and iOS this object may include various fields based on the device and operating system.
* On web, it is a partial representation of the [`MediaTrackSettings`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings) dictionary.
*/
exif?: Partial<MediaTrackSettings> | any;
};
export type CameraPictureOptions = {
/**
* Specify the compression quality from `0` to `1`. `0` means compress for small size, and `1` means compress for maximum quality.
*/
quality?: number;
/**
* Whether to also include the image data in Base64 format.
*/
base64?: boolean;
/**
* Whether to also include the EXIF data for the image.
*/
exif?: boolean;
/**
* Additional EXIF data to be included for the image. Only useful when `exif` option is set to `true`.
* @platform android
* @platform ios
*/
additionalExif?: Record<string, any>;
/**
* A callback invoked when picture is saved. If set, the promise of this method will resolve immediately with no data after picture is captured.
* The data that it should contain will be passed to this callback. If displaying or processing a captured photo right after taking it
* is not your case, this callback lets you skip waiting for it to be saved.
* @param picture
*/
onPictureSaved?: (picture: CameraCapturedPicture) => void;
/**
* If set to `true`, camera skips orientation adjustment and returns an image straight from the device's camera.
* If enabled, `quality` option is discarded (processing pipeline is skipped as a whole).
* Although enabling this option reduces image delivery time significantly, it may cause the image to appear in a wrong orientation
* in the `Image` component (at the time of writing, it does not respect EXIF orientation of the images).
* > **Note**: Enabling `skipProcessing` would cause orientation uncertainty. `Image` component does not respect EXIF
* > stored orientation information, that means obtained image would be displayed wrongly (rotated by 90°, 180° or 270°).
* > Different devices provide different orientations. For example some Sony Xperia or Samsung devices don't provide
* > correctly oriented images by default. To always obtain correctly oriented image disable `skipProcessing` option.
*/
skipProcessing?: boolean;
/**
* @platform web
*/
scale?: number;
/**
* @platform web
*/
imageType?: ImageType;
/**
* @platform web
*/
isImageMirror?: boolean;
/**
* When set to `true`, the output image will be flipped along the vertical axis when using the front camera.
* @default false
* @platform ios
* @platform android
* @deprecated Use `mirror` prop on `CameraView` instead.
*/
mirror?: boolean;
/**
* @hidden
*/
id?: number;
/**
* @hidden
*/
fastMode?: boolean;
/**
* @hidden
*/
maxDownsampling?: number;
};
export type CameraRecordingOptions = {
/**
* Maximum video duration in seconds.
*/
maxDuration?: number;
/**
* Maximum video file size in bytes.
*/
maxFileSize?: number;
/**
* If `true`, the recorded video will be flipped along the vertical axis. iOS flips videos recorded with the front camera by default,
* but you can reverse that back by setting this to `true`. On Android, this is handled in the user's device settings.
* @deprecated Use `mirror` prop on `CameraView` instead.
*/
mirror?: boolean;
/**
* This option specifies what codec to use when recording the video. See [`VideoCodec`](#videocodec) for the possible values.
* @platform ios
*/
codec?: VideoCodec;
};
/**
* @hidden
*/
export type PictureSavedListener = (event: {
nativeEvent: {
data: CameraCapturedPicture;
id: number;
};
}) => void;
/**
* @hidden
*/
export type CameraReadyListener = () => void;
/**
* @hidden
*/
export type ResponsiveOrientationChangedListener = (event: {
nativeEvent: ResponsiveOrientationChanged;
}) => void;
export type ResponsiveOrientationChanged = {
orientation: CameraOrientation;
};
/**
* @hidden
*/
export type MountErrorListener = (event: {
nativeEvent: CameraMountError;
}) => void;
export type CameraMountError = {
message: string;
};
export type Point = {
x: number;
y: number;
};
export type BarcodeSize = {
/**
* The height value.
*/
height: number;
/**
* The width value.
*/
width: number;
};
/**
* These coordinates are represented in the coordinate space of the camera source (e.g. when you
* are using the camera view, these values are adjusted to the dimensions of the view).
*/
export type BarcodePoint = Point;
export type BarcodeBounds = {
/**
* The origin point of the bounding box.
*/
origin: BarcodePoint;
/**
* The size of the bounding box.
*/
size: BarcodeSize;
};
export type BarcodeScanningResult = {
/**
* The barcode type.
*/
type: string;
/**
* The parsed information encoded in the barcode.
*/
data: string;
/**
* The raw information encoded in the barcode.
* May be different from `data` depending on the barcode type.
* @platform android
* @hidden
*/
raw?: string;
/**
* Corner points of the bounding box.
* `cornerPoints` is not always available and may be empty. On iOS, for `code39` and `pdf417`
* you don't get this value.
*/
cornerPoints: BarcodePoint[];
/**
* The [BarcodeBounds](#barcodebounds) object.
* `bounds` in some case will be representing an empty rectangle.
* Moreover, `bounds` doesn't have to bound the whole barcode.
* For some types, they will represent the area used by the scanner.
*/
bounds: BarcodeBounds;
};
export type ScanningResult = Omit<BarcodeScanningResult, 'bounds'>;
export type CameraProps = ViewProps & {
/**
* Camera facing. Use one of `CameraType`. When `front`, use the front-facing camera.
* When `back`, use the back-facing camera.
* @default 'back'
*/
facing?: CameraType;
/**
* Camera flash mode. Use one of `FlashMode` values. When `on`, the flash on your device will
* turn on when taking a picture. When `off`, it won't. Setting it to `auto` will fire flash if required.
* @default 'off'
*/
flash?: FlashMode;
/**
* A value between `0` and `1` being a percentage of device's max zoom. `0` - not zoomed, `1` - maximum zoom.
* @default 0
*/
zoom?: number;
/**
* Used to select image or video output
* @default 'picture'
*/
mode?: CameraMode;
/**
* If present, video will be recorded with no sound.
* @default false
*/
mute?: boolean;
/**
* A boolean that determines whether the camera should mirror the image when using the front camera.
* @default false
*/
mirror?: boolean;
/**
* Indicates the focus mode to use.
* @default off
* @platform ios
*/
autofocus?: FocusMode;
/**
* A boolean that determines whether the camera should be active.
* Useful in situations where the camera may not have unmounted but you still want to stop the camera session.
* @default true
* @platform ios
*/
active?: boolean;
/**
* Specify the quality of the recorded video. Use one of `VideoQuality` possible values:
* for 16:9 resolution `2160p`, `1080p`, `720p`, `480p` : `Android only` and for 4:3 `4:3` (the size is 640x480).
* If the chosen quality is not available for a device, the highest available is chosen.
*/
videoQuality?: VideoQuality;
/**
* A boolean that determines whether the camera shutter animation should be enabled.
* @default true
*/
animateShutter?: boolean;
/**
* A string representing the size of pictures [`takePictureAsync`](#takepictureasync) will take.
* Available sizes can be fetched with [`getAvailablePictureSizes`](#getavailablepicturesizes).
*/
pictureSize?: string;
/**
* A boolean to enable or disable the torch
* @default false
*/
enableTorch?: boolean;
/**
* The video stabilization mode used for a video recording. Use one of [`VideoStabilization.<value>`](#videostabilization).
* You can read more about each stabilization type in [Apple Documentation](https://developer.apple.com/documentation/avfoundation/avcapturevideostabilizationmode).
* @platform ios
*/
videoStabilizationMode?: VideoStabilization;
/**
* @example
* ```tsx
* <CameraView
* barcodeScannerSettings={{
* barcodeTypes: ["qr"],
* }}
* />
* ```
*/
barcodeScannerSettings?: BarcodeSettings;
/**
* A URL for an image to be shown while the camera is loading.
* @platform web
*/
poster?: string;
/**
* Whether to allow responsive orientation of the camera when the screen orientation is locked (i.e. when set to `true`
* landscape photos will be taken if the device is turned that way, even if the app or device orientation is locked to portrait)
* @platform ios
*/
responsiveOrientationWhenOrientationLocked?: boolean;
/**
* A string representing the aspect ratio of the preview. For example, `4:3` and `16:9`.
* Note: Setting the aspect ratio here will change the scaleType of the camera preview from `FILL` to `FIT`.
* Also, when using 1:1, devices only support certain sizes. If you specify an unsupported size, the closest supported ratio will be used.
* @platform android
*/
ratio?: CameraRatio;
/**
* Callback invoked when camera preview has been set.
*/
onCameraReady?: () => void;
/**
* Callback invoked when camera preview could not start.
* @param event Error object that contains a `message`.
*/
onMountError?: (event: CameraMountError) => void;
/**
* Callback that is invoked when a barcode has been successfully scanned. The callback is provided with
* an object of the [`BarcodeScanningResult`](#barcodescanningresult) shape, where the `type`
* refers to the barcode type that was scanned, and the `data` is the information encoded in the barcode
* (in this case of QR codes, this is often a URL). See [`BarcodeType`](#barcodetype) for supported values.
* for supported values.
* @param scanningResult
*/
onBarcodeScanned?: (scanningResult: BarcodeScanningResult) => void;
/**
* Callback invoked when responsive orientation changes. Only applicable if `responsiveOrientationWhenOrientationLocked` is `true`
* @param event result object that contains updated orientation of camera
* @platform ios
*/
onResponsiveOrientationChanged?: (event: ResponsiveOrientationChanged) => void;
};
/**
* @hidden
*/
export interface CameraViewRef {
readonly takePicture: (options: CameraPictureOptions) => Promise<CameraCapturedPicture>;
readonly getAvailablePictureSizes: () => Promise<string[]>;
readonly record: (options?: CameraRecordingOptions) => Promise<{
uri: string;
}>;
readonly stopRecording: () => Promise<void>;
readonly launchModernScanner: () => Promise<void>;
readonly resumePreview: () => Promise<void>;
readonly pausePreview: () => Promise<void>;
}
/**
* @hidden
*/
export type CameraNativeProps = {
pointerEvents?: any;
style?: any;
ref?: Ref<CameraViewRef>;
onCameraReady?: CameraReadyListener;
onMountError?: MountErrorListener;
onBarcodeScanned?: (event: {
nativeEvent: BarcodeScanningResult;
}) => void;
onPictureSaved?: PictureSavedListener;
onResponsiveOrientationChanged?: ResponsiveOrientationChangedListener;
facing?: string;
flashMode?: string;
enableTorch?: boolean;
animateShutter?: boolean;
autoFocus?: FocusMode;
mute?: boolean;
zoom?: number;
ratio?: CameraRatio;
barcodeScannerSettings?: BarcodeSettings;
barcodeScannerEnabled?: boolean;
poster?: string;
responsiveOrientationWhenOrientationLocked?: boolean;
};
export type BarcodeSettings = {
barcodeTypes: BarcodeType[];
};
/**
* @platform ios
*/
export type ScanningOptions = {
/**
* The type of codes to scan for.
*/
barcodeTypes: BarcodeType[];
/**
* Indicates whether people can use a two-finger pinch-to-zoom gesture.
* @default true
*/
isPinchToZoomEnabled?: boolean;
/**
* Guidance text, such as “Slow Down,” appears over the live video.
* @default true
*/
isGuidanceEnabled?: boolean;
/**
* Indicates whether the scanner displays highlights around recognized items.
* @default false
*/
isHighlightingEnabled?: boolean;
};
/**
* The available barcode types that can be scanned.
*/
export type BarcodeType = 'aztec' | 'ean13' | 'ean8' | 'qr' | 'pdf417' | 'upc_e' | 'datamatrix' | 'code39' | 'code93' | 'itf14' | 'codabar' | 'code128' | 'upc_a';
export { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions };
//# sourceMappingURL=Camera.types.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import { PermissionStatus, } from 'expo-modules-core';
export { PermissionStatus };
//# sourceMappingURL=Camera.types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,121 @@
import { Subscription } from 'expo-modules-core';
import * as React from 'react';
import { Ref } from 'react';
import { CameraCapturedPicture, CameraOrientation, CameraPictureOptions, CameraProps, CameraRecordingOptions, CameraViewRef, ScanningOptions, ScanningResult, VideoCodec } from './Camera.types';
export default class CameraView extends React.Component<CameraProps> {
/**
* Property that determines if the current device has the ability to use `DataScannerViewController` (iOS 16+).
*/
static isModernBarcodeScannerAvailable: boolean;
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static isAvailableAsync(): Promise<boolean>;
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static getAvailableVideoCodecsAsync(): Promise<VideoCodec[]>;
/**
* Get picture sizes that are supported by the device.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
getAvailablePictureSizesAsync(): Promise<string[]>;
/**
* Resumes the camera preview.
*/
resumePreview(): Promise<void>;
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
pausePreview(): Promise<void>;
static ConversionTables: {
type: Record<number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "replaceAll" | "at", string | undefined>;
flash: Record<number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "replaceAll" | "at", string | undefined>;
};
static defaultProps: CameraProps;
_cameraHandle?: number | null;
_cameraRef: React.RefObject<CameraViewRef>;
_lastEvents: {
[eventName: string]: string;
};
_lastEventsTimes: {
[eventName: string]: Date;
};
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcopyasyncoptions)
* > to make a permanent copy of the image.
*
* **Note** Avoid calling this method while the preview is paused. On iOS, this will take a picture of the last frame that is currently on screen, on Android, this will throw an error.
*/
takePictureAsync(options?: CameraPictureOptions): Promise<CameraCapturedPicture | undefined>;
/**
* Presents a modal view controller that uses the [`DataScannerViewController`](https://developer.apple.com/documentation/visionkit/scanning_data_with_the_camera) available on iOS 16+.
* @platform ios
*/
static launchScanner(options?: ScanningOptions): Promise<void>;
/**
* Dimiss the scanner presented by `launchScanner`.
* @platform ios
*/
static dismissScanner(): Promise<void>;
/**
* Invokes the `listener` function when a bar code has been successfully scanned. The callback is provided with
* an object of the `ScanningResult` shape, where the `type` refers to the bar code type that was scanned and the `data` is the information encoded in the bar code
* (in this case of QR codes, this is often a URL). See [`BarcodeType`](#barcodetype) for supported values.
* @param listener Invoked with the [ScanningResult](#scanningresult) when a bar code has been successfully scanned.
*
* @platform ios
*/
static onModernBarcodeScanned(listener: (event: ScanningResult) => void): Subscription;
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
recordAsync(options?: CameraRecordingOptions): Promise<{
uri: string;
} | undefined>;
/**
* Stops recording if any is in progress.
*/
stopRecording(): void;
_onCameraReady: () => void;
_onMountError: ({ nativeEvent }: {
nativeEvent: {
message: string;
};
}) => void;
_onResponsiveOrientationChanged: ({ nativeEvent, }: {
nativeEvent: {
orientation: CameraOrientation;
};
}) => void;
_onObjectDetected: (callback?: Function) => ({ nativeEvent }: {
nativeEvent: any;
}) => void;
_setReference: (ref: Ref<CameraViewRef>) => void;
render(): JSX.Element;
}
//# sourceMappingURL=CameraView.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"CameraView.d.ts","sourceRoot":"","sources":["../src/CameraView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+C,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9F,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,EACX,MAAM,gBAAgB,CAAC;AAiExB,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC;IAClE;;OAEG;IACH,MAAM,CAAC,+BAA+B,EAAE,OAAO,CAAiD;IAChG;;;;;OAKG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IASjD;;;;OAIG;WACU,4BAA4B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQlE;;;;OAIG;IACG,6BAA6B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIxD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAKnC,MAAM,CAAC,gBAAgB;;;MAAoB;IAE3C,MAAM,CAAC,YAAY,EAAE,WAAW,CAM9B;IAEF,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,iCAAoC;IAC9C,WAAW,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAM;IAClD,gBAAgB,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAM;IAGrD;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAM7C;;;OAGG;WACU,aAAa,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IASpE;;;OAGG;WACU,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5C;;;;;;;OAOG;IACH,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,YAAY;IAItF;;;;;;;;OAQG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAKzF;;OAEG;IACH,aAAa;IAIb,cAAc,aAIZ;IAEF,aAAa;qBAAoC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE;eAIlE;IAEF,+BAA+B;qBAGhB;YAAE,aAAa,iBAAiB,CAAA;SAAE;eAK/C;IAEF,iBAAiB,cACH,QAAQ;qBACa,GAAG;eAgBlC;IAEJ,aAAa,QAAS,IAAI,aAAa,CAAC,UAOtC;IAEF,MAAM;CAkBP"}

View File

@@ -0,0 +1,226 @@
import { Platform, UnavailabilityError, EventEmitter } from 'expo-modules-core';
import * as React from 'react';
import ExpoCamera from './ExpoCamera';
import CameraManager from './ExpoCameraManager';
import { ConversionTables, ensureNativeProps } from './utils/props';
const emitter = new EventEmitter(CameraManager);
const EventThrottleMs = 500;
const _PICTURE_SAVED_CALLBACKS = {};
let _GLOBAL_PICTURE_ID = 1;
function ensurePictureOptions(options) {
if (!options || typeof options !== 'object') {
return {};
}
if (!options.quality) {
options.quality = 1;
}
if (options.mirror) {
console.warn('The `mirror` option is deprecated. Please use the `mirror` prop on the `CameraView` instead.');
}
if (options.onPictureSaved) {
const id = _GLOBAL_PICTURE_ID++;
_PICTURE_SAVED_CALLBACKS[id] = options.onPictureSaved;
options.id = id;
options.fastMode = true;
}
return options;
}
function ensureRecordingOptions(options) {
if (!options || typeof options !== 'object') {
return {};
}
if (options.mirror) {
console.warn('The `mirror` option is deprecated. Please use the `mirror` prop on the `CameraView` instead.');
}
return options;
}
function _onPictureSaved({ nativeEvent, }) {
const { id, data } = nativeEvent;
const callback = _PICTURE_SAVED_CALLBACKS[id];
if (callback) {
callback(data);
delete _PICTURE_SAVED_CALLBACKS[id];
}
}
export default class CameraView extends React.Component {
/**
* Property that determines if the current device has the ability to use `DataScannerViewController` (iOS 16+).
*/
static isModernBarcodeScannerAvailable = CameraManager.isModernBarcodeScannerAvailable;
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static async isAvailableAsync() {
if (!CameraManager.isAvailableAsync) {
throw new UnavailabilityError('expo-camera', 'isAvailableAsync');
}
return await CameraManager.isAvailableAsync();
}
// @needsAudit
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static async getAvailableVideoCodecsAsync() {
if (!CameraManager.getAvailableVideoCodecsAsync) {
throw new UnavailabilityError('Camera', 'getAvailableVideoCodecsAsync');
}
return await CameraManager.getAvailableVideoCodecsAsync();
}
/**
* Get picture sizes that are supported by the device.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
async getAvailablePictureSizesAsync() {
return (await this._cameraRef.current?.getAvailablePictureSizes()) ?? [];
}
/**
* Resumes the camera preview.
*/
async resumePreview() {
return this._cameraRef.current?.resumePreview();
}
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
async pausePreview() {
return this._cameraRef.current?.pausePreview();
}
// Values under keys from this object will be transformed to native options
static ConversionTables = ConversionTables;
static defaultProps = {
zoom: 0,
facing: 'back',
enableTorch: false,
mode: 'picture',
flash: 'off',
};
_cameraHandle;
_cameraRef = React.createRef();
_lastEvents = {};
_lastEventsTimes = {};
// @needsAudit
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcopyasyncoptions)
* > to make a permanent copy of the image.
*
* **Note** Avoid calling this method while the preview is paused. On iOS, this will take a picture of the last frame that is currently on screen, on Android, this will throw an error.
*/
async takePictureAsync(options) {
const pictureOptions = ensurePictureOptions(options);
return await this._cameraRef.current?.takePicture(pictureOptions);
}
/**
* Presents a modal view controller that uses the [`DataScannerViewController`](https://developer.apple.com/documentation/visionkit/scanning_data_with_the_camera) available on iOS 16+.
* @platform ios
*/
static async launchScanner(options) {
if (!options) {
options = { barcodeTypes: [] };
}
if (Platform.OS === 'ios' && CameraView.isModernBarcodeScannerAvailable) {
await CameraManager.launchScanner(options);
}
}
/**
* Dimiss the scanner presented by `launchScanner`.
* @platform ios
*/
static async dismissScanner() {
if (Platform.OS === 'ios' && CameraView.isModernBarcodeScannerAvailable) {
await CameraManager.dismissScanner();
}
}
/**
* Invokes the `listener` function when a bar code has been successfully scanned. The callback is provided with
* an object of the `ScanningResult` shape, where the `type` refers to the bar code type that was scanned and the `data` is the information encoded in the bar code
* (in this case of QR codes, this is often a URL). See [`BarcodeType`](#barcodetype) for supported values.
* @param listener Invoked with the [ScanningResult](#scanningresult) when a bar code has been successfully scanned.
*
* @platform ios
*/
static onModernBarcodeScanned(listener) {
return emitter.addListener('onModernBarcodeScanned', listener);
}
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
async recordAsync(options) {
const recordingOptions = ensureRecordingOptions(options);
return await this._cameraRef.current?.record(recordingOptions);
}
/**
* Stops recording if any is in progress.
*/
stopRecording() {
this._cameraRef.current?.stopRecording();
}
_onCameraReady = () => {
if (this.props.onCameraReady) {
this.props.onCameraReady();
}
};
_onMountError = ({ nativeEvent }) => {
if (this.props.onMountError) {
this.props.onMountError(nativeEvent);
}
};
_onResponsiveOrientationChanged = ({ nativeEvent, }) => {
if (this.props.onResponsiveOrientationChanged) {
this.props.onResponsiveOrientationChanged(nativeEvent);
}
};
_onObjectDetected = (callback) => ({ nativeEvent }) => {
const { type } = nativeEvent;
if (this._lastEvents[type] &&
this._lastEventsTimes[type] &&
JSON.stringify(nativeEvent) === this._lastEvents[type] &&
new Date().getTime() - this._lastEventsTimes[type].getTime() < EventThrottleMs) {
return;
}
if (callback) {
callback(nativeEvent);
this._lastEventsTimes[type] = new Date();
this._lastEvents[type] = JSON.stringify(nativeEvent);
}
};
_setReference = (ref) => {
if (ref) {
// TODO(Bacon): Unify these - perhaps with hooks?
if (Platform.OS === 'web') {
this._cameraHandle = ref;
}
}
};
render() {
const nativeProps = ensureNativeProps(this.props);
const onBarcodeScanned = this.props.onBarcodeScanned
? this._onObjectDetected(this.props.onBarcodeScanned)
: undefined;
return (<ExpoCamera {...nativeProps} ref={this._cameraRef} onCameraReady={this._onCameraReady} onMountError={this._onMountError} onBarcodeScanned={onBarcodeScanned} onPictureSaved={_onPictureSaved} onResponsiveOrientationChanged={this._onResponsiveOrientationChanged}/>);
}
}
//# sourceMappingURL=CameraView.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
import { CameraNativeProps } from './Camera.types';
declare const ExpoCamera: React.ComponentType<CameraNativeProps>;
export default ExpoCamera;
//# sourceMappingURL=ExpoCamera.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.d.ts","sourceRoot":"","sources":["../src/ExpoCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAA0C,CAAC;AAElG,eAAe,UAAU,CAAC"}

View File

@@ -0,0 +1,4 @@
import { requireNativeViewManager } from 'expo-modules-core';
const ExpoCamera = requireNativeViewManager('ExpoCamera');
export default ExpoCamera;
//# sourceMappingURL=ExpoCamera.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.js","sourceRoot":"","sources":["../src/ExpoCamera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAK7D,MAAM,UAAU,GAA2C,wBAAwB,CAAC,YAAY,CAAC,CAAC;AAElG,eAAe,UAAU,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\n\nimport { CameraNativeProps } from './Camera.types';\n\nconst ExpoCamera: React.ComponentType<CameraNativeProps> = requireNativeViewManager('ExpoCamera');\n\nexport default ExpoCamera;\n"]}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import { CameraCapturedPicture, CameraNativeProps, CameraPictureOptions } from './legacy/Camera.types';
export interface ExponentCameraRef {
getAvailablePictureSizes: (ratio: string) => Promise<string[]>;
takePicture: (options: CameraPictureOptions) => Promise<CameraCapturedPicture>;
resumePreview: () => Promise<void>;
pausePreview: () => Promise<void>;
}
declare const ExponentCamera: React.ForwardRefExoticComponent<Pick<CameraNativeProps & {
children?: React.ReactNode;
}, "type" | "flashMode" | "children" | "pointerEvents" | "style" | "zoom" | "pictureSize" | "poster" | "responsiveOrientationWhenOrientationLocked" | "ratio" | "onCameraReady" | "onMountError" | "onResponsiveOrientationChanged" | "onPictureSaved" | "autoFocus" | "whiteBalance" | "onBarCodeScanned" | "onFacesDetected" | "onFaceDetectionError" | "focusDepth" | "barCodeScannerSettings" | "faceDetectorSettings" | "barCodeScannerEnabled" | "faceDetectorEnabled" | "useCamera2Api"> & React.RefAttributes<ExponentCameraRef>>;
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.web.d.ts","sourceRoot":"","sources":["../src/ExpoCamera.web.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,uBAAuB,CAAC;AAM/B,MAAM,WAAW,iBAAiB;IAChC,wBAAwB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,WAAW,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,QAAA,MAAM,cAAc;eAE6C,MAAM,SAAS;ygBA+G/E,CAAC;AAEF,eAAe,cAAc,CAAC"}

View File

@@ -0,0 +1,106 @@
import { CodedError } from 'expo-modules-core';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import createElement from 'react-native-web/dist/exports/createElement';
import CameraManager from './ExpoCameraManager.web';
import { CameraType, } from './legacy/Camera.types';
import { capture } from './web/WebCameraUtils';
import { PictureSizes } from './web/WebConstants';
import { useWebCameraStream } from './web/useWebCameraStream';
import { useWebQRScanner } from './web/useWebQRScanner';
const ExponentCamera = React.forwardRef(({ type, poster, ...props }, ref) => {
const video = React.useRef(null);
const native = useWebCameraStream(video, type, props, {
onCameraReady() {
if (props.onCameraReady) {
props.onCameraReady();
}
},
onMountError: props.onMountError,
});
const isQRScannerEnabled = React.useMemo(() => {
return !!(props.barCodeScannerSettings?.barCodeTypes?.includes('qr') && !!props.onBarCodeScanned);
}, [props.barCodeScannerSettings?.barCodeTypes, props.onBarCodeScanned]);
useWebQRScanner(video, {
interval: props.barCodeScannerSettings?.interval,
isEnabled: isQRScannerEnabled,
captureOptions: { scale: 1, isImageMirror: native.type === CameraType.front },
onScanned(event) {
if (props.onBarCodeScanned) {
props.onBarCodeScanned(event);
}
},
// onError: props.onMountError,
});
// const [pause, setPaused]
React.useImperativeHandle(ref, () => ({
async getAvailablePictureSizes(ratio) {
return PictureSizes;
},
async takePicture(options) {
if (!video.current || video.current?.readyState !== video.current?.HAVE_ENOUGH_DATA) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'HTMLVideoElement does not have enough camera data to construct an image yet.');
}
const settings = native.mediaTrackSettings;
if (!settings) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'MediaStream is not ready yet.');
}
return capture(video.current, settings, {
...options,
// This will always be defined, the option gets added to a queue in the upper-level. We should replace the original so it isn't called twice.
onPictureSaved(picture) {
if (options.onPictureSaved) {
options.onPictureSaved(picture);
}
if (props.onPictureSaved) {
props.onPictureSaved({ nativeEvent: { data: picture, id: -1 } });
}
},
});
},
async resumePreview() {
if (video.current) {
video.current.play();
}
},
async pausePreview() {
if (video.current) {
video.current.pause();
}
},
}), [native.mediaTrackSettings, props.onPictureSaved]);
// TODO(Bacon): Create a universal prop, on native the microphone is only used when recording videos.
// Because we don't support recording video in the browser we don't need the user to give microphone permissions.
const isMuted = true;
const style = React.useMemo(() => {
const isFrontFacingCamera = native.type === CameraManager.Type.front;
return [
StyleSheet.absoluteFill,
styles.video,
{
// Flip the camera
transform: isFrontFacingCamera ? [{ scaleX: -1 }] : undefined,
},
];
}, [native.type]);
return (<View pointerEvents="box-none" style={[styles.videoWrapper, props.style]}>
<Video autoPlay playsInline muted={isMuted} poster={poster}
// webkitPlaysinline
pointerEvents={props.pointerEvents} ref={video} style={style}/>
{props.children}
</View>);
});
export default ExponentCamera;
const Video = React.forwardRef((props, ref) => createElement('video', { ...props, ref }));
const styles = StyleSheet.create({
videoWrapper: {
flex: 1,
alignItems: 'stretch',
},
video: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
});
//# sourceMappingURL=ExpoCamera.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
declare const _default: any;
export default _default;
//# sourceMappingURL=ExpoCameraManager.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.d.ts","sourceRoot":"","sources":["../src/ExpoCameraManager.ts"],"names":[],"mappings":";AAEA,wBAAiD"}

View File

@@ -0,0 +1,3 @@
import { requireNativeModule } from 'expo-modules-core';
export default requireNativeModule('ExpoCamera');
//# sourceMappingURL=ExpoCameraManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.js","sourceRoot":"","sources":["../src/ExpoCameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,eAAe,mBAAmB,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nexport default requireNativeModule('ExpoCamera');\n"]}

View File

@@ -0,0 +1,41 @@
import { CameraCapturedPicture, CameraPictureOptions, PermissionResponse } from './legacy/Camera.types';
import { ExponentCameraRef } from './legacy/ExpoCamera.web';
declare const _default: {
readonly Type: {
back: string;
front: string;
};
readonly FlashMode: {
on: string;
off: string;
auto: string;
torch: string;
};
readonly AutoFocus: {
on: string;
off: string;
auto: string;
singleShot: string;
};
readonly WhiteBalance: {
auto: string;
continuous: string;
manual: string;
};
readonly VideoQuality: {};
readonly VideoStabilization: {};
isAvailableAsync(): Promise<boolean>;
takePicture(options: CameraPictureOptions, camera: ExponentCameraRef): Promise<CameraCapturedPicture>;
pausePreview(camera: ExponentCameraRef): Promise<void>;
resumePreview(camera: ExponentCameraRef): Promise<void>;
getAvailableCameraTypesAsync(): Promise<string[]>;
getAvailablePictureSizes(ratio: string, camera: ExponentCameraRef): Promise<string[]>;
getPermissionsAsync(): Promise<PermissionResponse>;
requestPermissionsAsync(): Promise<PermissionResponse>;
getCameraPermissionsAsync(): Promise<PermissionResponse>;
requestCameraPermissionsAsync(): Promise<PermissionResponse>;
getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
};
export default _default;
//# sourceMappingURL=ExpoCameraManager.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.web.d.ts","sourceRoot":"","sources":["../src/ExpoCameraManager.web.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EAEpB,kBAAkB,EAEnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;wBA0JhC,QAAQ,OAAO,CAAC;yBAI/B,oBAAoB,UACrB,iBAAiB,GACxB,QAAQ,qBAAqB,CAAC;yBAGN,iBAAiB,GAAG,QAAQ,IAAI,CAAC;0BAGhC,iBAAiB,GAAG,QAAQ,IAAI,CAAC;oCAGvB,QAAQ,MAAM,EAAE,CAAC;oCAYjB,MAAM,UAAU,iBAAiB,GAAG,QAAQ,MAAM,EAAE,CAAC;2BAe9D,QAAQ,kBAAkB,CAAC;+BAGvB,QAAQ,kBAAkB,CAAC;iCAGzB,QAAQ,kBAAkB,CAAC;qCAGvB,QAAQ,kBAAkB,CAAC;qCAG3B,QAAQ,kBAAkB,CAAC;yCAGvB,QAAQ,kBAAkB,CAAC;;AA7FxE,wBA4GE"}

View File

@@ -0,0 +1,213 @@
import { UnavailabilityError } from 'expo-modules-core';
import { CameraType, PermissionStatus, } from './legacy/Camera.types';
import { canGetUserMedia, isBackCameraAvailableAsync, isFrontCameraAvailableAsync, } from './web/WebUserMediaManager';
function getUserMedia(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
// TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
function () {
const error = new Error('Permission unimplemented');
error.code = 0;
error.name = 'NotAllowedError';
throw error;
};
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
function handleGetUserMediaError({ message }) {
// name: NotAllowedError
// code: 0
if (message === 'Permission dismissed') {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
else {
// TODO: Bacon: [OSX] The system could deny access to chrome.
// TODO: Bacon: add: { status: 'unimplemented' }
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
async function handleRequestPermissionsAsync() {
try {
await getUserMedia({
video: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
}
async function handlePermissionsQueryAsync(query) {
if (!navigator?.permissions?.query) {
throw new UnavailabilityError('expo-camera', 'navigator.permissions API is not available');
}
try {
const { state } = await navigator.permissions.query({ name: query });
switch (state) {
case 'prompt':
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
case 'granted':
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
case 'denied':
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
catch (e) {
// Firefox doesn't support querying for the camera permission, so return undetermined status
if (e instanceof TypeError) {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
throw e;
}
}
export default {
get Type() {
return {
back: 'back',
front: 'front',
};
},
get FlashMode() {
return {
on: 'on',
off: 'off',
auto: 'auto',
torch: 'torch',
};
},
get AutoFocus() {
return {
on: 'on',
off: 'off',
auto: 'auto',
singleShot: 'singleShot',
};
},
get WhiteBalance() {
return {
auto: 'auto',
continuous: 'continuous',
manual: 'manual',
};
},
get VideoQuality() {
return {};
},
get VideoStabilization() {
return {};
},
async isAvailableAsync() {
return canGetUserMedia();
},
async takePicture(options, camera) {
return await camera.takePicture(options);
},
async pausePreview(camera) {
await camera.pausePreview();
},
async resumePreview(camera) {
return await camera.resumePreview();
},
async getAvailableCameraTypesAsync() {
if (!canGetUserMedia() || !navigator.mediaDevices.enumerateDevices)
return [];
const devices = await navigator.mediaDevices.enumerateDevices();
const types = await Promise.all([
(await isFrontCameraAvailableAsync(devices)) && CameraType.front,
(await isBackCameraAvailableAsync()) && CameraType.back,
]);
return types.filter(Boolean);
},
async getAvailablePictureSizes(ratio, camera) {
return await camera.getAvailablePictureSizes(ratio);
},
/* async getSupportedRatios(camera: ExponentCameraRef): Promise<string[]> {
// TODO: Support on web
},
async record(
options?: CameraRecordingOptions,
camera: ExponentCameraRef
): Promise<{ uri: string }> {
// TODO: Support on web
},
async stopRecording(camera: ExponentCameraRef): Promise<void> {
// TODO: Support on web
}, */
async getPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getCameraPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestCameraPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getMicrophonePermissionsAsync() {
return handlePermissionsQueryAsync('microphone');
},
async requestMicrophonePermissionsAsync() {
try {
await getUserMedia({
audio: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
},
};
//# sourceMappingURL=ExpoCameraManager.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,68 @@
import { BarcodeScanningResult, BarcodeType } from './Camera.types';
import { PermissionResponse } from './legacy/Camera.types';
export { default as CameraView } from './CameraView';
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function getCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function requestCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = useCameraPermissions();
* ```
*/
export declare const useCameraPermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
export declare const useMicrophonePermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
/**
* Scan bar codes from the image at the given URL.
* @param url URL to get the image from.
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
* the platform.
* > __Note:__ Only QR codes are supported on iOS.
* On android, the barcode should take up the majority of the image for best results.
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
*/
export declare function scanFromURLAsync(url: string, barcodeTypes?: BarcodeType[]): Promise<BarcodeScanningResult[]>;
export * from './Camera.types';
/**
* @hidden
*/
export declare const Camera: {
getCameraPermissionsAsync: typeof getCameraPermissionsAsync;
requestCameraPermissionsAsync: typeof requestCameraPermissionsAsync;
getMicrophonePermissionsAsync: typeof getMicrophonePermissionsAsync;
requestMicrophonePermissionsAsync: typeof requestMicrophonePermissionsAsync;
scanFromURLAsync: typeof scanFromURLAsync;
};
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAGrD;;;GAGG;AACH,iBAAe,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEtE;AAGD;;;;GAIG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,wLAG/B,CAAC;AAGH;;;GAGG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;GAIG;AACH,iBAAe,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE9E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,wLAGnC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,WAAW,EAAW,GACnC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAElC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}

View File

@@ -0,0 +1,90 @@
import { createPermissionHook } from 'expo-modules-core';
import CameraManager from './ExpoCameraManager';
export { default as CameraView } from './CameraView';
// @needsAudit
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function getCameraPermissionsAsync() {
return CameraManager.getCameraPermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function requestCameraPermissionsAsync() {
return CameraManager.requestCameraPermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = useCameraPermissions();
* ```
*/
export const useCameraPermissions = createPermissionHook({
getMethod: getCameraPermissionsAsync,
requestMethod: requestCameraPermissionsAsync,
});
// @needsAudit
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function getMicrophonePermissionsAsync() {
return CameraManager.getMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function requestMicrophonePermissionsAsync() {
return CameraManager.requestMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
export const useMicrophonePermissions = createPermissionHook({
getMethod: getMicrophonePermissionsAsync,
requestMethod: requestMicrophonePermissionsAsync,
});
/**
* Scan bar codes from the image at the given URL.
* @param url URL to get the image from.
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
* the platform.
* > __Note:__ Only QR codes are supported on iOS.
* On android, the barcode should take up the majority of the image for best results.
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
*/
export async function scanFromURLAsync(url, barcodeTypes = ['qr']) {
return CameraManager.scanFromURLAsync(url, barcodeTypes);
}
export * from './Camera.types';
/**
* @hidden
*/
export const Camera = {
getCameraPermissionsAsync,
requestCameraPermissionsAsync,
getMicrophonePermissionsAsync,
requestMicrophonePermissionsAsync,
scanFromURLAsync,
};
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,yBAAyB;IACtC,OAAO,aAAa,CAAC,yBAAyB,EAAE,CAAC;AACnD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;IACvD,SAAS,EAAE,yBAAyB;IACpC,aAAa,EAAE,6BAA6B;CAC7C,CAAC,CAAC;AAEH,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,iCAAiC;IAC9C,OAAO,aAAa,CAAC,iCAAiC,EAAE,CAAC;AAC3D,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;IAC3D,SAAS,EAAE,6BAA6B;IACxC,aAAa,EAAE,iCAAiC;CACjD,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,eAA8B,CAAC,IAAI,CAAC;IAEpC,OAAO,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,yBAAyB;IACzB,6BAA6B;IAC7B,6BAA6B;IAC7B,iCAAiC;IACjC,gBAAgB;CACjB,CAAC","sourcesContent":["import { createPermissionHook } from 'expo-modules-core';\n\nimport { BarcodeScanningResult, BarcodeType } from './Camera.types';\nimport CameraManager from './ExpoCameraManager';\nimport { PermissionResponse } from './legacy/Camera.types';\n\nexport { default as CameraView } from './CameraView';\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing camera.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing camera.\n * On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the camera.\n * This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = useCameraPermissions();\n * ```\n */\nexport const useCameraPermissions = createPermissionHook({\n getMethod: getCameraPermissionsAsync,\n requestMethod: requestCameraPermissionsAsync,\n});\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing microphone.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing the microphone.\n * On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the microphone.\n * This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Camera.useMicrophonePermissions();\n * ```\n */\nexport const useMicrophonePermissions = createPermissionHook({\n getMethod: getMicrophonePermissionsAsync,\n requestMethod: requestMicrophonePermissionsAsync,\n});\n\n/**\n * Scan bar codes from the image at the given URL.\n * @param url URL to get the image from.\n * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on\n * the platform.\n * > __Note:__ Only QR codes are supported on iOS.\n * On android, the barcode should take up the majority of the image for best results.\n * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type\n * refers to the barcode type that was scanned and the data is the information encoded in the barcode.\n */\nexport async function scanFromURLAsync(\n url: string,\n barcodeTypes: BarcodeType[] = ['qr']\n): Promise<BarcodeScanningResult[]> {\n return CameraManager.scanFromURLAsync(url, barcodeTypes);\n}\n\nexport * from './Camera.types';\n\n/**\n * @hidden\n */\nexport const Camera = {\n getCameraPermissionsAsync,\n requestCameraPermissionsAsync,\n getMicrophonePermissionsAsync,\n requestMicrophonePermissionsAsync,\n scanFromURLAsync,\n};\n"]}

View File

@@ -0,0 +1,164 @@
import * as React from 'react';
import { CameraCapturedPicture, CameraOrientation, CameraPictureOptions, CameraProps, CameraRecordingOptions, CameraType, ConstantsType, PermissionResponse, VideoCodec } from './Camera.types';
export default class Camera extends React.Component<CameraProps> {
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static isAvailableAsync(): Promise<boolean>;
/**
* Returns a list of camera types `['front', 'back']`. This is useful for desktop browsers which only have front-facing cameras.
* @platform web
*/
static getAvailableCameraTypesAsync(): Promise<CameraType[]>;
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static getAvailableVideoCodecsAsync(): Promise<VideoCodec[]>;
static Constants: ConstantsType;
static ConversionTables: {
type: Record<"front" | "back", string | number | undefined>;
flashMode: Record<"off" | "on" | "auto" | "torch", string | number | undefined>;
autoFocus: Record<"off" | "on" | "auto" | "singleShot", string | number | boolean | undefined>;
whiteBalance: Record<"auto" | "sunny" | "cloudy" | "shadow" | "incandescent" | "fluorescent" | "continuous" | "manual", string | number | undefined>;
};
static defaultProps: CameraProps;
/**
* @deprecated Use `getCameraPermissionsAsync` or `getMicrophonePermissionsAsync` instead.
* Checks user's permissions for accessing camera.
*/
static getPermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify both `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` entries in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
* @deprecated Use `requestCameraPermissionsAsync` or `requestMicrophonePermissionsAsync` instead.
*/
static requestPermissionsAsync(): Promise<PermissionResponse>;
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static getCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static requestCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useCameraPermissions();
* ```
*/
static useCameraPermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
static useMicrophonePermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
_cameraHandle?: number | null;
_cameraRef?: React.Component | null;
_lastEvents: {
[eventName: string]: string;
};
_lastEventsTimes: {
[eventName: string]: Date;
};
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcachedirectory)
* > to make a permanent copy of the image.
*/
takePictureAsync(options?: CameraPictureOptions): Promise<CameraCapturedPicture>;
/**
* Get aspect ratios that are supported by the device and can be passed via `ratio` prop.
* @return Returns a Promise that resolves to an array of strings representing ratios, eg. `['4:3', '1:1']`.
* @platform android
*/
getSupportedRatiosAsync(): Promise<string[]>;
/**
* Get picture sizes that are supported by the device for given `ratio`.
* @param ratio A string representing aspect ratio of sizes to be returned.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
getAvailablePictureSizesAsync(ratio: string): Promise<string[]>;
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
recordAsync(options?: CameraRecordingOptions): Promise<{
uri: string;
}>;
/**
* Stops recording if any is in progress.
*/
stopRecording(): Promise<void>;
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
pausePreview(): Promise<void>;
/**
* Resumes the camera preview.
*/
resumePreview(): Promise<void>;
_onCameraReady: () => void;
_onMountError: ({ nativeEvent }: {
nativeEvent: {
message: string;
};
}) => void;
_onResponsiveOrientationChanged: ({ nativeEvent, }: {
nativeEvent: {
orientation: CameraOrientation;
};
}) => void;
_onObjectDetected: (callback?: Function) => ({ nativeEvent }: {
nativeEvent: any;
}) => void;
_setReference: (ref?: React.Component) => void;
render(): JSX.Element;
}
export declare const Constants: ConstantsType, getPermissionsAsync: typeof Camera.getPermissionsAsync, requestPermissionsAsync: typeof Camera.requestPermissionsAsync, getCameraPermissionsAsync: typeof Camera.getCameraPermissionsAsync, requestCameraPermissionsAsync: typeof Camera.requestCameraPermissionsAsync, getMicrophonePermissionsAsync: typeof Camera.getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync: typeof Camera.requestMicrophonePermissionsAsync;
//# sourceMappingURL=Camera.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Camera.d.ts","sourceRoot":"","sources":["../../src/legacy/Camera.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,UAAU,EACX,MAAM,gBAAgB,CAAC;AAoDxB,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC;IAC9D;;;;;OAKG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAQjD;;;OAGG;WACU,4BAA4B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IASlE;;;;OAIG;WACU,4BAA4B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQlE,MAAM,CAAC,SAAS,EAAE,aAAa,CAQ7B;IAGF,MAAM,CAAC,gBAAgB;;;;;MAAoB;IAE3C,MAAM,CAAC,YAAY,EAAE,WAAW,CAS9B;IAGF;;;OAGG;WACU,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAQ/D;;;;;OAKG;WACU,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAQnE;;;OAGG;WACU,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAKrE;;;;OAIG;WACU,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAKzE;;;;;;;;OAQG;IACH,MAAM,CAAC,oBAAoB,yLAGxB;IAGH;;;OAGG;WACU,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAKzE;;;;OAIG;WACU,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAK7E;;;;;;;;OAQG;IACH,MAAM,CAAC,wBAAwB,yLAG5B;IAEH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAM;IAClD,gBAAgB,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAM;IAGrD;;;;;;;;;;;;;;;OAeG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAMtF;;;;OAIG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQlD;;;;;OAKG;IACG,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOrE;;;;;;;;OAQG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAS7E;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQnC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,cAAc,aAIZ;IAEF,aAAa;qBAAoC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE;eAIlE;IAEF,+BAA+B;qBAGhB;YAAE,aAAa,iBAAiB,CAAA;SAAE;eAK/C;IAEF,iBAAiB,cACH,QAAQ;qBACa,GAAG;eAgBlC;IAEJ,aAAa,SAAU,MAAM,SAAS,UAapC;IAEF,MAAM;CAsBP;AAED,eAAO,MACL,SAAS,iBACT,mBAAmB,qCACnB,uBAAuB,yCACvB,yBAAyB,2CACzB,6BAA6B,+CAC7B,6BAA6B,+CAC7B,iCAAiC,iDACzB,CAAC"}

View File

@@ -0,0 +1,325 @@
import { createPermissionHook, Platform, UnavailabilityError } from 'expo-modules-core';
import * as React from 'react';
import { findNodeHandle } from 'react-native';
import ExpoCamera from './ExpoCamera';
import CameraManager from './ExpoCameraManager';
import { ConversionTables, ensureNativeProps } from './utils/props';
const EventThrottleMs = 500;
const _PICTURE_SAVED_CALLBACKS = {};
let _GLOBAL_PICTURE_ID = 1;
function ensurePictureOptions(options) {
const pictureOptions = !options || typeof options !== 'object' ? {} : options;
if (!pictureOptions.quality) {
pictureOptions.quality = 1;
}
if (pictureOptions.onPictureSaved) {
const id = _GLOBAL_PICTURE_ID++;
_PICTURE_SAVED_CALLBACKS[id] = pictureOptions.onPictureSaved;
pictureOptions.id = id;
pictureOptions.fastMode = true;
}
return pictureOptions;
}
function ensureRecordingOptions(options) {
let recordingOptions = options || {};
if (!recordingOptions || typeof recordingOptions !== 'object') {
recordingOptions = {};
}
else if (typeof recordingOptions.quality === 'string') {
recordingOptions.quality = Camera.Constants.VideoQuality[recordingOptions.quality];
}
return recordingOptions;
}
function _onPictureSaved({ nativeEvent, }) {
const { id, data } = nativeEvent;
const callback = _PICTURE_SAVED_CALLBACKS[id];
if (callback) {
callback(data);
delete _PICTURE_SAVED_CALLBACKS[id];
}
}
export default class Camera extends React.Component {
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static async isAvailableAsync() {
if (!CameraManager.isAvailableAsync) {
throw new UnavailabilityError('expo-camera', 'isAvailableAsync');
}
return await CameraManager.isAvailableAsync();
}
/**
* Returns a list of camera types `['front', 'back']`. This is useful for desktop browsers which only have front-facing cameras.
* @platform web
*/
static async getAvailableCameraTypesAsync() {
if (!CameraManager.getAvailableCameraTypesAsync) {
throw new UnavailabilityError('expo-camera', 'getAvailableCameraTypesAsync');
}
return await CameraManager.getAvailableCameraTypesAsync();
}
// @needsAudit
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static async getAvailableVideoCodecsAsync() {
if (!CameraManager.getAvailableVideoCodecsAsync) {
throw new UnavailabilityError('Camera', 'getAvailableVideoCodecsAsync');
}
return await CameraManager.getAvailableVideoCodecsAsync();
}
static Constants = {
Type: CameraManager.Type,
FlashMode: CameraManager.FlashMode,
AutoFocus: CameraManager.AutoFocus,
WhiteBalance: CameraManager.WhiteBalance,
VideoQuality: CameraManager.VideoQuality,
VideoStabilization: CameraManager.VideoStabilization || {},
VideoCodec: CameraManager.VideoCodec,
};
// Values under keys from this object will be transformed to native options
static ConversionTables = ConversionTables;
static defaultProps = {
zoom: 0,
ratio: '4:3',
focusDepth: 0,
faceDetectorSettings: {},
type: CameraManager.Type.back,
autoFocus: CameraManager.AutoFocus.on,
flashMode: CameraManager.FlashMode.off,
whiteBalance: CameraManager.WhiteBalance.auto,
};
// @needsAudit
/**
* @deprecated Use `getCameraPermissionsAsync` or `getMicrophonePermissionsAsync` instead.
* Checks user's permissions for accessing camera.
*/
static async getPermissionsAsync() {
console.warn(`"getPermissionsAsync()" is now deprecated. Please use "getCameraPermissionsAsync()" or "getMicrophonePermissionsAsync()" instead.`);
return CameraManager.getPermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify both `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` entries in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
* @deprecated Use `requestCameraPermissionsAsync` or `requestMicrophonePermissionsAsync` instead.
*/
static async requestPermissionsAsync() {
console.warn(`"requestPermissionsAsync()" is now deprecated. Please use "requestCameraPermissionsAsync()" or "requestMicrophonePermissionsAsync()" instead.`);
return CameraManager.requestPermissionsAsync();
}
// @needsAudit
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async getCameraPermissionsAsync() {
return CameraManager.getCameraPermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async requestCameraPermissionsAsync() {
return CameraManager.requestCameraPermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useCameraPermissions();
* ```
*/
static useCameraPermissions = createPermissionHook({
getMethod: Camera.getCameraPermissionsAsync,
requestMethod: Camera.requestCameraPermissionsAsync,
});
// @needsAudit
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async getMicrophonePermissionsAsync() {
return CameraManager.getMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async requestMicrophonePermissionsAsync() {
return CameraManager.requestMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
static useMicrophonePermissions = createPermissionHook({
getMethod: Camera.getMicrophonePermissionsAsync,
requestMethod: Camera.requestMicrophonePermissionsAsync,
});
_cameraHandle;
_cameraRef;
_lastEvents = {};
_lastEventsTimes = {};
// @needsAudit
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcachedirectory)
* > to make a permanent copy of the image.
*/
async takePictureAsync(options) {
const pictureOptions = ensurePictureOptions(options);
return await CameraManager.takePicture(pictureOptions, this._cameraHandle);
}
/**
* Get aspect ratios that are supported by the device and can be passed via `ratio` prop.
* @return Returns a Promise that resolves to an array of strings representing ratios, eg. `['4:3', '1:1']`.
* @platform android
*/
async getSupportedRatiosAsync() {
if (!CameraManager.getSupportedRatios) {
throw new UnavailabilityError('Camera', 'getSupportedRatiosAsync');
}
return await CameraManager.getSupportedRatios(this._cameraHandle);
}
/**
* Get picture sizes that are supported by the device for given `ratio`.
* @param ratio A string representing aspect ratio of sizes to be returned.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
async getAvailablePictureSizesAsync(ratio) {
if (!CameraManager.getAvailablePictureSizes) {
throw new UnavailabilityError('Camera', 'getAvailablePictureSizesAsync');
}
return await CameraManager.getAvailablePictureSizes(ratio, this._cameraHandle);
}
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
async recordAsync(options) {
if (!CameraManager.record) {
throw new UnavailabilityError('Camera', 'recordAsync');
}
const recordingOptions = ensureRecordingOptions(options);
return await CameraManager.record(recordingOptions, this._cameraHandle);
}
/**
* Stops recording if any is in progress.
*/
async stopRecording() {
if (!CameraManager.stopRecording) {
throw new UnavailabilityError('Camera', 'stopRecording');
}
return await CameraManager.stopRecording(this._cameraHandle);
}
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
async pausePreview() {
if (!CameraManager.pausePreview) {
throw new UnavailabilityError('Camera', 'pausePreview');
}
return await CameraManager.pausePreview(this._cameraHandle);
}
/**
* Resumes the camera preview.
*/
async resumePreview() {
if (!CameraManager.resumePreview) {
throw new UnavailabilityError('Camera', 'resumePreview');
}
return await CameraManager.resumePreview(this._cameraHandle);
}
_onCameraReady = () => {
if (this.props.onCameraReady) {
this.props.onCameraReady();
}
};
_onMountError = ({ nativeEvent }) => {
if (this.props.onMountError) {
this.props.onMountError(nativeEvent);
}
};
_onResponsiveOrientationChanged = ({ nativeEvent, }) => {
if (this.props.onResponsiveOrientationChanged) {
this.props.onResponsiveOrientationChanged(nativeEvent);
}
};
_onObjectDetected = (callback) => ({ nativeEvent }) => {
const { type } = nativeEvent;
if (this._lastEvents[type] &&
this._lastEventsTimes[type] &&
JSON.stringify(nativeEvent) === this._lastEvents[type] &&
new Date().getTime() - this._lastEventsTimes[type].getTime() < EventThrottleMs) {
return;
}
if (callback) {
callback(nativeEvent);
this._lastEventsTimes[type] = new Date();
this._lastEvents[type] = JSON.stringify(nativeEvent);
}
};
_setReference = (ref) => {
if (ref) {
this._cameraRef = ref;
// TODO(Bacon): Unify these - perhaps with hooks?
if (Platform.OS === 'web') {
this._cameraHandle = ref;
}
else {
this._cameraHandle = findNodeHandle(ref);
}
}
else {
this._cameraRef = null;
this._cameraHandle = null;
}
};
render() {
const nativeProps = ensureNativeProps(this.props);
const onBarCodeScanned = this.props.onBarCodeScanned
? this._onObjectDetected(this.props.onBarCodeScanned)
: undefined;
const onFacesDetected = this._onObjectDetected(this.props.onFacesDetected);
return (<ExpoCamera {...nativeProps} ref={this._setReference} onCameraReady={this._onCameraReady} onMountError={this._onMountError} onBarCodeScanned={onBarCodeScanned} onFacesDetected={onFacesDetected} onPictureSaved={_onPictureSaved} onResponsiveOrientationChanged={this._onResponsiveOrientationChanged}/>);
}
}
export const { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } = Camera;
//# sourceMappingURL=Camera.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,515 @@
import { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions } from 'expo-modules-core';
import type { ViewProps } from 'react-native';
export declare enum CameraType {
front = "front",
back = "back"
}
export declare enum FlashMode {
on = "on",
off = "off",
auto = "auto",
torch = "torch"
}
export declare enum AutoFocus {
on = "on",
off = "off",
/**
* @platform web
*/
auto = "auto",
/**
* @platform web
*/
singleShot = "singleShot"
}
export declare enum WhiteBalance {
auto = "auto",
/**
* @platform android
* @platform ios
*/
sunny = "sunny",
/**
* @platform android
* @platform ios
*/
cloudy = "cloudy",
/**
* @platform android
* @platform ios
*/
shadow = "shadow",
/**
* @platform android
* @platform ios
*/
incandescent = "incandescent",
/**
* @platform android
* @platform ios
*/
fluorescent = "fluorescent",
/**
* @platform web
*/
continuous = "continuous",
/**
* @platform web
*/
manual = "manual"
}
export declare enum ImageType {
png = "png",
jpg = "jpg"
}
/**
* This option specifies what codec to use when recording a video.
* @platform ios
*/
export declare enum VideoCodec {
H264 = "avc1",
HEVC = "hvc1",
JPEG = "jpeg",
AppleProRes422 = "apcn",
AppleProRes4444 = "ap4h"
}
/**
* This option specifies the stabilization mode to use when recording a video.
* @platform ios
*/
export declare enum VideoStabilization {
off = "off",
standard = "standard",
cinematic = "cinematic",
auto = "auto"
}
export declare enum VideoQuality {
'2160p' = "2160p",
'1080p' = "1080p",
'720p' = "720p",
'480p' = "480p",
'4:3' = "4:3"
}
export declare enum CameraOrientation {
portrait = 1,
portraitUpsideDown = 2,
landscapeLeft = 3,
landscapeRight = 4
}
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type ImageSize = {
width: number;
height: number;
};
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type WebCameraSettings = {
autoFocus?: string;
flashMode?: string;
whiteBalance?: string;
exposureCompensation?: number;
colorTemperature?: number;
iso?: number;
brightness?: number;
contrast?: number;
saturation?: number;
sharpness?: number;
focusDistance?: number;
zoom?: number;
};
export type CameraCapturedPicture = {
/**
* Captured image width.
*/
width: number;
/**
* Captured image height.
*/
height: number;
/**
* On web, the value of `uri` is the same as `base64` because file system URLs are not supported in the browser.
*/
uri: string;
/**
* A Base64 representation of the image.
*/
base64?: string;
/**
* On Android and iOS this object may include various fields based on the device and operating system.
* On web, it is a partial representation of the [`MediaTrackSettings`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings) dictionary.
*/
exif?: Partial<MediaTrackSettings> | any;
};
export type CameraPictureOptions = {
/**
* Specify the compression quality from `0` to `1`. `0` means compress for small size, and `1` means compress for maximum quality.
*/
quality?: number;
/**
* Whether to also include the image data in Base64 format.
*/
base64?: boolean;
/**
* Whether to also include the EXIF data for the image.
*/
exif?: boolean;
/**
* Additional EXIF data to be included for the image. Only useful when `exif` option is set to `true`.
* @platform android
* @platform ios
*/
additionalExif?: Record<string, any>;
/**
* A callback invoked when picture is saved. If set, the promise of this method will resolve immediately with no data after picture is captured.
* The data that it should contain will be passed to this callback. If displaying or processing a captured photo right after taking it
* is not your case, this callback lets you skip waiting for it to be saved.
* @param picture
*/
onPictureSaved?: (picture: CameraCapturedPicture) => void;
/**
* If set to `true`, camera skips orientation adjustment and returns an image straight from the device's camera.
* If enabled, `quality` option is discarded (processing pipeline is skipped as a whole).
* Although enabling this option reduces image delivery time significantly, it may cause the image to appear in a wrong orientation
* in the `Image` component (at the time of writing, it does not respect EXIF orientation of the images).
* > **Note**: Enabling `skipProcessing` would cause orientation uncertainty. `Image` component does not respect EXIF
* > stored orientation information, that means obtained image would be displayed wrongly (rotated by 90°, 180° or 270°).
* > Different devices provide different orientations. For example some Sony Xperia or Samsung devices don't provide
* > correctly oriented images by default. To always obtain correctly oriented image disable `skipProcessing` option.
*/
skipProcessing?: boolean;
/**
* @platform web
*/
scale?: number;
/**
* @platform web
*/
imageType?: ImageType;
/**
* @platform web
*/
isImageMirror?: boolean;
/**
* @hidden
*/
id?: number;
/**
* @hidden
*/
fastMode?: boolean;
/**
* @hidden
*/
maxDownsampling?: number;
};
export type CameraRecordingOptions = {
/**
* Maximum video duration in seconds.
*/
maxDuration?: number;
/**
* Maximum video file size in bytes.
*/
maxFileSize?: number;
/**
* Specify the quality of recorded video. Use one of [`VideoQuality.<value>`](#videoquality).
* Possible values: for 16:9 resolution `2160p`, `1080p`, `720p`, `480p` : `Android only` and for 4:3 `4:3` (the size is 640x480).
* If the chosen quality is not available for a device, the highest available is chosen.
*/
quality?: number | string;
/**
* If present, video will be recorded with no sound.
*/
mute?: boolean;
/**
* If `true`, the recorded video will be flipped along the vertical axis. iOS flips videos recorded with the front camera by default,
* but you can reverse that back by setting this to `true`. On Android, this is handled in the user's device settings.
* @platform ios
*/
mirror?: boolean;
/**
* Only works if `useCamera2Api` is set to `true`. This option specifies a desired video bitrate. For example, `5*1000*1000` would be 5Mbps.
* @platform android
*/
videoBitrate?: number;
/**
* This option specifies what codec to use when recording the video. See [`VideoCodec`](#videocodec) for the possible values.
* @platform ios
*/
codec?: VideoCodec;
};
/**
* @hidden
*/
export type PictureSavedListener = (event: {
nativeEvent: {
data: CameraCapturedPicture;
id: number;
};
}) => void;
/**
* @hidden
*/
export type CameraReadyListener = () => void;
/**
* @hidden
*/
export type ResponsiveOrientationChangedListener = (event: {
nativeEvent: ResponsiveOrientationChanged;
}) => void;
export type ResponsiveOrientationChanged = {
orientation: CameraOrientation;
};
/**
* @hidden
*/
export type MountErrorListener = (event: {
nativeEvent: CameraMountError;
}) => void;
export type CameraMountError = {
message: string;
};
export type Point = {
x: number;
y: number;
};
export type BarCodeSize = {
/**
* The height value.
*/
height: number;
/**
* The width value.
*/
width: number;
};
/**
* These coordinates are represented in the coordinate space of the camera source (e.g. when you
* are using the camera view, these values are adjusted to the dimensions of the view).
*/
export type BarCodePoint = Point;
export type BarCodeBounds = {
/**
* The origin point of the bounding box.
*/
origin: BarCodePoint;
/**
* The size of the bounding box.
*/
size: BarCodeSize;
};
export type BarCodeScanningResult = {
/**
* The barcode type.
*/
type: string;
/**
* The parsed information encoded in the bar code.
*/
data: string;
/**
* The raw information encoded in the bar code.
* May be different from `data` depending on the barcode type.
* @platform android
* @hidden
*/
raw?: string;
/**
* Corner points of the bounding box.
* `cornerPoints` is not always available and may be empty. On iOS, for `code39` and `pdf417`
* you don't get this value.
*/
cornerPoints: BarCodePoint[];
/**
* The [BarCodeBounds](#barcodebounds) object.
* `bounds` in some case will be representing an empty rectangle.
* Moreover, `bounds` doesn't have to bound the whole barcode.
* For some types, they will represent the area used by the scanner.
*/
bounds: BarCodeBounds;
};
export type FaceDetectionResult = {
/**
* Array of objects representing results of face detection.
* See [`FaceFeature`](facedetector/#facefeature) in FaceDetector documentation for more details.
*/
faces: object[];
};
/**
* @hidden
*/
export type ConstantsType = {
Type: CameraType;
FlashMode: FlashMode;
AutoFocus: AutoFocus;
WhiteBalance: WhiteBalance;
VideoQuality: VideoQuality;
VideoStabilization: VideoStabilization;
VideoCodec: VideoCodec;
};
export type CameraProps = ViewProps & {
/**
* Camera facing. Use one of `CameraType`. When `CameraType.front`, use the front-facing camera.
* When `CameraType.back`, use the back-facing camera.
* @default CameraType.back
*/
type?: number | CameraType;
/**
* Camera flash mode. Use one of [`FlashMode.<value>`](#flashmode-1). When `FlashMode.on`, the flash on your device will
* turn on when taking a picture, when `FlashMode.off`, it won't. Setting to `FlashMode.auto` will fire flash if required,
* `FlashMode.torch` turns on flash during the preview.
* @default FlashMode.off
*/
flashMode?: number | FlashMode;
/**
* Camera white balance. Use one of [`WhiteBalance.<value>`](#whitebalance). If a device does not support any of these values previous one is used.
* @default WhiteBalance.auto
*/
whiteBalance?: number | WhiteBalance;
/**
* State of camera auto focus. Use one of [`AutoFocus.<value>`](#autofocus-1). When `AutoFocus.on`,
* auto focus will be enabled, when `AutoFocus.off`, it won't and focus will lock as it was in the moment of change,
* but it can be adjusted on some devices via `focusDepth` prop.
* @default AutoFocus.on
*/
autoFocus?: boolean | number | AutoFocus;
/**
* A value between `0` and `1` being a percentage of device's max zoom. `0` - not zoomed, `1` - maximum zoom.
* @default 0
*/
zoom?: number;
/**
* A string representing aspect ratio of the preview, eg. `4:3`, `16:9`, `1:1`. To check if a ratio is supported
* by the device use [`getSupportedRatiosAsync`](#getsupportedratiosasync).
* @default 4:3
* @platform android
*/
ratio?: string;
/**
* Distance to plane of the sharpest focus. A value between `0` and `1` where: `0` - infinity focus, `1` - focus as close as possible.
* For Android this is available only for some devices and when `useCamera2Api` is set to `true`.
* @default 0
*/
focusDepth?: number;
/**
* Callback invoked when camera preview has been set.
*/
onCameraReady?: () => void;
/**
* Whether to use Android's Camera2 API. See `Note` at the top of this page.
* @platform android
*/
useCamera2Api?: boolean;
/**
* A string representing the size of pictures [`takePictureAsync`](#takepictureasyncoptions) will take.
* Available sizes can be fetched with [`getAvailablePictureSizesAsync`](#getavailablepicturesizesasyncratio).
*/
pictureSize?: string;
/**
* The video stabilization mode used for a video recording. Use one of [`VideoStabilization.<value>`](#videostabilization).
* You can read more about each stabilization type in [Apple Documentation](https://developer.apple.com/documentation/avfoundation/avcapturevideostabilizationmode).
* @platform ios
*/
videoStabilizationMode?: VideoStabilization;
/**
* Callback invoked when camera preview could not been started.
* @param event Error object that contains a `message`.
*/
onMountError?: (event: CameraMountError) => void;
/**
* Settings exposed by [`BarCodeScanner`](bar-code-scanner) module. Supported settings: **barCodeTypes**.
* @example
* ```tsx
* <Camera
* barCodeScannerSettings={{
* barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
* }}
* />
* ```
*/
barCodeScannerSettings?: BarCodeSettings;
/**
* Callback that is invoked when a bar code has been successfully scanned. The callback is provided with
* an object of the [`BarCodeScanningResult`](#barcodescanningresult) shape, where the `type`
* refers to the bar code type that was scanned and the `data` is the information encoded in the bar code
* (in this case of QR codes, this is often a URL). See [`BarCodeScanner.Constants.BarCodeType`](bar-code-scanner#supported-formats)
* for supported values.
* @param scanningResult
*/
onBarCodeScanned?: (scanningResult: BarCodeScanningResult) => void;
/**
* A settings object passed directly to an underlying module providing face detection features.
* See [`DetectionOptions`](facedetector/#detectionoptions) in FaceDetector documentation for details.
*/
faceDetectorSettings?: object;
/**
* Callback invoked with results of face detection on the preview.
* See [`DetectionResult`](facedetector/#detectionresult) in FaceDetector documentation for more details.
* @param faces
*/
onFacesDetected?: (faces: FaceDetectionResult) => void;
/**
* A URL for an image to be shown while the camera is loading.
* @platform web
*/
poster?: string;
/**
* Whether to allow responsive orientation of the camera when the screen orientation is locked (i.e. when set to `true`
* landscape photos will be taken if the device is turned that way, even if the app or device orientation is locked to portrait)
* @platform ios
*/
responsiveOrientationWhenOrientationLocked?: boolean;
/**
* Callback invoked when responsive orientation changes. Only applicable if `responsiveOrientationWhenOrientationLocked` is `true`
* @param event result object that contains updated orientation of camera
* @platform ios
*/
onResponsiveOrientationChanged?: (event: ResponsiveOrientationChanged) => void;
};
/**
* @hidden
*/
export type CameraNativeProps = {
pointerEvents?: any;
style?: any;
ref?: Function;
onCameraReady?: CameraReadyListener;
onMountError?: MountErrorListener;
onBarCodeScanned?: (event: {
nativeEvent: BarCodeScanningResult;
}) => void;
onFacesDetected?: (event: {
nativeEvent: FaceDetectionResult;
}) => void;
onFaceDetectionError?: (event: {
nativeEvent: Error;
}) => void;
onPictureSaved?: PictureSavedListener;
onResponsiveOrientationChanged?: ResponsiveOrientationChangedListener;
type?: number | string;
flashMode?: number | string;
autoFocus?: string | boolean | number;
focusDepth?: number;
zoom?: number;
whiteBalance?: number | string;
pictureSize?: string;
barCodeScannerSettings?: BarCodeSettings;
faceDetectorSettings?: object;
barCodeScannerEnabled?: boolean;
faceDetectorEnabled?: boolean;
ratio?: string;
useCamera2Api?: boolean;
poster?: string;
responsiveOrientationWhenOrientationLocked?: boolean;
};
export type BarCodeSettings = {
barCodeTypes: string[];
interval?: number;
};
export { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions };
//# sourceMappingURL=Camera.types.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,109 @@
import { PermissionStatus, } from 'expo-modules-core';
export var CameraType;
(function (CameraType) {
CameraType["front"] = "front";
CameraType["back"] = "back";
})(CameraType || (CameraType = {}));
export var FlashMode;
(function (FlashMode) {
FlashMode["on"] = "on";
FlashMode["off"] = "off";
FlashMode["auto"] = "auto";
FlashMode["torch"] = "torch";
})(FlashMode || (FlashMode = {}));
export var AutoFocus;
(function (AutoFocus) {
AutoFocus["on"] = "on";
AutoFocus["off"] = "off";
/**
* @platform web
*/
AutoFocus["auto"] = "auto";
/**
* @platform web
*/
AutoFocus["singleShot"] = "singleShot";
})(AutoFocus || (AutoFocus = {}));
export var WhiteBalance;
(function (WhiteBalance) {
WhiteBalance["auto"] = "auto";
/**
* @platform android
* @platform ios
*/
WhiteBalance["sunny"] = "sunny";
/**
* @platform android
* @platform ios
*/
WhiteBalance["cloudy"] = "cloudy";
/**
* @platform android
* @platform ios
*/
WhiteBalance["shadow"] = "shadow";
/**
* @platform android
* @platform ios
*/
WhiteBalance["incandescent"] = "incandescent";
/**
* @platform android
* @platform ios
*/
WhiteBalance["fluorescent"] = "fluorescent";
/**
* @platform web
*/
WhiteBalance["continuous"] = "continuous";
/**
* @platform web
*/
WhiteBalance["manual"] = "manual";
})(WhiteBalance || (WhiteBalance = {}));
export var ImageType;
(function (ImageType) {
ImageType["png"] = "png";
ImageType["jpg"] = "jpg";
})(ImageType || (ImageType = {}));
/**
* This option specifies what codec to use when recording a video.
* @platform ios
*/
export var VideoCodec;
(function (VideoCodec) {
VideoCodec["H264"] = "avc1";
VideoCodec["HEVC"] = "hvc1";
VideoCodec["JPEG"] = "jpeg";
VideoCodec["AppleProRes422"] = "apcn";
VideoCodec["AppleProRes4444"] = "ap4h";
})(VideoCodec || (VideoCodec = {}));
/**
* This option specifies the stabilization mode to use when recording a video.
* @platform ios
*/
export var VideoStabilization;
(function (VideoStabilization) {
VideoStabilization["off"] = "off";
VideoStabilization["standard"] = "standard";
VideoStabilization["cinematic"] = "cinematic";
VideoStabilization["auto"] = "auto";
})(VideoStabilization || (VideoStabilization = {}));
// @docsMissing
export var VideoQuality;
(function (VideoQuality) {
VideoQuality["2160p"] = "2160p";
VideoQuality["1080p"] = "1080p";
VideoQuality["720p"] = "720p";
VideoQuality["480p"] = "480p";
VideoQuality["4:3"] = "4:3";
})(VideoQuality || (VideoQuality = {}));
export var CameraOrientation;
(function (CameraOrientation) {
CameraOrientation[CameraOrientation["portrait"] = 1] = "portrait";
CameraOrientation[CameraOrientation["portraitUpsideDown"] = 2] = "portraitUpsideDown";
CameraOrientation[CameraOrientation["landscapeLeft"] = 3] = "landscapeLeft";
CameraOrientation[CameraOrientation["landscapeRight"] = 4] = "landscapeRight";
})(CameraOrientation || (CameraOrientation = {}));
export { PermissionStatus };
//# sourceMappingURL=Camera.types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
import { CameraNativeProps } from './Camera.types';
declare const ExponentCamera: React.ComponentType<CameraNativeProps>;
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CACb,CAAC;AAE/C,eAAe,cAAc,CAAC"}

View File

@@ -0,0 +1,4 @@
import { requireNativeViewManager } from 'expo-modules-core';
const ExponentCamera = requireNativeViewManager('ExpoCameraLegacy');
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.js","sourceRoot":"","sources":["../../src/legacy/ExpoCamera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAK7D,MAAM,cAAc,GAClB,wBAAwB,CAAC,kBAAkB,CAAC,CAAC;AAE/C,eAAe,cAAc,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\n\nimport { CameraNativeProps } from './Camera.types';\n\nconst ExponentCamera: React.ComponentType<CameraNativeProps> =\n requireNativeViewManager('ExpoCameraLegacy');\n\nexport default ExponentCamera;\n"]}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import { CameraCapturedPicture, CameraNativeProps, CameraPictureOptions } from './Camera.types';
export interface ExponentCameraRef {
getAvailablePictureSizes: (ratio: string) => Promise<string[]>;
takePicture: (options: CameraPictureOptions) => Promise<CameraCapturedPicture>;
resumePreview: () => Promise<void>;
pausePreview: () => Promise<void>;
}
declare const ExponentCamera: React.ForwardRefExoticComponent<Pick<CameraNativeProps & {
children?: React.ReactNode;
}, "type" | "flashMode" | "children" | "pointerEvents" | "style" | "zoom" | "pictureSize" | "poster" | "responsiveOrientationWhenOrientationLocked" | "ratio" | "onCameraReady" | "onMountError" | "onResponsiveOrientationChanged" | "onPictureSaved" | "autoFocus" | "whiteBalance" | "onBarCodeScanned" | "onFacesDetected" | "onFaceDetectionError" | "focusDepth" | "barCodeScannerSettings" | "faceDetectorSettings" | "barCodeScannerEnabled" | "faceDetectorEnabled" | "useCamera2Api"> & React.RefAttributes<ExponentCameraRef>>;
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.web.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCamera.web.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,gBAAgB,CAAC;AAOxB,MAAM,WAAW,iBAAiB;IAChC,wBAAwB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,WAAW,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,QAAA,MAAM,cAAc;eAE0D,MAAM,SAAS;ygBA+G5F,CAAC;AAEF,eAAe,cAAc,CAAC"}

View File

@@ -0,0 +1,108 @@
import { CodedError } from 'expo-modules-core';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import createElement from 'react-native-web/dist/exports/createElement';
import { CameraType, } from './Camera.types';
import CameraManager from './ExpoCameraManager.web';
import { capture } from '../web/WebCameraUtils';
import { PictureSizes } from '../web/WebConstants';
import { useWebCameraStream } from '../web/useWebCameraStream';
import { useWebQRScanner } from '../web/useWebQRScanner';
const ExponentCamera = React.forwardRef(({ type, pictureSize, poster, ...props }, ref) => {
const video = React.useRef(null);
const native = useWebCameraStream(video, type, props, {
onCameraReady() {
if (props.onCameraReady) {
props.onCameraReady();
}
},
onMountError: props.onMountError,
});
const isQRScannerEnabled = React.useMemo(() => {
return !!(props.barCodeScannerSettings?.barCodeTypes?.includes('qr') && !!props.onBarCodeScanned);
}, [props.barCodeScannerSettings?.barCodeTypes, props.onBarCodeScanned]);
useWebQRScanner(video, {
interval: props.barCodeScannerSettings?.interval,
isEnabled: isQRScannerEnabled,
captureOptions: { scale: 1, isImageMirror: native.type === CameraType.front },
onScanned(event) {
if (props.onBarCodeScanned) {
props.onBarCodeScanned(event);
}
},
// onError: props.onMountError,
});
// const [pause, setPaused]
React.useImperativeHandle(ref, () => ({
async getAvailablePictureSizes(ratio) {
return PictureSizes;
},
async takePicture(options) {
if (!video.current || video.current?.readyState !== video.current?.HAVE_ENOUGH_DATA) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'HTMLVideoElement does not have enough camera data to construct an image yet.');
}
const settings = native.mediaTrackSettings;
if (!settings) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'MediaStream is not ready yet.');
}
return capture(video.current, settings, {
...options,
// This will always be defined, the option gets added to a queue in the upper-level. We should replace the original so it isn't called twice.
onPictureSaved(picture) {
if (options.onPictureSaved) {
options.onPictureSaved(picture);
}
if (props.onPictureSaved) {
props.onPictureSaved({ nativeEvent: { data: picture, id: -1 } });
}
},
});
},
async resumePreview() {
if (video.current) {
video.current.play();
}
},
async pausePreview() {
if (video.current) {
video.current.pause();
}
},
}), [native.mediaTrackSettings, props.onPictureSaved]);
// TODO(Bacon): Create a universal prop, on native the microphone is only used when recording videos.
// Because we don't support recording video in the browser we don't need the user to give microphone permissions.
const isMuted = true;
const style = React.useMemo(() => {
const isFrontFacingCamera = native.type === CameraManager.Type.front;
return [
StyleSheet.absoluteFill,
styles.video,
{
pointerEvents: props.pointerEvents,
// Flip the camera
transform: isFrontFacingCamera ? [{ scaleX: -1 }] : undefined,
},
];
}, [props.pointerEvents, native.type]);
return (<View style={[styles.videoWrapper, props.style]}>
<Video autoPlay playsInline muted={isMuted} poster={poster}
// webkitPlaysinline
ref={video} style={style}/>
{props.children}
</View>);
});
export default ExponentCamera;
const Video = React.forwardRef((props, ref) => createElement('video', { ...props, ref }));
const styles = StyleSheet.create({
videoWrapper: {
flex: 1,
alignItems: 'stretch',
pointerEvents: 'box-none',
},
video: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
});
//# sourceMappingURL=ExpoCamera.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
declare const CameraManager: Record<string, any>;
export default CameraManager;
//# sourceMappingURL=ExpoCameraManager.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCameraManager.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAA2C,CAAC;AAEnF,eAAe,aAAa,CAAC"}

View File

@@ -0,0 +1,4 @@
import { requireNativeModule } from 'expo-modules-core';
const CameraManager = requireNativeModule('ExpoCameraLegacy');
export default CameraManager;
//# sourceMappingURL=ExpoCameraManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.js","sourceRoot":"","sources":["../../src/legacy/ExpoCameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,aAAa,GAAwB,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;AAEnF,eAAe,aAAa,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nconst CameraManager: Record<string, any> = requireNativeModule('ExpoCameraLegacy');\n\nexport default CameraManager;\n"]}

View File

@@ -0,0 +1,41 @@
import { CameraCapturedPicture, CameraPictureOptions, PermissionResponse } from './Camera.types';
import { ExponentCameraRef } from './ExpoCamera.web';
declare const _default: {
readonly Type: {
back: string;
front: string;
};
readonly FlashMode: {
on: string;
off: string;
auto: string;
torch: string;
};
readonly AutoFocus: {
on: string;
off: string;
auto: string;
singleShot: string;
};
readonly WhiteBalance: {
auto: string;
continuous: string;
manual: string;
};
readonly VideoQuality: {};
readonly VideoStabilization: {};
isAvailableAsync(): Promise<boolean>;
takePicture(options: CameraPictureOptions, camera: ExponentCameraRef): Promise<CameraCapturedPicture>;
pausePreview(camera: ExponentCameraRef): Promise<void>;
resumePreview(camera: ExponentCameraRef): Promise<void>;
getAvailableCameraTypesAsync(): Promise<string[]>;
getAvailablePictureSizes(ratio: string, camera: ExponentCameraRef): Promise<string[]>;
getPermissionsAsync(): Promise<PermissionResponse>;
requestPermissionsAsync(): Promise<PermissionResponse>;
getCameraPermissionsAsync(): Promise<PermissionResponse>;
requestCameraPermissionsAsync(): Promise<PermissionResponse>;
getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
};
export default _default;
//# sourceMappingURL=ExpoCameraManager.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.web.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCameraManager.web.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EAEpB,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;wBA0JzB,QAAQ,OAAO,CAAC;yBAI/B,oBAAoB,UACrB,iBAAiB,GACxB,QAAQ,qBAAqB,CAAC;yBAGN,iBAAiB,GAAG,QAAQ,IAAI,CAAC;0BAGhC,iBAAiB,GAAG,QAAQ,IAAI,CAAC;oCAGvB,QAAQ,MAAM,EAAE,CAAC;oCAYjB,MAAM,UAAU,iBAAiB,GAAG,QAAQ,MAAM,EAAE,CAAC;2BAe9D,QAAQ,kBAAkB,CAAC;+BAGvB,QAAQ,kBAAkB,CAAC;iCAGzB,QAAQ,kBAAkB,CAAC;qCAGvB,QAAQ,kBAAkB,CAAC;qCAG3B,QAAQ,kBAAkB,CAAC;yCAGvB,QAAQ,kBAAkB,CAAC;;AA7FxE,wBA4GE"}

View File

@@ -0,0 +1,213 @@
import { UnavailabilityError } from 'expo-modules-core';
import { CameraType, PermissionStatus, } from './Camera.types';
import { canGetUserMedia, isBackCameraAvailableAsync, isFrontCameraAvailableAsync, } from '../web/WebUserMediaManager';
function getUserMedia(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
// TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
function () {
const error = new Error('Permission unimplemented');
error.code = 0;
error.name = 'NotAllowedError';
throw error;
};
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
function handleGetUserMediaError({ message }) {
// name: NotAllowedError
// code: 0
if (message === 'Permission dismissed') {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
else {
// TODO: Bacon: [OSX] The system could deny access to chrome.
// TODO: Bacon: add: { status: 'unimplemented' }
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
async function handleRequestPermissionsAsync() {
try {
await getUserMedia({
video: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
}
async function handlePermissionsQueryAsync(query) {
if (!navigator?.permissions?.query) {
throw new UnavailabilityError('expo-camera', 'navigator.permissions API is not available');
}
try {
const { state } = await navigator.permissions.query({ name: query });
switch (state) {
case 'prompt':
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
case 'granted':
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
case 'denied':
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
catch (e) {
// Firefox doesn't support querying for the camera permission, so return undetermined status
if (e instanceof TypeError) {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
throw e;
}
}
export default {
get Type() {
return {
back: 'back',
front: 'front',
};
},
get FlashMode() {
return {
on: 'on',
off: 'off',
auto: 'auto',
torch: 'torch',
};
},
get AutoFocus() {
return {
on: 'on',
off: 'off',
auto: 'auto',
singleShot: 'singleShot',
};
},
get WhiteBalance() {
return {
auto: 'auto',
continuous: 'continuous',
manual: 'manual',
};
},
get VideoQuality() {
return {};
},
get VideoStabilization() {
return {};
},
async isAvailableAsync() {
return canGetUserMedia();
},
async takePicture(options, camera) {
return await camera.takePicture(options);
},
async pausePreview(camera) {
await camera.pausePreview();
},
async resumePreview(camera) {
return await camera.resumePreview();
},
async getAvailableCameraTypesAsync() {
if (!canGetUserMedia() || !navigator.mediaDevices.enumerateDevices)
return [];
const devices = await navigator.mediaDevices.enumerateDevices();
const types = await Promise.all([
(await isFrontCameraAvailableAsync(devices)) && CameraType.front,
(await isBackCameraAvailableAsync()) && CameraType.back,
]);
return types.filter(Boolean);
},
async getAvailablePictureSizes(ratio, camera) {
return await camera.getAvailablePictureSizes(ratio);
},
/* async getSupportedRatios(camera: ExponentCameraRef): Promise<string[]> {
// TODO: Support on web
},
async record(
options?: CameraRecordingOptions,
camera: ExponentCameraRef
): Promise<{ uri: string }> {
// TODO: Support on web
},
async stopRecording(camera: ExponentCameraRef): Promise<void> {
// TODO: Support on web
}, */
async getPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getCameraPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestCameraPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getMicrophonePermissionsAsync() {
return handlePermissionsQueryAsync('microphone');
},
async requestMicrophonePermissionsAsync() {
try {
await getUserMedia({
audio: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
},
};
//# sourceMappingURL=ExpoCameraManager.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
export { default as Camera } from './Camera';
export { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } from './Camera';
export * from './Camera.types';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,6BAA6B,EAC7B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,UAAU,CAAC;AAElB,cAAc,gBAAgB,CAAC"}

View File

@@ -0,0 +1,4 @@
export { default as Camera } from './Camera';
export { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } from './Camera';
export * from './Camera.types';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,6BAA6B,EAC7B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,UAAU,CAAC;AAElB,cAAc,gBAAgB,CAAC","sourcesContent":["export { default as Camera } from './Camera';\nexport {\n Constants,\n getPermissionsAsync,\n requestPermissionsAsync,\n getCameraPermissionsAsync,\n requestCameraPermissionsAsync,\n getMicrophonePermissionsAsync,\n requestMicrophonePermissionsAsync,\n} from './Camera';\n\nexport * from './Camera.types';\n"]}

View File

@@ -0,0 +1,10 @@
import { CameraNativeProps, CameraType, FlashMode, AutoFocus, WhiteBalance, CameraProps } from '../Camera.types';
export declare const ConversionTables: {
type: Record<keyof typeof CameraType, CameraNativeProps['type']>;
flashMode: Record<keyof typeof FlashMode, CameraNativeProps['flashMode']>;
autoFocus: Record<keyof typeof AutoFocus, CameraNativeProps['autoFocus']>;
whiteBalance: Record<keyof typeof WhiteBalance, CameraNativeProps['whiteBalance']>;
};
export declare function convertNativeProps(props?: CameraProps): CameraNativeProps;
export declare function ensureNativeProps(props?: CameraProps): CameraNativeProps;
//# sourceMappingURL=props.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../../src/legacy/utils/props.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAIzB,eAAO,MAAM,gBAAgB,EAAE;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,OAAO,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IACjE,SAAS,EAAE,MAAM,CAAC,MAAM,OAAO,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,SAAS,EAAE,MAAM,CAAC,MAAM,OAAO,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,YAAY,EAAE,MAAM,CAAC,MAAM,OAAO,YAAY,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC,CAAC;CAMpF,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAgBzE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAqBxE"}

View File

@@ -0,0 +1,42 @@
import { Platform } from 'expo-modules-core';
import CameraManager from '../ExpoCameraManager';
// Values under keys from this object will be transformed to native options
export const ConversionTables = {
type: CameraManager.Type,
flashMode: CameraManager.FlashMode,
autoFocus: CameraManager.AutoFocus,
whiteBalance: CameraManager.WhiteBalance,
};
export function convertNativeProps(props) {
if (!props || typeof props !== 'object') {
return {};
}
const nativeProps = {};
for (const [key, value] of Object.entries(props)) {
if (typeof value === 'string' && ConversionTables[key]) {
nativeProps[key] = ConversionTables[key][value];
}
else {
nativeProps[key] = value;
}
}
return nativeProps;
}
export function ensureNativeProps(props) {
const newProps = convertNativeProps(props);
if (newProps.onBarCodeScanned) {
newProps.barCodeScannerEnabled = true;
}
if (newProps.onFacesDetected) {
newProps.faceDetectorEnabled = true;
}
if (Platform.OS !== 'android') {
delete newProps.ratio;
delete newProps.useCamera2Api;
}
if (Platform.OS !== 'web') {
delete newProps.poster;
}
return newProps;
}
//# sourceMappingURL=props.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"props.js","sourceRoot":"","sources":["../../../src/legacy/utils/props.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAU7C,OAAO,aAAa,MAAM,sBAAsB,CAAC;AAEjD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAKzB;IACF,IAAI,EAAE,aAAa,CAAC,IAAI;IACxB,SAAS,EAAE,aAAa,CAAC,SAAS;IAClC,SAAS,EAAE,aAAa,CAAC,SAAS;IAClC,YAAY,EAAE,aAAa,CAAC,YAAY;CACzC,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,KAAmB;IACpD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACvC,OAAO,EAAE,CAAC;KACX;IAED,MAAM,WAAW,GAAsB,EAAE,CAAC;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,gBAAgB,CAAC,GAAG,CAAC,EAAE;YACtD,WAAW,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;SACjD;aAAM;YACL,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;SAC1B;KACF;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE3C,IAAI,QAAQ,CAAC,gBAAgB,EAAE;QAC7B,QAAQ,CAAC,qBAAqB,GAAG,IAAI,CAAC;KACvC;IAED,IAAI,QAAQ,CAAC,eAAe,EAAE;QAC5B,QAAQ,CAAC,mBAAmB,GAAG,IAAI,CAAC;KACrC;IAED,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE;QAC7B,OAAO,QAAQ,CAAC,KAAK,CAAC;QACtB,OAAO,QAAQ,CAAC,aAAa,CAAC;KAC/B;IAED,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE;QACzB,OAAO,QAAQ,CAAC,MAAM,CAAC;KACxB;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { Platform } from 'expo-modules-core';\n\nimport {\n CameraNativeProps,\n CameraType,\n FlashMode,\n AutoFocus,\n WhiteBalance,\n CameraProps,\n} from '../Camera.types';\nimport CameraManager from '../ExpoCameraManager';\n\n// Values under keys from this object will be transformed to native options\nexport const ConversionTables: {\n type: Record<keyof typeof CameraType, CameraNativeProps['type']>;\n flashMode: Record<keyof typeof FlashMode, CameraNativeProps['flashMode']>;\n autoFocus: Record<keyof typeof AutoFocus, CameraNativeProps['autoFocus']>;\n whiteBalance: Record<keyof typeof WhiteBalance, CameraNativeProps['whiteBalance']>;\n} = {\n type: CameraManager.Type,\n flashMode: CameraManager.FlashMode,\n autoFocus: CameraManager.AutoFocus,\n whiteBalance: CameraManager.WhiteBalance,\n};\n\nexport function convertNativeProps(props?: CameraProps): CameraNativeProps {\n if (!props || typeof props !== 'object') {\n return {};\n }\n\n const nativeProps: CameraNativeProps = {};\n\n for (const [key, value] of Object.entries(props)) {\n if (typeof value === 'string' && ConversionTables[key]) {\n nativeProps[key] = ConversionTables[key][value];\n } else {\n nativeProps[key] = value;\n }\n }\n\n return nativeProps;\n}\n\nexport function ensureNativeProps(props?: CameraProps): CameraNativeProps {\n const newProps = convertNativeProps(props);\n\n if (newProps.onBarCodeScanned) {\n newProps.barCodeScannerEnabled = true;\n }\n\n if (newProps.onFacesDetected) {\n newProps.faceDetectorEnabled = true;\n }\n\n if (Platform.OS !== 'android') {\n delete newProps.ratio;\n delete newProps.useCamera2Api;\n }\n\n if (Platform.OS !== 'web') {\n delete newProps.poster;\n }\n\n return newProps;\n}\n"]}

View File

@@ -0,0 +1,8 @@
import { CameraNativeProps, CameraType, FlashMode, CameraProps } from '../Camera.types';
export declare const ConversionTables: {
type: Record<keyof CameraType, CameraNativeProps['facing']>;
flash: Record<keyof FlashMode, CameraNativeProps['flashMode']>;
};
export declare function convertNativeProps(props?: CameraProps): CameraNativeProps;
export declare function ensureNativeProps(props?: CameraProps): CameraNativeProps;
//# sourceMappingURL=props.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../src/utils/props.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAIxF,eAAO,MAAM,gBAAgB,EAAE;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,UAAU,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5D,KAAK,EAAE,MAAM,CAAC,MAAM,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;CAIhE,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAgBzE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAaxE"}

View File

@@ -0,0 +1,34 @@
import { Platform } from 'expo-modules-core';
import CameraManager from '../ExpoCameraManager';
// Values under keys from this object will be transformed to native options
export const ConversionTables = {
type: CameraManager.Type,
flash: CameraManager.FlashMode,
};
export function convertNativeProps(props) {
if (!props || typeof props !== 'object') {
return {};
}
const nativeProps = {};
for (const [key, value] of Object.entries(props)) {
if (typeof value === 'string' && ConversionTables[key]) {
nativeProps[key] = ConversionTables[key][value];
}
else {
nativeProps[key] = value;
}
}
return nativeProps;
}
export function ensureNativeProps(props) {
const newProps = convertNativeProps(props);
newProps.barcodeScannerEnabled = !!props?.onBarcodeScanned;
newProps.flashMode = props?.flash ?? 'off';
newProps.mute = props?.mute ?? false;
newProps.autoFocus = props?.autofocus ?? 'off';
if (Platform.OS !== 'web') {
delete newProps.poster;
}
return newProps;
}
//# sourceMappingURL=props.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"props.js","sourceRoot":"","sources":["../../src/utils/props.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,OAAO,aAAa,MAAM,sBAAsB,CAAC;AAEjD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAGzB;IACF,IAAI,EAAE,aAAa,CAAC,IAAI;IACxB,KAAK,EAAE,aAAa,CAAC,SAAS;CAC/B,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,KAAmB;IACpD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACvC,OAAO,EAAE,CAAC;KACX;IAED,MAAM,WAAW,GAAsB,EAAE,CAAC;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,gBAAgB,CAAC,GAAG,CAAC,EAAE;YACtD,WAAW,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;SACjD;aAAM;YACL,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;SAC1B;KACF;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE3C,QAAQ,CAAC,qBAAqB,GAAG,CAAC,CAAC,KAAK,EAAE,gBAAgB,CAAC;IAC3D,QAAQ,CAAC,SAAS,GAAG,KAAK,EAAE,KAAK,IAAI,KAAK,CAAC;IAC3C,QAAQ,CAAC,IAAI,GAAG,KAAK,EAAE,IAAI,IAAI,KAAK,CAAC;IACrC,QAAQ,CAAC,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC;IAE/C,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE;QACzB,OAAO,QAAQ,CAAC,MAAM,CAAC;KACxB;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { Platform } from 'expo-modules-core';\n\nimport { CameraNativeProps, CameraType, FlashMode, CameraProps } from '../Camera.types';\nimport CameraManager from '../ExpoCameraManager';\n\n// Values under keys from this object will be transformed to native options\nexport const ConversionTables: {\n type: Record<keyof CameraType, CameraNativeProps['facing']>;\n flash: Record<keyof FlashMode, CameraNativeProps['flashMode']>;\n} = {\n type: CameraManager.Type,\n flash: CameraManager.FlashMode,\n};\n\nexport function convertNativeProps(props?: CameraProps): CameraNativeProps {\n if (!props || typeof props !== 'object') {\n return {};\n }\n\n const nativeProps: CameraNativeProps = {};\n\n for (const [key, value] of Object.entries(props)) {\n if (typeof value === 'string' && ConversionTables[key]) {\n nativeProps[key] = ConversionTables[key][value];\n } else {\n nativeProps[key] = value;\n }\n }\n\n return nativeProps;\n}\n\nexport function ensureNativeProps(props?: CameraProps): CameraNativeProps {\n const newProps = convertNativeProps(props);\n\n newProps.barcodeScannerEnabled = !!props?.onBarcodeScanned;\n newProps.flashMode = props?.flash ?? 'off';\n newProps.mute = props?.mute ?? false;\n newProps.autoFocus = props?.autofocus ?? 'off';\n\n if (Platform.OS !== 'web') {\n delete newProps.poster;\n }\n\n return newProps;\n}\n"]}

View File

@@ -0,0 +1,32 @@
import { CameraType, CameraCapturedPicture, ImageSize, ImageType, WebCameraSettings, CameraPictureOptions } from '../legacy/Camera.types';
interface ConstrainLongRange {
max?: number;
min?: number;
exact?: number;
ideal?: number;
}
export declare function getImageSize(videoWidth: number, videoHeight: number, scale: number): ImageSize;
export declare function toDataURL(canvas: HTMLCanvasElement, imageType: ImageType, quality: number): string;
export declare function hasValidConstraints(preferredCameraType?: CameraType, width?: number | ConstrainLongRange, height?: number | ConstrainLongRange): boolean;
export declare function captureImageData(video: HTMLVideoElement | null, pictureOptions?: Pick<CameraPictureOptions, 'scale' | 'isImageMirror'>): ImageData | null;
export declare function captureImageContext(video: HTMLVideoElement, { scale, isImageMirror }: Pick<CameraPictureOptions, 'scale' | 'isImageMirror'>): HTMLCanvasElement;
export declare function captureImage(video: HTMLVideoElement, pictureOptions: CameraPictureOptions): string;
export declare function getIdealConstraints(preferredCameraType: CameraType, width?: number | ConstrainLongRange, height?: number | ConstrainLongRange): MediaStreamConstraints;
/**
* Invoke getStreamDevice a second time with the opposing camera type if the preferred type cannot be retrieved.
*
* @param preferredCameraType
* @param preferredWidth
* @param preferredHeight
*/
export declare function getPreferredStreamDevice(preferredCameraType: CameraType, preferredWidth?: number | ConstrainLongRange, preferredHeight?: number | ConstrainLongRange): Promise<MediaStream>;
export declare function getStreamDevice(preferredCameraType: CameraType, preferredWidth?: number | ConstrainLongRange, preferredHeight?: number | ConstrainLongRange): Promise<MediaStream>;
export declare function isWebKit(): boolean;
export declare function compareStreams(a: MediaStream | null, b: MediaStream | null): boolean;
export declare function capture(video: HTMLVideoElement, settings: MediaTrackSettings, config: CameraPictureOptions): CameraCapturedPicture;
export declare function syncTrackCapabilities(cameraType: CameraType, stream: MediaStream | null, settings?: WebCameraSettings): Promise<void>;
export declare function stopMediaStream(stream: MediaStream | null): void;
export declare function setVideoSource(video: HTMLVideoElement, stream: MediaStream | MediaSource | Blob | null): void;
export declare function isCapabilityAvailable(video: HTMLVideoElement, keyName: string): boolean;
export {};
//# sourceMappingURL=WebCameraUtils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebCameraUtils.d.ts","sourceRoot":"","sources":["../../src/web/WebCameraUtils.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAEhC,UAAU,kBAAkB;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAS9F;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,MAAM,GACd,MAAM,CAkBR;AAED,wBAAgB,mBAAmB,CACjC,mBAAmB,CAAC,EAAE,UAAU,EAChC,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,EACnC,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,GACnC,OAAO,CAET;AAmBD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,gBAAgB,GAAG,IAAI,EAC9B,cAAc,GAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,eAAe,CAAM,GACzE,SAAS,GAAG,IAAI,CAalB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EACvB,EAAE,KAAS,EAAE,aAAqB,EAAE,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,eAAe,CAAC,GAC1F,iBAAiB,CAyBnB;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,gBAAgB,EACvB,cAAc,EAAE,oBAAoB,GACnC,MAAM,CAKR;AASD,wBAAgB,mBAAmB,CACjC,mBAAmB,EAAE,UAAU,EAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,EACnC,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,GACnC,sBAAsB,CAoCxB;AAMD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,mBAAmB,EAAE,UAAU,EAC/B,cAAc,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAC5C,eAAe,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAC5C,OAAO,CAAC,WAAW,CAAC,CAatB;AAED,wBAAsB,eAAe,CACnC,mBAAmB,EAAE,UAAU,EAC/B,cAAc,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAC5C,eAAe,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAC5C,OAAO,CAAC,WAAW,CAAC,CAQtB;AAED,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,EAAE,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAOpF;AAED,wBAAgB,OAAO,CACrB,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,oBAAoB,GAC3B,qBAAqB,CAqBvB;AAED,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,WAAW,GAAG,IAAI,EAC1B,QAAQ,GAAE,iBAAsB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAMf;AAiFD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,QAazD;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,GAAG,IAAI,GAC9C,IAAI,CAkBN;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CASvF"}

View File

@@ -0,0 +1,309 @@
/* eslint-env browser */
import invariant from 'invariant';
import * as CapabilityUtils from './WebCapabilityUtils';
import { CameraTypeToFacingMode, ImageTypeFormat, MinimumConstraints } from './WebConstants';
import { requestUserMediaAsync } from './WebUserMediaManager';
import { CameraType, ImageType, } from '../legacy/Camera.types';
export function getImageSize(videoWidth, videoHeight, scale) {
const width = videoWidth * scale;
const ratio = videoWidth / width;
const height = videoHeight / ratio;
return {
width,
height,
};
}
export function toDataURL(canvas, imageType, quality) {
invariant(Object.values(ImageType).includes(imageType), `expo-camera: ${imageType} is not a valid ImageType. Expected a string from: ${Object.values(ImageType).join(', ')}`);
const format = ImageTypeFormat[imageType];
if (imageType === ImageType.jpg) {
invariant(quality <= 1 && quality >= 0, `expo-camera: ${quality} is not a valid image quality. Expected a number from 0...1`);
return canvas.toDataURL(format, quality);
}
else {
return canvas.toDataURL(format);
}
}
export function hasValidConstraints(preferredCameraType, width, height) {
return preferredCameraType !== undefined && width !== undefined && height !== undefined;
}
function ensureCameraPictureOptions(config) {
const captureOptions = {
scale: 1,
imageType: ImageType.png,
isImageMirror: false,
};
for (const key in config) {
if (key in config && config[key] !== undefined && key in captureOptions) {
captureOptions[key] = config[key];
}
}
return captureOptions;
}
const DEFAULT_QUALITY = 0.92;
export function captureImageData(video, pictureOptions = {}) {
if (!video || video.readyState !== video.HAVE_ENOUGH_DATA) {
return null;
}
const canvas = captureImageContext(video, pictureOptions);
const context = canvas.getContext('2d', { alpha: false });
if (!context || !canvas.width || !canvas.height) {
return null;
}
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
return imageData;
}
export function captureImageContext(video, { scale = 1, isImageMirror = false }) {
const { videoWidth, videoHeight } = video;
const { width, height } = getImageSize(videoWidth, videoHeight, scale);
// Build the canvas size and draw the camera image to the context from video
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d', { alpha: false });
if (!context) {
// Should never be called
throw new Error('Context is not defined');
}
// sharp image details
// context.imageSmoothingEnabled = false;
// Flip horizontally (as css transform: rotateY(180deg))
if (isImageMirror) {
context.setTransform(-1, 0, 0, 1, canvas.width, 0);
}
context.drawImage(video, 0, 0, width, height);
return canvas;
}
export function captureImage(video, pictureOptions) {
const config = ensureCameraPictureOptions(pictureOptions);
const canvas = captureImageContext(video, config);
const { imageType, quality = DEFAULT_QUALITY } = config;
return toDataURL(canvas, imageType, quality);
}
function getSupportedConstraints() {
if (navigator.mediaDevices && navigator.mediaDevices.getSupportedConstraints) {
return navigator.mediaDevices.getSupportedConstraints();
}
return null;
}
export function getIdealConstraints(preferredCameraType, width, height) {
const preferredConstraints = {
audio: false,
video: {},
};
if (hasValidConstraints(preferredCameraType, width, height)) {
return MinimumConstraints;
}
const supports = getSupportedConstraints();
// TODO(Bacon): Test this
if (!supports || !supports.facingMode || !supports.width || !supports.height) {
return MinimumConstraints;
}
if (preferredCameraType && Object.values(CameraType).includes(preferredCameraType)) {
const facingMode = CameraTypeToFacingMode[preferredCameraType];
if (isWebKit()) {
const key = facingMode === 'user' ? 'exact' : 'ideal';
preferredConstraints.video.facingMode = {
[key]: facingMode,
};
}
else {
preferredConstraints.video.facingMode = {
ideal: CameraTypeToFacingMode[preferredCameraType],
};
}
}
if (isMediaTrackConstraints(preferredConstraints.video)) {
preferredConstraints.video.width = width;
preferredConstraints.video.height = height;
}
return preferredConstraints;
}
function isMediaTrackConstraints(input) {
return input && typeof input.video !== 'boolean';
}
/**
* Invoke getStreamDevice a second time with the opposing camera type if the preferred type cannot be retrieved.
*
* @param preferredCameraType
* @param preferredWidth
* @param preferredHeight
*/
export async function getPreferredStreamDevice(preferredCameraType, preferredWidth, preferredHeight) {
try {
return await getStreamDevice(preferredCameraType, preferredWidth, preferredHeight);
}
catch (error) {
// A hack on desktop browsers to ensure any camera is used.
// eslint-disable-next-line no-undef
if (error instanceof OverconstrainedError && error.constraint === 'facingMode') {
const nextCameraType = preferredCameraType === CameraType.back ? CameraType.front : CameraType.back;
return await getStreamDevice(nextCameraType, preferredWidth, preferredHeight);
}
throw error;
}
}
export async function getStreamDevice(preferredCameraType, preferredWidth, preferredHeight) {
const constraints = getIdealConstraints(preferredCameraType, preferredWidth, preferredHeight);
const stream = await requestUserMediaAsync(constraints);
return stream;
}
export function isWebKit() {
return /WebKit/.test(navigator.userAgent) && !/Edg/.test(navigator.userAgent);
}
export function compareStreams(a, b) {
if (!a || !b) {
return false;
}
const settingsA = a.getTracks()[0].getSettings();
const settingsB = b.getTracks()[0].getSettings();
return settingsA.deviceId === settingsB.deviceId;
}
export function capture(video, settings, config) {
const base64 = captureImage(video, config);
const capturedPicture = {
uri: base64,
base64,
width: 0,
height: 0,
};
if (settings) {
const { width = 0, height = 0 } = settings;
capturedPicture.width = width;
capturedPicture.height = height;
capturedPicture.exif = settings;
}
if (config.onPictureSaved) {
config.onPictureSaved(capturedPicture);
}
return capturedPicture;
}
export async function syncTrackCapabilities(cameraType, stream, settings = {}) {
if (stream?.getVideoTracks) {
await Promise.all(stream.getVideoTracks().map((track) => onCapabilitiesReady(cameraType, track, settings)));
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
async function onCapabilitiesReady(cameraType, track, settings = {}) {
if (typeof track.getCapabilities !== 'function') {
return;
}
const capabilities = track.getCapabilities();
// Create an empty object because if you set a constraint that isn't available an error will be thrown.
const constraints = {};
// TODO(Bacon): Add `pointsOfInterest` support
const clampedValues = [
'exposureCompensation',
'colorTemperature',
'iso',
'brightness',
'contrast',
'saturation',
'sharpness',
'focusDistance',
'zoom',
];
for (const property of clampedValues) {
if (capabilities[property]) {
constraints[property] = convertNormalizedSetting(capabilities[property], settings[property]);
}
}
function validatedInternalConstrainedValue(constraintKey, settingsKey, converter) {
const convertedSetting = converter(settings[settingsKey]);
return validatedConstrainedValue({
constraintKey,
settingsKey,
convertedSetting,
capabilities,
settings,
cameraType,
});
}
if (capabilities.focusMode && settings.autoFocus !== undefined) {
constraints.focusMode = validatedInternalConstrainedValue('focusMode', 'autoFocus', CapabilityUtils.convertAutoFocusJSONToNative);
}
if (capabilities.torch && settings.flashMode !== undefined) {
constraints.torch = validatedInternalConstrainedValue('torch', 'flashMode', CapabilityUtils.convertFlashModeJSONToNative);
}
if (capabilities.whiteBalanceMode && settings.whiteBalance !== undefined) {
constraints.whiteBalanceMode = validatedInternalConstrainedValue('whiteBalanceMode', 'whiteBalance', CapabilityUtils.convertWhiteBalanceJSONToNative);
}
try {
await track.applyConstraints({ advanced: [constraints] });
}
catch (error) {
if (__DEV__)
console.warn('Failed to apply constraints', error);
}
}
export function stopMediaStream(stream) {
if (!stream) {
return;
}
if (stream.getAudioTracks) {
stream.getAudioTracks().forEach((track) => track.stop());
}
if (stream.getVideoTracks) {
stream.getVideoTracks().forEach((track) => track.stop());
}
if (isMediaStreamTrack(stream)) {
stream.stop();
}
}
export function setVideoSource(video, stream) {
const createObjectURL = window.URL.createObjectURL ?? window.webkitURL.createObjectURL;
if (typeof video.srcObject !== 'undefined') {
video.srcObject = stream;
}
else if (typeof video.mozSrcObject !== 'undefined') {
video.mozSrcObject = stream;
}
else if (stream && createObjectURL) {
video.src = createObjectURL(stream);
}
if (!stream) {
const revokeObjectURL = window.URL.revokeObjectURL ?? window.webkitURL.revokeObjectURL;
const source = video.src ?? video.srcObject ?? video.mozSrcObject;
if (revokeObjectURL && typeof source === 'string') {
revokeObjectURL(source);
}
}
}
export function isCapabilityAvailable(video, keyName) {
const stream = video.srcObject;
if (stream instanceof MediaStream) {
const videoTrack = stream.getVideoTracks()[0];
return videoTrack.getCapabilities?.()?.[keyName];
}
return false;
}
function isMediaStreamTrack(input) {
return typeof input.stop === 'function';
}
function convertNormalizedSetting(range, value) {
if (!value) {
return;
}
// convert the normalized incoming setting to the native camera zoom range
const converted = convertRange(value, [range.min, range.max]);
// clamp value so we don't get an error
return Math.min(range.max, Math.max(range.min, converted));
}
function convertRange(value, r2, r1 = [0, 1]) {
return ((value - r1[0]) * (r2[1] - r2[0])) / (r1[1] - r1[0]) + r2[0];
}
function validatedConstrainedValue(props) {
const { constraintKey, settingsKey, convertedSetting, capabilities, settings, cameraType } = props;
const setting = settings[settingsKey];
if (Array.isArray(capabilities[constraintKey]) &&
convertedSetting &&
!capabilities[constraintKey].includes(convertedSetting)) {
if (__DEV__) {
// Only warn in dev mode.
console.warn(` { ${settingsKey}: "${setting}" } (converted to "${convertedSetting}" in the browser) is not supported for camera type "${cameraType}" in your browser. Using the default value instead.`);
}
return undefined;
}
return convertedSetting;
}
//# sourceMappingURL=WebCameraUtils.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
export declare function convertFlashModeJSONToNative(input: string): boolean;
export declare function convertWhiteBalanceJSONToNative(input: string): MeteringMode | undefined;
export declare function convertAutoFocusJSONToNative(input: string): MeteringMode | undefined;
//# sourceMappingURL=WebCapabilityUtils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebCapabilityUtils.d.ts","sourceRoot":"","sources":["../../src/web/WebCapabilityUtils.ts"],"names":[],"mappings":"AAGA,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUnE;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAYvF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAYpF"}

View File

@@ -0,0 +1,41 @@
/*
* Native web camera (Android) has a torch: boolean
*/
export function convertFlashModeJSONToNative(input) {
switch (input) {
case 'torch':
return true;
case 'on':
case 'off':
case 'auto':
default:
return false;
}
}
export function convertWhiteBalanceJSONToNative(input) {
switch (input) {
case 'on':
case 'auto':
return 'continuous';
case 'off':
return 'none';
case 'singleShot':
return 'single-shot';
default:
return undefined;
}
}
export function convertAutoFocusJSONToNative(input) {
switch (input) {
case 'on':
case 'auto':
return 'continuous';
case 'off':
return 'manual';
case 'singleShot':
return 'single-shot';
default:
return undefined;
}
}
//# sourceMappingURL=WebCapabilityUtils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebCapabilityUtils.js","sourceRoot":"","sources":["../../src/web/WebCapabilityUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAAa;IACxD,QAAQ,KAAK,EAAE;QACb,KAAK,OAAO;YACV,OAAO,IAAI,CAAC;QACd,KAAK,IAAI,CAAC;QACV,KAAK,KAAK,CAAC;QACX,KAAK,MAAM,CAAC;QACZ;YACE,OAAO,KAAK,CAAC;KAChB;AACH,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,KAAa;IAC3D,QAAQ,KAAK,EAAE;QACb,KAAK,IAAI,CAAC;QACV,KAAK,MAAM;YACT,OAAO,YAAY,CAAC;QACtB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC;QAChB,KAAK,YAAY;YACf,OAAO,aAAa,CAAC;QACvB;YACE,OAAO,SAAS,CAAC;KACpB;AACH,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,KAAa;IACxD,QAAQ,KAAK,EAAE;QACb,KAAK,IAAI,CAAC;QACV,KAAK,MAAM;YACT,OAAO,YAAY,CAAC;QACtB,KAAK,KAAK;YACR,OAAO,QAAQ,CAAC;QAClB,KAAK,YAAY;YACf,OAAO,aAAa,CAAC;QACvB;YACE,OAAO,SAAS,CAAC;KACpB;AACH,CAAC","sourcesContent":["/*\n * Native web camera (Android) has a torch: boolean\n */\nexport function convertFlashModeJSONToNative(input: string): boolean {\n switch (input) {\n case 'torch':\n return true;\n case 'on':\n case 'off':\n case 'auto':\n default:\n return false;\n }\n}\n\nexport function convertWhiteBalanceJSONToNative(input: string): MeteringMode | undefined {\n switch (input) {\n case 'on':\n case 'auto':\n return 'continuous';\n case 'off':\n return 'none';\n case 'singleShot':\n return 'single-shot';\n default:\n return undefined;\n }\n}\n\nexport function convertAutoFocusJSONToNative(input: string): MeteringMode | undefined {\n switch (input) {\n case 'on':\n case 'auto':\n return 'continuous';\n case 'off':\n return 'manual';\n case 'singleShot':\n return 'single-shot';\n default:\n return undefined;\n }\n}\n"]}

View File

@@ -0,0 +1,23 @@
import { CameraType } from '../legacy/Camera.types';
export declare const VIDEO_ASPECT_RATIOS: {
'3840x2160': number;
'1920x1080': number;
'1280x720': number;
'640x480': number;
'352x288': number;
};
export declare const PictureSizes: string[];
export declare const ImageTypeFormat: {
jpg: string;
png: string;
};
export declare const MinimumConstraints: MediaStreamConstraints;
export declare const CameraTypeToFacingMode: {
front: string;
back: string;
};
export declare const FacingModeToCameraType: {
user: CameraType;
environment: CameraType;
};
//# sourceMappingURL=WebConstants.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebConstants.d.ts","sourceRoot":"","sources":["../../src/web/WebConstants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,wBAAwB,CAAC;AAG/D,eAAO,MAAM,mBAAmB;;;;;;CAM/B,CAAC;AAEF,eAAO,MAAM,YAAY,UAAmC,CAAC;AAE7D,eAAO,MAAM,eAAe;;;CAG3B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,sBAGhC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;CAGlC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;CAGlC,CAAC"}

View File

@@ -0,0 +1,27 @@
import { CameraType, ImageType } from '../legacy/Camera.types';
// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio
export const VIDEO_ASPECT_RATIOS = {
'3840x2160': 3840 / 2160,
'1920x1080': 1920 / 1080,
'1280x720': 1280 / 720,
'640x480': 640 / 480,
'352x288': 352 / 288,
};
export const PictureSizes = Object.keys(VIDEO_ASPECT_RATIOS);
export const ImageTypeFormat = {
[ImageType.jpg]: 'image/jpeg',
[ImageType.png]: 'image/png',
};
export const MinimumConstraints = {
audio: false,
video: true,
};
export const CameraTypeToFacingMode = {
[CameraType.front]: 'user',
[CameraType.back]: 'environment',
};
export const FacingModeToCameraType = {
user: CameraType.front,
environment: CameraType.back,
};
//# sourceMappingURL=WebConstants.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebConstants.js","sourceRoot":"","sources":["../../src/web/WebConstants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,WAAW,EAAE,IAAI,GAAG,IAAI;IACxB,WAAW,EAAE,IAAI,GAAG,IAAI;IACxB,UAAU,EAAE,IAAI,GAAG,GAAG;IACtB,SAAS,EAAE,GAAG,GAAG,GAAG;IACpB,SAAS,EAAE,GAAG,GAAG,GAAG;CACrB,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AAE7D,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,YAAY;IAC7B,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,WAAW;CAC7B,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM;IAC1B,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,aAAa;CACjC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE,UAAU,CAAC,KAAK;IACtB,WAAW,EAAE,UAAU,CAAC,IAAI;CAC7B,CAAC","sourcesContent":["import { CameraType, ImageType } from '../legacy/Camera.types';\n\n// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio\nexport const VIDEO_ASPECT_RATIOS = {\n '3840x2160': 3840 / 2160,\n '1920x1080': 1920 / 1080,\n '1280x720': 1280 / 720,\n '640x480': 640 / 480,\n '352x288': 352 / 288,\n};\n\nexport const PictureSizes = Object.keys(VIDEO_ASPECT_RATIOS);\n\nexport const ImageTypeFormat = {\n [ImageType.jpg]: 'image/jpeg',\n [ImageType.png]: 'image/png',\n};\n\nexport const MinimumConstraints: MediaStreamConstraints = {\n audio: false,\n video: true,\n};\n\nexport const CameraTypeToFacingMode = {\n [CameraType.front]: 'user',\n [CameraType.back]: 'environment',\n};\n\nexport const FacingModeToCameraType = {\n user: CameraType.front,\n environment: CameraType.back,\n};\n"]}

View File

@@ -0,0 +1,12 @@
export declare const userMediaRequested: boolean;
export declare const mountedInstances: any[];
export declare function requestUserMediaAsync(props: {
audio?: any;
video?: any;
}, isMuted?: boolean): Promise<MediaStream>;
export declare function getAnyUserMediaAsync(constraints: MediaStreamConstraints, ignoreConstraints?: boolean): Promise<MediaStream>;
export declare function getUserMediaAsync(constraints: MediaStreamConstraints): Promise<MediaStream>;
export declare function canGetUserMedia(): boolean;
export declare function isFrontCameraAvailableAsync(devices?: MediaDeviceInfo[]): Promise<null | string>;
export declare function isBackCameraAvailableAsync(devices?: MediaDeviceInfo[]): Promise<null | string>;
//# sourceMappingURL=WebUserMediaManager.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebUserMediaManager.d.ts","sourceRoot":"","sources":["../../src/web/WebUserMediaManager.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,kBAAkB,EAAE,OAAe,CAAC;AAEjD,eAAO,MAAM,gBAAgB,EAAE,GAAG,EAAO,CAAC;AAoE1C,wBAAsB,qBAAqB,CACzC,KAAK,EAAE;IAAE,KAAK,CAAC,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAA;CAAE,EACnC,OAAO,GAAE,OAAc,GACtB,OAAO,CAAC,WAAW,CAAC,CAMtB;AAED,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,sBAAsB,EACnC,iBAAiB,GAAE,OAAe,GACjC,OAAO,CAAC,WAAW,CAAC,CAYtB;AAED,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAUjG;AAED,wBAAgB,eAAe,IAAI,OAAO,CAYzC;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,CAAC,EAAE,eAAe,EAAE,GAC1B,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAExB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,CAAC,EAAE,eAAe,EAAE,GAC1B,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAExB"}

View File

@@ -0,0 +1,120 @@
/* eslint-env browser */
/**
* A web-only module for ponyfilling the UserMedia API.
*/
import { Platform } from 'expo-modules-core';
export const userMediaRequested = false;
export const mountedInstances = [];
async function requestLegacyUserMediaAsync(props) {
const optionalSource = (id) => ({ optional: [{ sourceId: id }] });
const constraintToSourceId = (constraint) => {
const { deviceId } = constraint;
if (typeof deviceId === 'string') {
return deviceId;
}
if (Array.isArray(deviceId) && deviceId.length > 0) {
return deviceId[0];
}
if (typeof deviceId === 'object' && deviceId.ideal) {
return deviceId.ideal;
}
return null;
};
const sources = await new Promise((resolve) =>
// @ts-ignore: https://caniuse.com/#search=getSources Chrome for Android (78) & Samsung Internet (10.1) use this
MediaStreamTrack.getSources((sources) => resolve(sources)));
let audioSource = null;
let videoSource = null;
sources.forEach((source) => {
if (source.kind === 'audio') {
audioSource = source.id;
}
else if (source.kind === 'video') {
videoSource = source.id;
}
});
const audioSourceId = constraintToSourceId(props.audioConstraints);
if (audioSourceId) {
audioSource = audioSourceId;
}
const videoSourceId = constraintToSourceId(props.videoConstraints);
if (videoSourceId) {
videoSource = videoSourceId;
}
return [optionalSource(audioSource), optionalSource(videoSource)];
}
async function sourceSelectedAsync(isMuted, audioConstraints, videoConstraints) {
const constraints = {
video: typeof videoConstraints !== 'undefined' ? videoConstraints : true,
};
if (!isMuted) {
constraints.audio = typeof audioConstraints !== 'undefined' ? audioConstraints : true;
}
return await getAnyUserMediaAsync(constraints);
}
export async function requestUserMediaAsync(props, isMuted = true) {
if (canGetUserMedia()) {
return await sourceSelectedAsync(isMuted, props.audio, props.video);
}
const [audio, video] = await requestLegacyUserMediaAsync(props);
return await sourceSelectedAsync(isMuted, audio, video);
}
export async function getAnyUserMediaAsync(constraints, ignoreConstraints = false) {
try {
return await getUserMediaAsync({
...constraints,
video: ignoreConstraints || constraints.video,
});
}
catch (error) {
if (!ignoreConstraints && error.name === 'ConstraintNotSatisfiedError') {
return await getAnyUserMediaAsync(constraints, true);
}
throw error;
}
}
export async function getUserMediaAsync(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
const _getUserMedia = navigator['mozGetUserMedia'] || navigator['webkitGetUserMedia'] || navigator['msGetUserMedia'];
return new Promise((resolve, reject) => _getUserMedia.call(navigator, constraints, resolve, reject));
}
export function canGetUserMedia() {
return (
// SSR
Platform.isDOMAvailable &&
// Has any form of media API
!!((navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ||
navigator['mozGetUserMedia'] ||
navigator['webkitGetUserMedia'] ||
navigator['msGetUserMedia']));
}
export async function isFrontCameraAvailableAsync(devices) {
return await supportsCameraType(['front', 'user', 'facetime'], 'user', devices);
}
export async function isBackCameraAvailableAsync(devices) {
return await supportsCameraType(['back', 'rear'], 'environment', devices);
}
async function supportsCameraType(labels, type, devices) {
if (!devices) {
if (!navigator.mediaDevices.enumerateDevices) {
return null;
}
devices = await navigator.mediaDevices.enumerateDevices();
}
const cameras = devices.filter((t) => t.kind === 'videoinput');
const [hasCamera] = cameras.filter((camera) => labels.some((label) => camera.label.toLowerCase().includes(label)));
const [isCapable] = cameras.filter((camera) => {
if (!('getCapabilities' in camera)) {
return null;
}
const capabilities = camera.getCapabilities();
if (!capabilities.facingMode) {
return null;
}
return capabilities.facingMode.find((_) => type);
});
return isCapable?.deviceId || hasCamera?.deviceId || null;
}
//# sourceMappingURL=WebUserMediaManager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
import * as React from 'react';
import { CameraReadyListener, CameraType, MountErrorListener } from '../legacy/Camera.types';
export declare function useWebCameraStream(video: React.MutableRefObject<HTMLVideoElement | null>, preferredType: CameraType, settings: Record<string, any>, { onCameraReady, onMountError, }: {
onCameraReady?: CameraReadyListener;
onMountError?: MountErrorListener;
}): {
type: CameraType | null;
mediaTrackSettings: MediaTrackSettings | null;
};
//# sourceMappingURL=useWebCameraStream.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useWebCameraStream.d.ts","sourceRoot":"","sources":["../../src/web/useWebCameraStream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,kBAAkB,EAEnB,MAAM,wBAAwB,CAAC;AAgChC,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,EACtD,aAAa,EAAE,UAAU,EACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,EACE,aAAa,EACb,YAAY,GACb,EAAE;IAAE,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAAC,YAAY,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAC5E;IACD,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAC/C,CAoJA"}

View File

@@ -0,0 +1,166 @@
/* eslint-env browser */
import * as React from 'react';
import * as Utils from './WebCameraUtils';
import { FacingModeToCameraType } from './WebConstants';
const VALID_SETTINGS_KEYS = [
'autoFocus',
'flashMode',
'exposureCompensation',
'colorTemperature',
'iso',
'brightness',
'contrast',
'saturation',
'sharpness',
'focusDistance',
'whiteBalance',
'zoom',
];
function useLoadedVideo(video, onLoaded) {
React.useEffect(() => {
if (video) {
video.addEventListener('loadedmetadata', () => {
// without this async block the constraints aren't properly applied to the camera,
// this means that if you were to turn on the torch and swap to the front camera,
// then swap back to the rear camera the torch setting wouldn't be applied.
requestAnimationFrame(() => {
onLoaded();
});
});
}
}, [video]);
}
export function useWebCameraStream(video, preferredType, settings, { onCameraReady, onMountError, }) {
const isStartingCamera = React.useRef(false);
const activeStreams = React.useRef([]);
const capabilities = React.useRef({
autoFocus: 'continuous',
flashMode: 'off',
whiteBalance: 'continuous',
zoom: 1,
});
const [stream, setStream] = React.useState(null);
const mediaTrackSettings = React.useMemo(() => {
return stream ? stream.getTracks()[0].getSettings() : null;
}, [stream]);
// The actual camera type - this can be different from the incoming camera type.
const type = React.useMemo(() => {
if (!mediaTrackSettings) {
return null;
}
// On desktop no value will be returned, in this case we should assume the cameraType is 'front'
const { facingMode = 'user' } = mediaTrackSettings;
return FacingModeToCameraType[facingMode];
}, [mediaTrackSettings]);
const getStreamDeviceAsync = React.useCallback(async () => {
try {
return await Utils.getPreferredStreamDevice(preferredType);
}
catch (nativeEvent) {
if (__DEV__) {
console.warn(`Error requesting UserMedia for type "${preferredType}":`, nativeEvent);
}
if (onMountError) {
onMountError({ nativeEvent });
}
return null;
}
}, [preferredType, onMountError]);
const resumeAsync = React.useCallback(async () => {
const nextStream = await getStreamDeviceAsync();
if (Utils.compareStreams(nextStream, stream)) {
// Do nothing if the streams are the same.
// This happens when the device only supports one camera (i.e. desktop) and the mode was toggled between front/back while already active.
// Without this check there is a screen flash while the video switches.
return false;
}
// Save a history of all active streams (usually 2+) so we can close them later.
// Keeping them open makes swapping camera types much faster.
if (!activeStreams.current.some((value) => value.id === nextStream?.id)) {
activeStreams.current.push(nextStream);
}
// Set the new stream -> update the video, settings, and actual camera type.
setStream(nextStream);
if (onCameraReady) {
onCameraReady();
}
return false;
}, [getStreamDeviceAsync, setStream, onCameraReady, stream, activeStreams.current]);
React.useEffect(() => {
// Restart the camera and guard concurrent actions.
if (isStartingCamera.current) {
return;
}
isStartingCamera.current = true;
resumeAsync()
.then((isStarting) => {
isStartingCamera.current = isStarting;
})
.catch(() => {
// ensure the camera can be started again.
isStartingCamera.current = false;
});
}, [preferredType]);
// Update the native camera with any custom capabilities.
React.useEffect(() => {
const changes = {};
for (const key of Object.keys(settings)) {
if (!VALID_SETTINGS_KEYS.includes(key)) {
continue;
}
const nextValue = settings[key];
if (nextValue !== capabilities.current[key]) {
changes[key] = nextValue;
}
}
// Only update the native camera if changes were found
const hasChanges = !!Object.keys(changes).length;
const nextWebCameraSettings = { ...capabilities.current, ...changes };
if (hasChanges) {
Utils.syncTrackCapabilities(preferredType, stream, changes);
}
capabilities.current = nextWebCameraSettings;
}, [
settings.autoFocus,
settings.flashMode,
settings.exposureCompensation,
settings.colorTemperature,
settings.iso,
settings.brightness,
settings.contrast,
settings.saturation,
settings.sharpness,
settings.focusDistance,
settings.whiteBalance,
settings.zoom,
]);
React.useEffect(() => {
// set or unset the video source.
if (!video.current) {
return;
}
Utils.setVideoSource(video.current, stream);
}, [video.current, stream]);
React.useEffect(() => {
return () => {
// Clean up on dismount, this is important for making sure the camera light goes off when the component is removed.
for (const stream of activeStreams.current) {
// Close all open streams.
Utils.stopMediaStream(stream);
}
if (video.current) {
// Invalidate the video source.
Utils.setVideoSource(video.current, stream);
}
};
}, []);
// Update props when the video loads.
useLoadedVideo(video.current, () => {
Utils.syncTrackCapabilities(preferredType, stream, capabilities.current);
});
return {
type,
mediaTrackSettings,
};
}
//# sourceMappingURL=useWebCameraStream.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
import * as React from 'react';
import { BarCodeScanningResult, CameraPictureOptions, MountErrorListener } from '../legacy/Camera.types';
export declare function useWebQRScanner(video: React.MutableRefObject<HTMLVideoElement | null>, { isEnabled, captureOptions, interval, onScanned, onError, }: {
isEnabled: boolean;
captureOptions: Pick<CameraPictureOptions, 'scale' | 'isImageMirror'>;
interval?: number;
onScanned?: (scanningResult: {
nativeEvent: BarCodeScanningResult;
}) => void;
onError?: MountErrorListener;
}): void;
//# sourceMappingURL=useWebQRScanner.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useWebQRScanner.d.ts","sourceRoot":"","sources":["../../src/web/useWebQRScanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,wBAAwB,CAAC;AA6EhC,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,EACtD,EACE,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,eAAe,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,CAAC,cAAc,EAAE;QAAE,WAAW,EAAE,qBAAqB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,QAyDF"}

View File

@@ -0,0 +1,119 @@
import * as React from 'react';
import { captureImageData } from './WebCameraUtils';
const qrWorkerMethod = ({ data, width, height }) => {
// eslint-disable-next-line no-undef
const decoded = self.jsQR(data, width, height, {
inversionAttempts: 'attemptBoth',
});
let parsed;
try {
parsed = JSON.parse(decoded);
}
catch {
parsed = decoded;
}
if (parsed?.data) {
const nativeEvent = {
type: 'qr',
data: parsed.data,
cornerPoints: [],
bounds: { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } },
};
if (parsed.location) {
nativeEvent.cornerPoints = [
parsed.location.topLeftCorner,
parsed.location.bottomLeftCorner,
parsed.location.topRightCorner,
parsed.location.bottomRightCorner,
];
}
return nativeEvent;
}
return parsed;
};
const createWorkerAsyncFunction = (fn, deps) => {
if (typeof window === 'undefined') {
return async () => {
throw new Error('Cannot use createWorkerAsyncFunction in a non-browser environment');
};
}
const stringifiedFn = [
`self.func = ${fn.toString()};`,
'self.onmessage = (e) => {',
' const result = self.func(e.data);',
' self.postMessage(result);',
'};',
];
if (deps.length > 0) {
stringifiedFn.unshift(`importScripts(${deps.map((dep) => `'${dep}'`).join(', ')});`);
}
const blob = new Blob(stringifiedFn, { type: 'text/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
// First-In First-Out queue of promises
const promises = [];
worker.onmessage = (e) => promises.shift()?.resolve(e.data);
return (data) => {
return new Promise((resolve, reject) => {
promises.push({ resolve, reject });
worker.postMessage(data);
});
};
};
const decode = createWorkerAsyncFunction(qrWorkerMethod, [
'https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js',
]);
export function useWebQRScanner(video, { isEnabled, captureOptions, interval, onScanned, onError, }) {
const isRunning = React.useRef(false);
const timeout = React.useRef(undefined);
async function scanAsync() {
// If interval is 0 then only scan once.
if (!isRunning.current || !onScanned) {
stop();
return;
}
try {
const data = captureImageData(video.current, captureOptions);
if (data) {
const nativeEvent = await decode(data);
if (nativeEvent?.data) {
onScanned({
nativeEvent,
});
}
}
}
catch (error) {
if (onError) {
onError({ nativeEvent: error });
}
}
finally {
// If interval is 0 then only scan once.
if (interval === 0) {
stop();
return;
}
const intervalToUse = !interval || interval < 0 ? 16 : interval;
// @ts-ignore: Type 'Timeout' is not assignable to type 'number'
timeout.current = setTimeout(() => {
scanAsync();
}, intervalToUse);
}
}
function stop() {
isRunning.current = false;
clearTimeout(timeout.current);
}
React.useEffect(() => {
if (isEnabled) {
isRunning.current = true;
scanAsync();
}
return () => {
if (isEnabled) {
stop();
}
};
}, [isEnabled]);
}
//# sourceMappingURL=useWebQRScanner.js.map

File diff suppressed because one or more lines are too long