1import { createPermissionHook, Platform, UnavailabilityError } from 'expo-modules-core'; 2import * as React from 'react'; 3import { findNodeHandle } from 'react-native'; 4import ExponentCamera from './ExponentCamera'; 5import CameraManager from './ExponentCameraManager'; 6import { ConversionTables, ensureNativeProps } from './utils/props'; 7const EventThrottleMs = 500; 8const _PICTURE_SAVED_CALLBACKS = {}; 9let _GLOBAL_PICTURE_ID = 1; 10function ensurePictureOptions(options) { 11 const pictureOptions = !options || typeof options !== 'object' ? {} : options; 12 if (!pictureOptions.quality) { 13 pictureOptions.quality = 1; 14 } 15 if (pictureOptions.onPictureSaved) { 16 const id = _GLOBAL_PICTURE_ID++; 17 _PICTURE_SAVED_CALLBACKS[id] = pictureOptions.onPictureSaved; 18 pictureOptions.id = id; 19 pictureOptions.fastMode = true; 20 } 21 return pictureOptions; 22} 23function ensureRecordingOptions(options) { 24 let recordingOptions = options || {}; 25 if (!recordingOptions || typeof recordingOptions !== 'object') { 26 recordingOptions = {}; 27 } 28 else if (typeof recordingOptions.quality === 'string') { 29 recordingOptions.quality = Camera.Constants.VideoQuality[recordingOptions.quality]; 30 } 31 return recordingOptions; 32} 33function _onPictureSaved({ nativeEvent, }) { 34 const { id, data } = nativeEvent; 35 const callback = _PICTURE_SAVED_CALLBACKS[id]; 36 if (callback) { 37 callback(data); 38 delete _PICTURE_SAVED_CALLBACKS[id]; 39 } 40} 41export default class Camera extends React.Component { 42 /** 43 * Check whether the current device has a camera. This is useful for web and simulators cases. 44 * This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser). 45 * You will still need to check if the native permission has been accepted. 46 * @platform web 47 */ 48 static async isAvailableAsync() { 49 if (!CameraManager.isAvailableAsync) { 50 throw new UnavailabilityError('expo-camera', 'isAvailableAsync'); 51 } 52 return await CameraManager.isAvailableAsync(); 53 } 54 /** 55 * Returns a list of camera types `['front', 'back']`. This is useful for desktop browsers which only have front-facing cameras. 56 * @platform web 57 */ 58 static async getAvailableCameraTypesAsync() { 59 if (!CameraManager.getAvailableCameraTypesAsync) { 60 throw new UnavailabilityError('expo-camera', 'getAvailableCameraTypesAsync'); 61 } 62 return await CameraManager.getAvailableCameraTypesAsync(); 63 } 64 // @needsAudit 65 /** 66 * Queries the device for the available video codecs that can be used in video recording. 67 * @return A promise that resolves to a list of strings that represents available codecs. 68 * @platform ios 69 */ 70 static async getAvailableVideoCodecsAsync() { 71 if (!CameraManager.getAvailableVideoCodecsAsync) { 72 throw new UnavailabilityError('Camera', 'getAvailableVideoCodecsAsync'); 73 } 74 return await CameraManager.getAvailableVideoCodecsAsync(); 75 } 76 static Constants = { 77 Type: CameraManager.Type, 78 FlashMode: CameraManager.FlashMode, 79 AutoFocus: CameraManager.AutoFocus, 80 WhiteBalance: CameraManager.WhiteBalance, 81 VideoQuality: CameraManager.VideoQuality, 82 VideoStabilization: CameraManager.VideoStabilization || {}, 83 VideoCodec: CameraManager.VideoCodec, 84 }; 85 // Values under keys from this object will be transformed to native options 86 static ConversionTables = ConversionTables; 87 static defaultProps = { 88 zoom: 0, 89 ratio: '4:3', 90 focusDepth: 0, 91 faceDetectorSettings: {}, 92 type: CameraManager.Type.back, 93 autoFocus: CameraManager.AutoFocus.on, 94 flashMode: CameraManager.FlashMode.off, 95 whiteBalance: CameraManager.WhiteBalance.auto, 96 }; 97 // @needsAudit 98 /** 99 * @deprecated Use `getCameraPermissionsAsync` or `getMicrophonePermissionsAsync` instead. 100 * Checks user's permissions for accessing camera. 101 */ 102 static async getPermissionsAsync() { 103 console.warn(`"getPermissionsAsync()" is now deprecated. Please use "getCameraPermissionsAsync()" or "getMicrophonePermissionsAsync()" instead.`); 104 return CameraManager.getPermissionsAsync(); 105 } 106 // @needsAudit 107 /** 108 * Asks the user to grant permissions for accessing camera. 109 * On iOS this will require apps to specify both `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` entries in the **Info.plist**. 110 * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse). 111 * @deprecated Use `requestCameraPermissionsAsync` or `requestMicrophonePermissionsAsync` instead. 112 */ 113 static async requestPermissionsAsync() { 114 console.warn(`"requestPermissionsAsync()" is now deprecated. Please use "requestCameraPermissionsAsync()" or "requestMicrophonePermissionsAsync()" instead.`); 115 return CameraManager.requestPermissionsAsync(); 116 } 117 // @needsAudit 118 /** 119 * Checks user's permissions for accessing camera. 120 * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse). 121 */ 122 static async getCameraPermissionsAsync() { 123 return CameraManager.getCameraPermissionsAsync(); 124 } 125 // @needsAudit 126 /** 127 * Asks the user to grant permissions for accessing camera. 128 * On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**. 129 * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse). 130 */ 131 static async requestCameraPermissionsAsync() { 132 return CameraManager.requestCameraPermissionsAsync(); 133 } 134 // @needsAudit 135 /** 136 * Check or request permissions to access the camera. 137 * This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions. 138 * 139 * @example 140 * ```ts 141 * const [status, requestPermission] = Camera.useCameraPermissions(); 142 * ``` 143 */ 144 static useCameraPermissions = createPermissionHook({ 145 getMethod: Camera.getCameraPermissionsAsync, 146 requestMethod: Camera.requestCameraPermissionsAsync, 147 }); 148 // @needsAudit 149 /** 150 * Checks user's permissions for accessing microphone. 151 * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse). 152 */ 153 static async getMicrophonePermissionsAsync() { 154 return CameraManager.getMicrophonePermissionsAsync(); 155 } 156 // @needsAudit 157 /** 158 * Asks the user to grant permissions for accessing the microphone. 159 * On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**. 160 * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse). 161 */ 162 static async requestMicrophonePermissionsAsync() { 163 return CameraManager.requestMicrophonePermissionsAsync(); 164 } 165 // @needsAudit 166 /** 167 * Check or request permissions to access the microphone. 168 * This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions. 169 * 170 * @example 171 * ```ts 172 * const [status, requestPermission] = Camera.useMicrophonePermissions(); 173 * ``` 174 */ 175 static useMicrophonePermissions = createPermissionHook({ 176 getMethod: Camera.getMicrophonePermissionsAsync, 177 requestMethod: Camera.requestMicrophonePermissionsAsync, 178 }); 179 _cameraHandle; 180 _cameraRef; 181 _lastEvents = {}; 182 _lastEventsTimes = {}; 183 // @needsAudit 184 /** 185 * Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation 186 * (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential 187 * to set ratio prop to get a picture with correct dimensions. 188 * > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method. 189 * @param options An object in form of `CameraPictureOptions` type. 190 * @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS, 191 * Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify 192 * the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data 193 * of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source 194 * for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF 195 * data for the image--the names of its properties are EXIF tags and their values are the values for those tags. 196 * 197 * > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem.md#filesystemcopyasyncoptions) 198 * > to make a permanent copy of the image. 199 */ 200 async takePictureAsync(options) { 201 const pictureOptions = ensurePictureOptions(options); 202 return await CameraManager.takePicture(pictureOptions, this._cameraHandle); 203 } 204 /** 205 * Get aspect ratios that are supported by the device and can be passed via `ratio` prop. 206 * @return Returns a Promise that resolves to an array of strings representing ratios, eg. `['4:3', '1:1']`. 207 * @platform android 208 */ 209 async getSupportedRatiosAsync() { 210 if (!CameraManager.getSupportedRatios) { 211 throw new UnavailabilityError('Camera', 'getSupportedRatiosAsync'); 212 } 213 return await CameraManager.getSupportedRatios(this._cameraHandle); 214 } 215 /** 216 * Get picture sizes that are supported by the device for given `ratio`. 217 * @param ratio A string representing aspect ratio of sizes to be returned. 218 * @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop. 219 * The list varies across Android devices but is the same for every iOS. 220 */ 221 async getAvailablePictureSizesAsync(ratio) { 222 if (!CameraManager.getAvailablePictureSizes) { 223 throw new UnavailabilityError('Camera', 'getAvailablePictureSizesAsync'); 224 } 225 return await CameraManager.getAvailablePictureSizes(ratio, this._cameraHandle); 226 } 227 /** 228 * Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation. 229 * Flipping camera during a recording results in stopping it. 230 * @param options A map of `CameraRecordingOptions` type. 231 * @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS. 232 * The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped. 233 * @platform android 234 * @platform ios 235 */ 236 async recordAsync(options) { 237 if (!CameraManager.record) { 238 throw new UnavailabilityError('Camera', 'recordAsync'); 239 } 240 const recordingOptions = ensureRecordingOptions(options); 241 return await CameraManager.record(recordingOptions, this._cameraHandle); 242 } 243 /** 244 * Stops recording if any is in progress. 245 */ 246 stopRecording() { 247 if (!CameraManager.stopRecording) { 248 throw new UnavailabilityError('Camera', 'stopRecording'); 249 } 250 CameraManager.stopRecording(this._cameraHandle); 251 } 252 /** 253 * Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused. 254 */ 255 pausePreview() { 256 if (!CameraManager.pausePreview) { 257 throw new UnavailabilityError('Camera', 'pausePreview'); 258 } 259 CameraManager.pausePreview(this._cameraHandle); 260 } 261 /** 262 * Resumes the camera preview. 263 */ 264 resumePreview() { 265 if (!CameraManager.resumePreview) { 266 throw new UnavailabilityError('Camera', 'resumePreview'); 267 } 268 CameraManager.resumePreview(this._cameraHandle); 269 } 270 _onCameraReady = () => { 271 if (this.props.onCameraReady) { 272 this.props.onCameraReady(); 273 } 274 }; 275 _onMountError = ({ nativeEvent }) => { 276 if (this.props.onMountError) { 277 this.props.onMountError(nativeEvent); 278 } 279 }; 280 _onResponsiveOrientationChanged = ({ nativeEvent, }) => { 281 if (this.props.onResponsiveOrientationChanged) { 282 this.props.onResponsiveOrientationChanged(nativeEvent); 283 } 284 }; 285 _onObjectDetected = (callback) => ({ nativeEvent }) => { 286 const { type } = nativeEvent; 287 if (this._lastEvents[type] && 288 this._lastEventsTimes[type] && 289 JSON.stringify(nativeEvent) === this._lastEvents[type] && 290 new Date().getTime() - this._lastEventsTimes[type].getTime() < EventThrottleMs) { 291 return; 292 } 293 if (callback) { 294 callback(nativeEvent); 295 this._lastEventsTimes[type] = new Date(); 296 this._lastEvents[type] = JSON.stringify(nativeEvent); 297 } 298 }; 299 _setReference = (ref) => { 300 if (ref) { 301 this._cameraRef = ref; 302 // TODO(Bacon): Unify these - perhaps with hooks? 303 if (Platform.OS === 'web') { 304 this._cameraHandle = ref; 305 } 306 else { 307 this._cameraHandle = findNodeHandle(ref); 308 } 309 } 310 else { 311 this._cameraRef = null; 312 this._cameraHandle = null; 313 } 314 }; 315 render() { 316 const nativeProps = ensureNativeProps(this.props); 317 const onBarCodeScanned = this.props.onBarCodeScanned 318 ? this._onObjectDetected(this.props.onBarCodeScanned) 319 : undefined; 320 const onFacesDetected = this._onObjectDetected(this.props.onFacesDetected); 321 return (React.createElement(ExponentCamera, { ...nativeProps, ref: this._setReference, onCameraReady: this._onCameraReady, onMountError: this._onMountError, onBarCodeScanned: onBarCodeScanned, onFacesDetected: onFacesDetected, onPictureSaved: _onPictureSaved, onResponsiveOrientationChanged: this._onResponsiveOrientationChanged })); 322 } 323} 324export const { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } = Camera; 325//# sourceMappingURL=Camera.js.map