This commit is contained in:
parent
d93a6c7e11
commit
22533ed8e8
@ -1,21 +1,24 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.Manifest;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.facebook.react.bridge.*;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
||||
import com.facebook.react.uimanager.UIBlock;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import org.reactnative.barcodedetector.BarcodeFormatUtils;
|
||||
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
|
||||
import org.reactnative.camera.utils.ScopedContext;
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import com.google.android.cameraview.Size;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@ -369,4 +372,22 @@ public class CameraModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void checkIfRecordAudioPermissionsAreDefined(final Promise promise) {
|
||||
try {
|
||||
PackageInfo info = getCurrentActivity().getPackageManager().getPackageInfo(getReactApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
if (info.requestedPermissions != null) {
|
||||
for (String p : info.requestedPermissions) {
|
||||
if (p.equals(Manifest.permission.RECORD_AUDIO)) {
|
||||
promise.resolve(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,8 +159,16 @@ _It's the RNCamera's reference_
|
||||
|
||||
#### `status`
|
||||
|
||||
One of `RNCamera.Constants.CameraStatus`
|
||||
|
||||
'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED'
|
||||
|
||||
#### `recordAudioPermissionStatus`
|
||||
|
||||
One of `RNCamera.Constants.RecordAudioPermissionStatus`.
|
||||
|
||||
`'AUTHORIZED'` | `'NOT_AUTHORIZED'` | `'PENDING_AUTHORIZATION'`
|
||||
|
||||
## Properties
|
||||
|
||||
#### `autoFocus`
|
||||
|
||||
@ -81,9 +81,8 @@ export default class CameraScreen extends React.Component {
|
||||
|
||||
takePicture = async function() {
|
||||
if (this.camera) {
|
||||
this.camera.takePictureAsync().then(data => {
|
||||
console.log('data: ', data);
|
||||
});
|
||||
const data = await this.camera.takePictureAsync();
|
||||
console.warn('takePicture ', data);
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,10 +95,10 @@ export default class CameraScreen extends React.Component {
|
||||
this.setState({ isRecording: true });
|
||||
const data = await promise;
|
||||
this.setState({ isRecording: false });
|
||||
console.warn(data);
|
||||
console.warn('takeVideo', data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -607,11 +607,6 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
|
||||
[self onReady:nil];
|
||||
return;
|
||||
#endif
|
||||
// NSDictionary *cameraPermissions = [EXCameraPermissionRequester permissions];
|
||||
// if (![cameraPermissions[@"status"] isEqualToString:@"granted"]) {
|
||||
// [self onMountingError:@{@"message": @"Camera permissions not granted - component could not be rendered."}];
|
||||
// return;
|
||||
// }
|
||||
dispatch_async(self.sessionQueue, ^{
|
||||
if (self.presetCamera == AVCaptureDevicePositionUnspecified) {
|
||||
return;
|
||||
|
||||
@ -371,11 +371,31 @@ RCT_EXPORT_METHOD(checkDeviceAuthorizationStatus:(RCTPromiseResolveBlock)resolve
|
||||
|
||||
RCT_EXPORT_METHOD(checkVideoAuthorizationStatus:(RCTPromiseResolveBlock)resolve
|
||||
reject:(__unused RCTPromiseRejectBlock)reject) {
|
||||
__block NSString *mediaType = AVMediaTypeVideo;
|
||||
|
||||
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
|
||||
resolve(@(granted));
|
||||
}];
|
||||
if ([[NSBundle mainBundle].infoDictionary objectForKey:@"NSCameraUsageDescription"] != nil) {
|
||||
__block NSString *mediaType = AVMediaTypeVideo;
|
||||
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
|
||||
resolve(@(granted));
|
||||
}];
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
RCTLogWarn(@"Checking video permissions without having key 'NSCameraUsageDescription' defined in your Info.plist. You will have to add it to your Info.plist file, otherwise RNCamera is not allowed to use the camera. You can learn more about adding permissions here: https://stackoverflow.com/a/38498347/4202031");
|
||||
#endif
|
||||
resolve(@(NO));
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(checkRecordAudioAuthorizationStatus:(RCTPromiseResolveBlock)resolve
|
||||
reject:(__unused RCTPromiseRejectBlock)reject) {
|
||||
if ([[NSBundle mainBundle].infoDictionary objectForKey:@"NSMicrophoneUsageDescription"] != nil) {
|
||||
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
|
||||
resolve(@(granted));
|
||||
}];
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
RCTLogWarn(@"Checking audio permissions without having key 'NSMicrophoneUsageDescription' defined in your Info.plist. Audio Recording for your video files is therefore disabled. If you do not need audio on your videos is is recommended to set the 'captureAudio' property on your component instance to 'false', otherwise you will have to add the key 'NSMicrophoneUsageDescription' to your Info.plist. You can learn more about adding permissions here: https://stackoverflow.com/a/38498347/4202031");
|
||||
#endif
|
||||
resolve(@(NO));
|
||||
}
|
||||
}
|
||||
|
||||
RCT_REMAP_METHOD(getAvailablePictureSizes,
|
||||
|
||||
@ -14,9 +14,39 @@ import {
|
||||
View,
|
||||
Text,
|
||||
UIManager,
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
|
||||
import { requestPermissions } from './handlePermissions';
|
||||
const requestPermissions = async (
|
||||
hasVideoAndAudio,
|
||||
CameraManager,
|
||||
permissionDialogTitle,
|
||||
permissionDialogMessage,
|
||||
): Promise<boolean> => {
|
||||
if (Platform.OS === 'ios') {
|
||||
let check = hasVideoAndAudio
|
||||
? CameraManager.checkDeviceAuthorizationStatus
|
||||
: CameraManager.checkVideoAuthorizationStatus;
|
||||
|
||||
if (check) return await check();
|
||||
} else if (Platform.OS === 'android') {
|
||||
let params = undefined;
|
||||
if (permissionDialogTitle || permissionDialogMessage)
|
||||
params = { title: permissionDialogTitle, message: permissionDialogMessage };
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, params);
|
||||
if (!hasVideoAndAudio)
|
||||
return granted === PermissionsAndroid.RESULTS.GRANTED || granted === true;
|
||||
const grantedAudio = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
|
||||
params,
|
||||
);
|
||||
return (
|
||||
(granted === PermissionsAndroid.RESULTS.GRANTED || granted === true) &&
|
||||
(grantedAudio === PermissionsAndroid.RESULTS.GRANTED || grantedAudio === true)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {},
|
||||
|
||||
124
src/RNCamera.js
124
src/RNCamera.js
@ -11,11 +11,61 @@ import {
|
||||
ActivityIndicator,
|
||||
Text,
|
||||
StyleSheet,
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
|
||||
import type { FaceFeature } from './FaceDetector';
|
||||
|
||||
import { requestPermissions } from './handlePermissions';
|
||||
const requestPermissions = async (
|
||||
captureAudio: boolean,
|
||||
CameraManager: any,
|
||||
permissionDialogTitle?: string,
|
||||
permissionDialogMessage?: string,
|
||||
): Promise<{ hasCameraPermissions: boolean, hasRecordAudioPermissions: boolean }> => {
|
||||
let hasCameraPermissions = false;
|
||||
let hasRecordAudioPermissions = false;
|
||||
|
||||
let params = undefined;
|
||||
if (permissionDialogTitle || permissionDialogMessage) {
|
||||
params = { title: permissionDialogTitle, message: permissionDialogMessage };
|
||||
}
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
hasCameraPermissions = await CameraManager.checkVideoAuthorizationStatus();
|
||||
} else if (Platform.OS === 'android') {
|
||||
const cameraPermissionResult = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.CAMERA,
|
||||
params,
|
||||
);
|
||||
hasCameraPermissions = cameraPermissionResult === PermissionsAndroid.RESULTS.GRANTED;
|
||||
}
|
||||
|
||||
if (captureAudio) {
|
||||
if (Platform.OS === 'ios') {
|
||||
hasRecordAudioPermissions = await CameraManager.checkRecordAudioAuthorizationStatus();
|
||||
} else if (Platform.OS === 'android') {
|
||||
if (await CameraManager.checkIfRecordAudioPermissionsAreDefined()) {
|
||||
const audioPermissionResult = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
|
||||
params,
|
||||
);
|
||||
hasRecordAudioPermissions = audioPermissionResult === PermissionsAndroid.RESULTS.GRANTED;
|
||||
} else if (__DEV__) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`The 'captureAudio' property set on RNCamera instance but 'RECORD_AUDIO' permissions not defined in the applications 'AndroidManifest.xml'. ` +
|
||||
`If you want to record audio you will have to add '<uses-permission android:name="android.permission.RECORD_AUDIO"/>' to your 'AndroidManifest.xml'. ` +
|
||||
`Otherwise you should set the 'captureAudio' property on the component instance to 'false'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasCameraPermissions,
|
||||
hasRecordAudioPermissions,
|
||||
};
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
authorizationContainer: {
|
||||
@ -110,6 +160,7 @@ type PropsType = typeof View.props & {
|
||||
type StateType = {
|
||||
isAuthorized: boolean,
|
||||
isAuthorizationChecked: boolean,
|
||||
recordAudioPermissionStatus: RecordAudioPermissionStatus,
|
||||
};
|
||||
|
||||
export type Status = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED';
|
||||
@ -120,6 +171,16 @@ const CameraStatus: { [key: Status]: Status } = {
|
||||
NOT_AUTHORIZED: 'NOT_AUTHORIZED',
|
||||
};
|
||||
|
||||
export type RecordAudioPermissionStatus = 'AUTHORIZED' | 'NOT_AUTHORIZED' | 'PENDING_AUTHORIZATION';
|
||||
|
||||
const RecordAudioPermissionStatusEnum: {
|
||||
[key: RecordAudioPermissionStatus]: RecordAudioPermissionStatus,
|
||||
} = {
|
||||
AUTHORIZED: 'AUTHORIZED',
|
||||
PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION',
|
||||
NOT_AUTHORIZED: 'NOT_AUTHORIZED',
|
||||
};
|
||||
|
||||
const CameraManager: Object = NativeModules.RNCameraManager ||
|
||||
NativeModules.RNCameraModule || {
|
||||
stubbed: true,
|
||||
@ -172,6 +233,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection,
|
||||
FaceDetection: CameraManager.FaceDetection,
|
||||
CameraStatus,
|
||||
RecordAudioPermissionStatus: RecordAudioPermissionStatusEnum,
|
||||
VideoStabilization: CameraManager.VideoStabilization,
|
||||
Orientation: {
|
||||
auto: 'auto',
|
||||
@ -281,6 +343,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
this.state = {
|
||||
isAuthorized: false,
|
||||
isAuthorizationChecked: false,
|
||||
recordAudioPermissionStatus: RecordAudioPermissionStatusEnum.PENDING_AUTHORIZATION,
|
||||
};
|
||||
}
|
||||
|
||||
@ -296,9 +359,11 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
if (typeof options.orientation !== 'number') {
|
||||
const { orientation } = options;
|
||||
options.orientation = CameraManager.Orientation[orientation];
|
||||
if (typeof options.orientation !== 'number') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Orientation '${orientation}' is invalid.`);
|
||||
if (__DEV__) {
|
||||
if (typeof options.orientation !== 'number') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Orientation '${orientation}' is invalid.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -333,9 +398,11 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
if (typeof options.orientation !== 'number') {
|
||||
const { orientation } = options;
|
||||
options.orientation = CameraManager.Orientation[orientation];
|
||||
if (typeof options.orientation !== 'number') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Orientation '${orientation}' is invalid.`);
|
||||
if (__DEV__) {
|
||||
if (typeof options.orientation !== 'number') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Orientation '${orientation}' is invalid.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,11 +414,26 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
}
|
||||
}
|
||||
|
||||
const { captureAudio } = this.props
|
||||
const { recordAudioPermissionStatus } = this.state;
|
||||
const { captureAudio } = this.props;
|
||||
|
||||
if (!captureAudio) {
|
||||
options.mute = true
|
||||
if (
|
||||
!captureAudio ||
|
||||
recordAudioPermissionStatus !== RecordAudioPermissionStatusEnum.AUTHORIZED
|
||||
) {
|
||||
options.mute = true;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (
|
||||
(options.mute || captureAudio) &&
|
||||
recordAudioPermissionStatus !== RecordAudioPermissionStatusEnum.AUTHORIZED
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Recording with audio not possible. Permissions are missing.');
|
||||
}
|
||||
}
|
||||
|
||||
return await CameraManager.record(options, this._cameraHandle);
|
||||
}
|
||||
|
||||
@ -423,9 +505,8 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const hasVideoAndAudio = this.props.captureAudio;
|
||||
const isAuthorized = await requestPermissions(
|
||||
hasVideoAndAudio,
|
||||
const { hasCameraPermissions, hasRecordAudioPermissions } = await requestPermissions(
|
||||
this.props.captureAudio,
|
||||
CameraManager,
|
||||
this.props.permissionDialogTitle,
|
||||
this.props.permissionDialogMessage,
|
||||
@ -433,7 +514,16 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
if (this._isMounted === false) {
|
||||
return;
|
||||
}
|
||||
this.setState({ isAuthorized, isAuthorizationChecked: true });
|
||||
|
||||
const recordAudioPermissionStatus = hasRecordAudioPermissions
|
||||
? RecordAudioPermissionStatusEnum.AUTHORIZED
|
||||
: RecordAudioPermissionStatusEnum.NOT_AUTHORIZED;
|
||||
|
||||
this.setState({
|
||||
isAuthorized: hasCameraPermissions,
|
||||
isAuthorizationChecked: true,
|
||||
recordAudioPermissionStatus,
|
||||
});
|
||||
}
|
||||
|
||||
getStatus = (): Status => {
|
||||
@ -449,7 +539,11 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
|
||||
renderChildren = (): * => {
|
||||
if (this.hasFaCC()) {
|
||||
return this.props.children({ camera: this, status: this.getStatus() });
|
||||
return this.props.children({
|
||||
camera: this,
|
||||
status: this.getStatus(),
|
||||
recordAudioPermissionStatus: this.state.recordAudioPermissionStatus,
|
||||
});
|
||||
}
|
||||
return this.props.children;
|
||||
};
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import { PermissionsAndroid, Platform } from 'react-native';
|
||||
|
||||
export const requestPermissions = async (
|
||||
hasVideoAndAudio,
|
||||
CameraManager,
|
||||
permissionDialogTitle,
|
||||
permissionDialogMessage,
|
||||
) => {
|
||||
if (Platform.OS === 'ios') {
|
||||
let check = hasVideoAndAudio
|
||||
? CameraManager.checkDeviceAuthorizationStatus
|
||||
: CameraManager.checkVideoAuthorizationStatus;
|
||||
|
||||
if (check) return await check();
|
||||
} else if (Platform.OS === 'android') {
|
||||
let params = undefined;
|
||||
if (permissionDialogTitle || permissionDialogMessage)
|
||||
params = { title: permissionDialogTitle, message: permissionDialogMessage };
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, params);
|
||||
if (!hasVideoAndAudio)
|
||||
return granted === PermissionsAndroid.RESULTS.GRANTED || granted === true;
|
||||
const grantedAudio = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
|
||||
params,
|
||||
);
|
||||
return (
|
||||
(granted === PermissionsAndroid.RESULTS.GRANTED || granted === true) &&
|
||||
(grantedAudio === PermissionsAndroid.RESULTS.GRANTED || grantedAudio === true)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
12
types/index.d.ts
vendored
12
types/index.d.ts
vendored
@ -86,10 +86,18 @@ type GoogleVisionBarcodeMode = Readonly<{ NORMAL: any; ALTERNATE: any; INVERTED:
|
||||
// FaCC (Function as Child Components)
|
||||
type Self<T> = { [P in keyof T]: P };
|
||||
type CameraStatus = Readonly<Self<{ READY: any; PENDING_AUTHORIZATION: any; NOT_AUTHORIZED: any }>>;
|
||||
type RecordAudioPermissionStatus = Readonly<
|
||||
Self<{
|
||||
AUTHORIZED: 'AUTHORIZED';
|
||||
PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION';
|
||||
NOT_AUTHORIZED: 'NOT_AUTHORIZED';
|
||||
}>
|
||||
>;
|
||||
type FaCC = (
|
||||
params: {
|
||||
camera: RNCamera;
|
||||
status: keyof CameraStatus;
|
||||
recordAudioPermissionStatus: keyof RecordAudioPermissionStatus;
|
||||
},
|
||||
) => JSX.Element;
|
||||
|
||||
@ -130,6 +138,7 @@ export interface RNCameraProps {
|
||||
pendingAuthorizationView?: JSX.Element;
|
||||
useCamera2Api?: boolean;
|
||||
whiteBalance?: keyof WhiteBalance;
|
||||
captureAudio?: boolean;
|
||||
|
||||
onCameraReady?(): void;
|
||||
onMountError?(error: { message: string }): void;
|
||||
@ -175,9 +184,6 @@ export interface RNCameraProps {
|
||||
playSoundOnCapture?: boolean;
|
||||
|
||||
// -- IOS ONLY PROPS
|
||||
|
||||
/** iOS Only */
|
||||
captureAudio?: boolean;
|
||||
defaultVideoQuality?: keyof VideoQuality;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user