UNPKG

expo-camera

Version:

Expo Camera standalone universal module

297 lines (252 loc) 8.26 kB
// @flow import React from 'react'; import PropTypes from 'prop-types'; import mapValues from 'lodash.mapvalues'; import { NativeModulesProxy, requireNativeViewManager } from 'expo-core'; import { findNodeHandle, ViewPropTypes, Platform } from 'react-native'; type PictureOptions = { quality?: number, base64?: boolean, exif?: boolean, skipProcessing?: boolean, onPictureSaved?: Function, // internal id?: number, fastMode?: boolean, }; type RecordingOptions = { maxDuration?: number, maxFileSize?: number, quality?: number | string, }; type CapturedPicture = { width: number, height: number, uri: string, base64?: string, exif?: Object, }; type RecordingResult = { uri: string, }; type EventCallbackArgumentsType = { nativeEvent: Object, }; type MountErrorNativeEventType = { message: string, }; type PictureSavedNativeEventType = { data: CapturedPicture, id: number, }; type PropsType = ViewPropTypes & { zoom?: number, ratio?: string, focusDepth?: number, type?: number | string, onCameraReady?: Function, useCamera2Api?: boolean, flashMode?: number | string, whiteBalance?: number | string, autoFocus?: string | boolean | number, pictureSize?: string, videoStabilizationMode?: number, onMountError?: MountErrorNativeEventType => void, barCodeScannerSettings?: {}, onBarCodeScanned?: ({ type: string, data: string }) => void, faceDetectorSettings?: {}, onFacesDetected?: ({ faces: Array<*> }) => void, }; const CameraManager: Object = NativeModulesProxy.ExponentCameraManager || NativeModulesProxy.ExponentCameraModule; const EventThrottleMs = 500; const _PICTURE_SAVED_CALLBACKS = {}; let _GLOBAL_PICTURE_ID = 1; export default class Camera extends React.Component<PropsType> { static Constants = { Type: CameraManager.Type, FlashMode: CameraManager.FlashMode, AutoFocus: CameraManager.AutoFocus, WhiteBalance: CameraManager.WhiteBalance, VideoQuality: CameraManager.VideoQuality, VideoStabilization: CameraManager.VideoStabilization || {} }; // Values under keys from this object will be transformed to native options static ConversionTables = { type: CameraManager.Type, flashMode: CameraManager.FlashMode, autoFocus: CameraManager.AutoFocus, whiteBalance: CameraManager.WhiteBalance, }; static propTypes = { ...ViewPropTypes, zoom: PropTypes.number, ratio: PropTypes.string, focusDepth: PropTypes.number, onMountError: PropTypes.func, pictureSize: PropTypes.string, onCameraReady: PropTypes.func, useCamera2Api: PropTypes.bool, onBarCodeScanned: PropTypes.func, barCodeScannerSettings: PropTypes.object, onFacesDetected: PropTypes.func, faceDetectorSettings: PropTypes.object, type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), videoStabilizationMode: PropTypes.number, whiteBalance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), autoFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]), }; static defaultProps: Object = { zoom: 0, ratio: '4:3', focusDepth: 0, faceDetectorSettings: {}, type: CameraManager.Type.back, autoFocus: CameraManager.AutoFocus.on, flashMode: CameraManager.FlashMode.off, whiteBalance: CameraManager.WhiteBalance.auto, }; _cameraRef: ?Object; _cameraHandle: ?number; _lastEvents: { [string]: string }; _lastEventsTimes: { [string]: Date }; constructor(props: PropsType) { super(props); this._lastEvents = {}; this._lastEventsTimes = {}; } async takePictureAsync(options?: PictureOptions): Promise<CapturedPicture> { if (!options) { options = {}; } if (!options.quality) { options.quality = 1; } if (options.onPictureSaved) { const id = _GLOBAL_PICTURE_ID++; _PICTURE_SAVED_CALLBACKS[id] = options.onPictureSaved; options.id = id; options.fastMode = true; } return await CameraManager.takePicture(options, this._cameraHandle); } async getSupportedRatiosAsync(): Promise<Array<string>> { if (Platform.OS === 'android') { return await CameraManager.getSupportedRatios(this._cameraHandle); } else { throw new Error('Ratio is not supported on iOS'); } } async getAvailablePictureSizesAsync(ratio?: string): Promise<Array<string>> { return await CameraManager.getAvailablePictureSizes(ratio, this._cameraHandle); } async recordAsync(options?: RecordingOptions): Promise<RecordingResult> { if (!options || typeof options !== 'object') { options = {}; } else if (typeof options.quality === 'string') { options.quality = Camera.Constants.VideoQuality[options.quality]; } return await CameraManager.record(options, this._cameraHandle); } stopRecording() { CameraManager.stopRecording(this._cameraHandle); } pausePreview() { CameraManager.pausePreview(this._cameraHandle); } resumePreview() { CameraManager.resumePreview(this._cameraHandle); } _onCameraReady = () => { if (this.props.onCameraReady) { this.props.onCameraReady(); } }; _onMountError = ({ nativeEvent }: { nativeEvent: MountErrorNativeEventType }) => { if (this.props.onMountError) { this.props.onMountError(nativeEvent); } }; _onPictureSaved = ({ nativeEvent }: { nativeEvent: PictureSavedNativeEventType }) => { const callback = _PICTURE_SAVED_CALLBACKS[nativeEvent.id]; if (callback) { callback(nativeEvent.data); delete _PICTURE_SAVED_CALLBACKS[nativeEvent.id]; } }; _onObjectDetected = (callback: ?Function) => ({ nativeEvent }: EventCallbackArgumentsType) => { const { type } = nativeEvent; if ( this._lastEvents[type] && this._lastEventsTimes[type] && JSON.stringify(nativeEvent) === this._lastEvents[type] && new Date() - this._lastEventsTimes[type] < EventThrottleMs ) { return; } if (callback) { callback(nativeEvent); this._lastEventsTimes[type] = new Date(); this._lastEvents[type] = JSON.stringify(nativeEvent); } }; _setReference = (ref: ?Object) => { if (ref) { this._cameraRef = ref; this._cameraHandle = findNodeHandle(ref); } else { this._cameraRef = null; this._cameraHandle = null; } }; _onBarCodeScanned = () => { const onBarCodeRead = this.props.onBarCodeRead && ((data) => { console.warn("'onBarCodeRead' is deprecated in favour of 'onBarCodeScanned'"); return this.props.onBarCodeRead(data); }); return this.props.onBarCodeScanned || onBarCodeRead; }; render() { const nativeProps = this._convertNativeProps(this.props); return ( <ExponentCamera {...nativeProps} ref={this._setReference} onCameraReady={this._onCameraReady} onMountError={this._onMountError} onPictureSaved={this._onPictureSaved} onBarCodeScanned={this._onObjectDetected(this._onBarCodeScanned())} onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)} /> ); } _convertNativeProps(props: PropsType) { const newProps = mapValues(props, this._convertProp); const propsKeys = Object.keys(newProps); if (!propsKeys.includes("barCodeScannerSettings") && propsKeys.includes("barCodeTypes")) { // barCodeTypes is deprecated newProps.barCodeScannerSettings = { barCodeTypes: newProps.barCodeTypes, }; } if (props.onBarCodeScanned || props.onBarCodeRead) { // onBarCodeRead is deprecated newProps.barCodeScannerEnabled = true; } if (props.onFacesDetected) { newProps.faceDetectorEnabled = true; } if (Platform.OS === 'ios') { delete newProps.ratio; delete newProps.useCamera2Api; } return newProps; } _convertProp(value: *, key: string): * { if (typeof value === 'string' && Camera.ConversionTables[key]) { return Camera.ConversionTables[key][value]; } return value; } } export const Constants = Camera.Constants; const ExponentCamera = requireNativeViewManager('ExponentCamera', Camera);