UNPKG

6.98 kBJavaScriptView Raw
1// @flow
2import React from 'react';
3import PropTypes from 'prop-types';
4import { mapValues } from 'lodash';
5import {
6 findNodeHandle,
7 NativeModules,
8 ViewPropTypes,
9 Platform,
10 requireNativeComponent,
11} from 'react-native';
12
13import type { FaceFeature } from './FaceDetector';
14
15type PictureOptions = {
16 quality?: number,
17};
18
19type TrackedFaceFeature = FaceFeature & {
20 faceID?: number,
21};
22
23type RecordingOptions = {
24 maxDuration?: number,
25 maxFileSize?: number,
26 quality?: number | string,
27};
28
29type EventCallbackArgumentsType = {
30 nativeEvent: Object,
31};
32
33type MountErrorNativeEventType = {
34 message: string,
35};
36
37type PropsType = ViewPropTypes & {
38 zoom?: number,
39 ratio?: string,
40 focusDepth?: number,
41 type?: number | string,
42 onCameraReady?: Function,
43 onBarCodeRead?: Function,
44 faceDetectionMode?: number,
45 flashMode?: number | string,
46 barCodeTypes?: Array<string | number>,
47 whiteBalance?: number | string,
48 faceDetectionLandmarks?: number,
49 autoFocus?: string | boolean | number,
50 faceDetectionClassifications?: number,
51 onMountError?: MountErrorNativeEventType => void,
52 onFacesDetected?: ({ faces: Array<TrackedFaceFeature> }) => void,
53};
54
55const CameraManager: Object =
56 NativeModules.ExponentCameraManager || NativeModules.ExponentCameraModule;
57
58const EventThrottleMs = 500;
59
60export default class Camera extends React.Component<PropsType> {
61 static Constants = {
62 Type: CameraManager.Type,
63 FlashMode: CameraManager.FlashMode,
64 AutoFocus: CameraManager.AutoFocus,
65 WhiteBalance: CameraManager.WhiteBalance,
66 VideoQuality: CameraManager.VideoQuality,
67 BarCodeType: CameraManager.BarCodeType,
68 FaceDetection: CameraManager.FaceDetection,
69 };
70
71 // Values under keys from this object will be transformed to native options
72 static ConversionTables = {
73 type: CameraManager.Type,
74 flashMode: CameraManager.FlashMode,
75 autoFocus: CameraManager.AutoFocus,
76 whiteBalance: CameraManager.WhiteBalance,
77 faceDetectionMode: CameraManager.FaceDetection.Mode,
78 faceDetectionLandmarks: CameraManager.FaceDetection.Landmarks,
79 faceDetectionClassifications: CameraManager.FaceDetection.Classifications,
80 };
81
82 static propTypes = {
83 ...ViewPropTypes,
84 zoom: PropTypes.number,
85 ratio: PropTypes.string,
86 focusDepth: PropTypes.number,
87 onMountError: PropTypes.func,
88 onCameraReady: PropTypes.func,
89 onBarCodeRead: PropTypes.func,
90 onFacesDetected: PropTypes.func,
91 faceDetectionMode: PropTypes.number,
92 faceDetectionLandmarks: PropTypes.number,
93 faceDetectionClassifications: PropTypes.number,
94 barCodeTypes: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
95 type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
96 flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
97 whiteBalance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
98 autoFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
99 };
100
101 static defaultProps: Object = {
102 zoom: 0,
103 ratio: '4:3',
104 focusDepth: 0,
105 type: CameraManager.Type.back,
106 autoFocus: CameraManager.AutoFocus.on,
107 flashMode: CameraManager.FlashMode.off,
108 whiteBalance: CameraManager.WhiteBalance.auto,
109 faceDetectionMode: CameraManager.FaceDetection.fast,
110 barCodeTypes: Object.values(CameraManager.BarCodeType),
111 faceDetectionLandmarks: CameraManager.FaceDetection.Landmarks.none,
112 faceDetectionClassifications: CameraManager.FaceDetection.Classifications.none,
113 };
114
115 _cameraRef: ?Object;
116 _cameraHandle: ?number;
117 _lastEvents: { [string]: string };
118 _lastEventsTimes: { [string]: Date };
119
120 constructor(props: PropsType) {
121 super(props);
122 this._lastEvents = {};
123 this._lastEventsTimes = {};
124 }
125
126 async takePictureAsync(options?: PictureOptions) {
127 if (!options) {
128 options = {};
129 }
130 if (!options.quality) {
131 options.quality = 1;
132 }
133 return await CameraManager.takePicture(options, this._cameraHandle);
134 }
135
136 async getSupportedRatiosAsync() {
137 if (Platform.OS === 'android') {
138 return await CameraManager.getSupportedRatios(this._cameraHandle);
139 } else {
140 throw new Error('Ratio is not supported on iOS');
141 }
142 }
143
144 async recordAsync(options?: RecordingOptions) {
145 if (!options || typeof options !== 'object') {
146 options = {};
147 } else if (typeof options.quality === 'string') {
148 options.quality = Camera.Constants.VideoQuality[options.quality];
149 }
150 return await CameraManager.record(options, this._cameraHandle);
151 }
152
153 stopRecording() {
154 CameraManager.stopRecording(this._cameraHandle);
155 }
156
157 _onCameraReady = () => {
158 if (this.props.onCameraReady) {
159 this.props.onCameraReady();
160 }
161 };
162
163 _onMountError = ({ nativeEvent }: { nativeEvent: MountErrorNativeEventType }) => {
164 if (this.props.onMountError) {
165 this.props.onMountError(nativeEvent);
166 }
167 };
168
169 _onObjectDetected = (callback: ?Function) => ({ nativeEvent }: EventCallbackArgumentsType) => {
170 const { type } = nativeEvent;
171 if (
172 this._lastEvents[type] &&
173 this._lastEventsTimes[type] &&
174 JSON.stringify(nativeEvent) === this._lastEvents[type] &&
175 new Date() - this._lastEventsTimes[type] < EventThrottleMs
176 ) {
177 return;
178 }
179
180 if (callback) {
181 callback(nativeEvent);
182 this._lastEventsTimes[type] = new Date();
183 this._lastEvents[type] = JSON.stringify(nativeEvent);
184 }
185 };
186
187 _setReference = (ref: ?Object) => {
188 if (ref) {
189 this._cameraRef = ref;
190 this._cameraHandle = findNodeHandle(ref);
191 } else {
192 this._cameraRef = null;
193 this._cameraHandle = null;
194 }
195 };
196
197 render() {
198 const nativeProps = this._convertNativeProps(this.props);
199
200 return (
201 <ExponentCamera
202 {...nativeProps}
203 ref={this._setReference}
204 onCameraReady={this._onCameraReady}
205 onMountError={this._onMountError}
206 onBarCodeRead={this._onObjectDetected(this.props.onBarCodeRead)}
207 onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)}
208 />
209 );
210 }
211
212 _convertNativeProps(props: PropsType) {
213 const newProps = mapValues(props, this._convertProp);
214
215 if (props.onBarCodeRead) {
216 newProps.barCodeScannerEnabled = true;
217 }
218
219 if (props.onFacesDetected) {
220 newProps.faceDetectorEnabled = true;
221 }
222
223 if (Platform.OS === 'ios') {
224 delete newProps.ratio;
225 }
226
227 return newProps;
228 }
229
230 _convertProp(value: *, key: string): * {
231 if (typeof value === 'string' && Camera.ConversionTables[key]) {
232 return Camera.ConversionTables[key][value];
233 }
234
235 return value;
236 }
237}
238
239export const Constants = Camera.Constants;
240
241const ExponentCamera = requireNativeComponent('ExponentCamera', Camera, {
242 nativeOnly: {
243 onCameraReady: true,
244 onMountError: true,
245 onBarCodeRead: true,
246 onFaceDetected: true,
247 faceDetectorEnabled: true,
248 barCodeScannerEnabled: true,
249 },
250});