xref: /expo/packages/expo-camera/build/Camera.js (revision 689c28c8)
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