feat(rn-camera): deviceOrientation, videoOrientation, pictureOrientation

* feat(rn-camera): add deviceOrientation and videoOrientation to record response.

* feat(rn-camera): add deviceOrientation and pictureOrientation to take-picture-response

* fix(rn-camera): unify orientation handling for recording and takeing picture

* fix(types): adjust typescript types

* docs(rn-camra): document new properties

* fix(android): respect actual boolean value instead of only checking if it is present.

* feat(android): implement orientation prop for takePictureAsync

* feat(android): implement orientation prop for recordVideoAsync

* docs(rn-camera): adjust documentation

* fix(types): unmark properties as ios only

* fix(android): use constants
This commit is contained in:
Laurin Quast 2018-12-12 15:04:08 +01:00 committed by GitHub
parent 26300870ad
commit 401c485db3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 239 additions and 74 deletions

View File

@ -98,6 +98,8 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
private int mDisplayOrientation;
private int mOrientation = Constants.ORIENTATION_AUTO;
private float mZoom;
private int mWhiteBalance;
@ -154,7 +156,8 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
mMediaRecorder = null;
if (mIsRecording) {
mCallback.onVideoRecorded(mVideoPath);
int deviceOrientation = displayOrientationToOrientationEnum(mDisplayOrientation);
mCallback.onVideoRecorded(mVideoPath, mOrientation != Constants.ORIENTATION_AUTO ? mOrientation : deviceOrientation, deviceOrientation);
mIsRecording = false;
}
}
@ -400,8 +403,46 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
}
int orientationEnumToRotation(int orientation) {
switch(orientation) {
case Constants.ORIENTATION_UP:
return 0;
case Constants.ORIENTATION_DOWN:
return 180;
case Constants.ORIENTATION_LEFT:
return 270;
case Constants.ORIENTATION_RIGHT:
return 90;
default:
return Constants.ORIENTATION_UP;
}
}
int displayOrientationToOrientationEnum(int rotation) {
switch (rotation) {
case 0:
return Constants.ORIENTATION_UP;
case 90:
return Constants.ORIENTATION_RIGHT;
case 180:
return Constants.ORIENTATION_DOWN;
case 270:
return Constants.ORIENTATION_LEFT;
default:
return 1;
}
}
void takePictureInternal(final ReadableMap options) {
if (!isPictureCaptureInProgress.getAndSet(true)) {
if (options.hasKey("orientation")) {
mOrientation = options.getInt("orientation");
int rotation = orientationEnumToRotation(mOrientation);
mCameraParameters.setRotation(calcCameraRotation(rotation));
mCamera.setParameters(mCameraParameters);
}
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
@ -413,21 +454,25 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
if (mIsScanning) {
camera.setPreviewCallback(Camera1.this);
}
}else{
} else {
camera.stopPreview();
mIsPreviewActive = false;
camera.setPreviewCallback(null);
}
mCallback.onPictureTaken(data);
mOrientation = Constants.ORIENTATION_AUTO;
mCallback.onPictureTaken(data, displayOrientationToOrientationEnum(mDisplayOrientation));
}
});
}
}
@Override
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) {
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int orientation) {
if (!mIsRecording) {
if (orientation != Constants.ORIENTATION_AUTO) {
mOrientation = orientation;
}
setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile);
try {
mMediaRecorder.prepare();
@ -458,7 +503,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
return;
}
mDisplayOrientation = displayOrientation;
if (isCameraOpened()) {
if (isCameraOpened() && mOrientation == Constants.ORIENTATION_AUTO) {
mCameraParameters.setRotation(calcCameraRotation(displayOrientation));
mCamera.setParameters(mCameraParameters);
final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
@ -576,7 +621,12 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());
mCameraParameters.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight());
mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
if (mOrientation != Constants.ORIENTATION_AUTO) {
mCameraParameters.setRotation(calcCameraRotation(orientationEnumToRotation(mOrientation)));
} else {
mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
}
setAutoFocusInternal(mAutoFocus);
setFlashInternal(mFlash);
setAspectRatio(mAspectRatio);
@ -797,7 +847,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
setCamcorderProfile(CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH), recordAudio);
}
mMediaRecorder.setOrientationHint(calcCameraRotation(mDisplayOrientation));
mMediaRecorder.setOrientationHint(calcCameraRotation(mOrientation != Constants.ORIENTATION_AUTO ? orientationEnumToRotation(mOrientation) : mDisplayOrientation));
if (maxDuration != -1) {
mMediaRecorder.setMaxDuration(maxDuration);
@ -823,12 +873,13 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
mMediaRecorder = null;
}
int deviceOrientation = displayOrientationToOrientationEnum(mDisplayOrientation);
if (mVideoPath == null || !new File(mVideoPath).exists()) {
mCallback.onVideoRecorded(null);
mCallback.onVideoRecorded(null, mOrientation != Constants.ORIENTATION_AUTO ? mOrientation : deviceOrientation, deviceOrientation);
return;
}
mCallback.onVideoRecorded(mVideoPath);
mCallback.onVideoRecorded(mVideoPath, mOrientation != Constants.ORIENTATION_AUTO ? mOrientation : deviceOrientation, deviceOrientation);
mVideoPath = null;
}

View File

@ -178,7 +178,8 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
if (image.getFormat() == ImageFormat.JPEG) {
mCallback.onPictureTaken(data);
// @TODO: implement deviceOrientation
mCallback.onPictureTaken(data, 0);
} else {
mCallback.onFramePreview(data, image.getWidth(), image.getHeight(), mDisplayOrientation);
}
@ -316,7 +317,8 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
mMediaRecorder = null;
if (mIsRecording) {
mCallback.onVideoRecorded(mVideoPath);
// @TODO: implement videoOrientation and deviceOrientation calculation
mCallback.onVideoRecorded(mVideoPath, 0, 0);
mIsRecording = false;
}
}
@ -473,7 +475,7 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
}
@Override
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) {
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int orientation) {
if (!mIsRecording) {
setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile);
try {
@ -1130,10 +1132,12 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
mMediaRecorder = null;
if (mVideoPath == null || !new File(mVideoPath).exists()) {
mCallback.onVideoRecorded(null);
// @TODO: implement videoOrientation and deviceOrientation calculation
mCallback.onVideoRecorded(null, 0 , 0);
return;
}
mCallback.onVideoRecorded(mVideoPath);
// @TODO: implement videoOrientation and deviceOrientation calculation
mCallback.onVideoRecorded(mVideoPath, 0, 0);
mVideoPath = null;
}

View File

@ -500,7 +500,7 @@ public class CameraView extends FrameLayout {
/**
* Take a picture. The result will be returned to
* {@link Callback#onPictureTaken(CameraView, byte[])}.
* {@link Callback#onPictureTaken(CameraView, byte[], int)}.
*/
public void takePicture(ReadableMap options) {
mImpl.takePicture(options);
@ -508,15 +508,15 @@ public class CameraView extends FrameLayout {
/**
* Record a video and save it to file. The result will be returned to
* {@link Callback#onVideoRecorded(CameraView, String)}.
* {@link Callback#onVideoRecorded(CameraView, String, int, int)}.
* @param path Path to file that video will be saved to.
* @param maxDuration Maximum duration of the recording, in seconds.
* @param maxFileSize Maximum recording file size, in bytes.
* @param profile Quality profile of the recording.
*/
public boolean record(String path, int maxDuration, int maxFileSize,
boolean recordAudio, CamcorderProfile profile) {
return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile);
boolean recordAudio, CamcorderProfile profile, int orientation) {
return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile, orientation);
}
public void stopRecording() {
@ -575,16 +575,16 @@ public class CameraView extends FrameLayout {
}
@Override
public void onPictureTaken(byte[] data) {
public void onPictureTaken(byte[] data, int deviceOrientation) {
for (Callback callback : mCallbacks) {
callback.onPictureTaken(CameraView.this, data);
callback.onPictureTaken(CameraView.this, data, deviceOrientation);
}
}
@Override
public void onVideoRecorded(String path) {
public void onVideoRecorded(String path, int videoOrientation, int deviceOrientation) {
for (Callback callback : mCallbacks) {
callback.onVideoRecorded(CameraView.this, path);
callback.onVideoRecorded(CameraView.this, path, videoOrientation, deviceOrientation);
}
}
@ -706,7 +706,7 @@ public class CameraView extends FrameLayout {
* @param cameraView The associated {@link CameraView}.
* @param data JPEG data.
*/
public void onPictureTaken(CameraView cameraView, byte[] data) {
public void onPictureTaken(CameraView cameraView, byte[] data, int deviceOrientation) {
}
/**
@ -715,7 +715,7 @@ public class CameraView extends FrameLayout {
* @param cameraView The associated {@link CameraView}.
* @param path Path to recoredd video file.
*/
public void onVideoRecorded(CameraView cameraView, String path) {
public void onVideoRecorded(CameraView cameraView, String path, int videoOrientation, int deviceOrientation) {
}
public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int orientation) {

View File

@ -79,7 +79,7 @@ abstract class CameraViewImpl {
abstract void takePicture(ReadableMap options);
abstract boolean record(String path, int maxDuration, int maxFileSize,
boolean recordAudio, CamcorderProfile profile);
boolean recordAudio, CamcorderProfile profile, int orientation);
abstract void stopRecording();
@ -115,9 +115,9 @@ abstract class CameraViewImpl {
void onCameraClosed();
void onPictureTaken(byte[] data);
void onPictureTaken(byte[] data, int deviceOrientation);
void onVideoRecorded(String path);
void onVideoRecorded(String path, int videoOrientation, int deviceOrientation);
void onFramePreview(byte[] data, int width, int height, int orientation);

View File

@ -39,4 +39,9 @@ public interface Constants {
int WB_FLUORESCENT = 4;
int WB_INCANDESCENT = 5;
int ORIENTATION_AUTO = 0;
int ORIENTATION_UP = 1;
int ORIENTATION_DOWN = 2;
int ORIENTATION_LEFT = 3;
int ORIENTATION_RIGHT = 4;
}

View File

@ -125,6 +125,15 @@ public class CameraModule extends ReactContextBaseJavaModule {
put("BarcodeMode", getGoogleVisionBarcodeModeConstants());
}
}));
put("Orientation", Collections.unmodifiableMap(new HashMap<String, Object>() {
{
put("auto", Constants.ORIENTATION_AUTO);
put("portrait", Constants.ORIENTATION_UP);
put("portraitUpsideDown", Constants.ORIENTATION_DOWN);
put("landscapeLeft", Constants.ORIENTATION_LEFT);
put("landscapeRight", Constants.ORIENTATION_RIGHT);
}
}));
}
private Map<String, Object> getTypeConstants() {

View File

@ -39,4 +39,10 @@ public interface Constants {
int WB_SHADOW = 3;
int WB_FLUORESCENT = 4;
int WB_INCANDESCENT = 5;
int ORIENTATION_AUTO = 0;
int ORIENTATION_UP = 1;
int ORIENTATION_DOWN = 2;
int ORIENTATION_LEFT = 3;
int ORIENTATION_RIGHT = 4;
}

View File

@ -86,7 +86,7 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
}
@Override
public void onPictureTaken(CameraView cameraView, final byte[] data) {
public void onPictureTaken(CameraView cameraView, final byte[] data, int deviceOrientation) {
Promise promise = mPictureTakenPromises.poll();
ReadableMap options = mPictureTakenOptions.remove(promise);
if (options.hasKey("fastMode") && options.getBoolean("fastMode")) {
@ -94,20 +94,22 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
}
final File cacheDirectory = mPictureTakenDirectories.remove(promise);
if(Build.VERSION.SDK_INT >= 11/*HONEYCOMB*/) {
new ResolveTakenPictureAsyncTask(data, promise, options, cacheDirectory, RNCameraView.this)
new ResolveTakenPictureAsyncTask(data, promise, options, cacheDirectory, deviceOrientation, RNCameraView.this)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
new ResolveTakenPictureAsyncTask(data, promise, options, cacheDirectory, RNCameraView.this)
new ResolveTakenPictureAsyncTask(data, promise, options, cacheDirectory, deviceOrientation, RNCameraView.this)
.execute();
}
RNCameraViewHelper.emitPictureTakenEvent(cameraView);
}
@Override
public void onVideoRecorded(CameraView cameraView, String path) {
public void onVideoRecorded(CameraView cameraView, String path, int videoOrientation, int deviceOrientation) {
if (mVideoRecordedPromise != null) {
if (path != null) {
WritableMap result = Arguments.createMap();
result.putInt("videoOrientation", videoOrientation);
result.putInt("deviceOrientation", deviceOrientation);
result.putString("uri", RNFileUtils.uriFromFile(new File(path)).toString());
mVideoRecordedPromise.resolve(result);
} else {
@ -264,9 +266,17 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
profile = RNCameraViewHelper.getCamcorderProfile(options.getInt("quality"));
}
boolean recordAudio = !options.hasKey("mute");
boolean recordAudio = true;
if (options.hasKey("mute")) {
recordAudio = !options.getBoolean("mute");
}
if (super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile)) {
int orientation = Constants.ORIENTATION_AUTO;
if (options.hasKey("orientation")) {
orientation = options.getInt("orientation");
}
if (super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile, orientation)) {
mVideoRecordedPromise = promise;
} else {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.");

View File

@ -30,13 +30,15 @@ public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, Writable
private ReadableMap mOptions;
private File mCacheDirectory;
private Bitmap mBitmap;
private int mDeviceOrientation;
private PictureSavedDelegate mPictureSavedDelegate;
public ResolveTakenPictureAsyncTask(byte[] imageData, Promise promise, ReadableMap options, File cacheDirectory, PictureSavedDelegate delegate) {
public ResolveTakenPictureAsyncTask(byte[] imageData, Promise promise, ReadableMap options, File cacheDirectory, int deviceOrientation, PictureSavedDelegate delegate) {
mPromise = promise;
mOptions = options;
mImageData = imageData;
mCacheDirectory = cacheDirectory;
mDeviceOrientation = deviceOrientation;
mPictureSavedDelegate = delegate;
}
@ -49,6 +51,9 @@ public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, Writable
WritableMap response = Arguments.createMap();
ByteArrayInputStream inputStream = null;
response.putInt("deviceOrientation", mDeviceOrientation);
response.putInt("pictureOrientation", mOptions.hasKey("orientation") ? mOptions.getInt("orientation") : mDeviceOrientation);
if (mOptions.hasKey("skipProcessing")) {
try {
// Prepare file output

View File

@ -444,16 +444,19 @@ Supported options:
- `doNotSave` (boolean true or false). Use this with `true` if you do not want the picture to be saved as a file to cache. If no value is specified `doNotSave:false` is used. If you only need the base64 for the image, you can use this with `base64:true` and avoid having to save the file.
- `pauseAfterCapture` (boolean true or false). If true, pause the preview layer immediately after capturing the image. You will need to call `cameraRef.resumePreview()` before using the camera again. If no value is specified `pauseAfterCapture:false` is used.
- `orientation` (string or number). Specifies the orientation that us used for taking the picture. Possible values: `"portrait"`, `"portraitUpsideDown"`, `"landscapeLeft"` or `"landscapeRight"`.
The promise will be fulfilled with an object with some of the following properties:
- `width`: returns the image's width (taking image orientation into account)
- `height`: returns the image's height (taking image orientation into account)
- `uri`: returns the path to the image saved on your app's cache directory.
- `base64`: returns the base64 representation of the image if required.
- `uri`: (string) the path to the image saved on your app's cache directory.
- `base64`: (string?) the base64 representation of the image if required.
- `exif`: returns an exif map of the image if required.
- `pictureOrientation`: (number) the orientation of the picture
- `deviceOrientation`: (number) the orientation of the device
#### `recordAsync([options]): Promise`
@ -481,6 +484,8 @@ The promise will be fulfilled with an object with some of the following properti
- `ios` Specifies capture settings suitable for CIF quality (352x288 pixel) video output.
- `android` Not supported.
- `orientation` (string or number). Specifies the orientation that us used for recording the video. Possible values: `"portrait"`, `"portraitUpsideDown"`, `"landscapeLeft"` or `"landscapeRight"`.
If nothing is passed the device's highest camera quality will be used as default.
- `iOS` `codec`. This option specifies the codec of the output video. Setting the codec is only supported on `iOS >= 10`. The possible values are:
- `RNCamera.Constants.VideoCodec['H264']`
@ -500,7 +505,11 @@ The promise will be fulfilled with an object with some of the following properti
The promise will be fulfilled with an object with some of the following properties:
- `uri`: returns the path to the video saved on your app's cache directory.
- `uri`: (string) the path to the video saved on your app's cache directory.
- `videoOrientation`: (number) orientation of the video
- `deviceOrientation`: (number) orientation of the device
- `iOS` `codec`: the codec of the recorded video. One of `RNCamera.Constants.VideoCodec`

View File

@ -44,6 +44,8 @@
@property(assign, nonatomic) AVVideoCodecType videoCodecType;
@property (assign, nonatomic) AVCaptureVideoStabilizationMode videoStabilizationMode;
@property(assign, nonatomic, nullable) NSNumber *defaultVideoQuality;
@property(assign, nonatomic, nullable) NSNumber *deviceOrientation;
@property(assign, nonatomic, nullable) NSNumber *orientation;
- (id)initWithBridge:(RCTBridge *)bridge;
- (void)updateType;

View File

@ -373,20 +373,23 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
- (void)takePictureWithOrientation:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject{
[self.sensorOrientationChecker getDeviceOrientationWithBlock:^(UIInterfaceOrientation orientation) {
NSMutableDictionary *tmpOptions = [options mutableCopy];
tmpOptions[@"orientation"]=[NSNumber numberWithInteger:[self.sensorOrientationChecker convertToAVCaptureVideoOrientation: orientation]];
if ([tmpOptions valueForKey:@"orientation"] == nil) {
tmpOptions[@"orientation"] = [NSNumber numberWithInteger:[self.sensorOrientationChecker convertToAVCaptureVideoOrientation:orientation]];
}
self.deviceOrientation = [NSNumber numberWithInteger:orientation];
self.orientation = [NSNumber numberWithInteger:[tmpOptions[@"orientation"] integerValue]];
[self takePicture:tmpOptions resolve:resolve reject:reject];
}];
}
- (void)takePicture:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
int orientation;
if ([options[@"orientation"] integerValue]) {
orientation = [options[@"orientation"] integerValue];
} else {
if (!self.deviceOrientation) {
[self takePictureWithOrientation:options resolve:resolve reject:reject];
return;
}
NSInteger orientation = [options[@"orientation"] integerValue];
AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:orientation];
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
@ -395,7 +398,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[[self.previewLayer connection] setEnabled:NO];
}
BOOL useFastMode = options[@"fastMode"] && [options[@"fastMode"] boolValue];
BOOL useFastMode = [options valueForKey:@"fastMode"] != nil && [options[@"fastMode"] boolValue];
if (useFastMode) {
resolve(nil);
}
@ -439,8 +442,6 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
response[@"base64"] = [takenImageData base64EncodedStringWithOptions:0];
}
if ([options[@"exif"] boolValue]) {
int imageRotation;
switch (takenImage.imageOrientation) {
@ -464,6 +465,11 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[RNImageUtils updatePhotoMetadata:imageSampleBuffer withAdditionalData:@{ @"Orientation": @(imageRotation) } inResponse:response]; // TODO
}
response[@"pictureOrientation"] = @([self.orientation integerValue]);
response[@"deviceOrientation"] = @([self.deviceOrientation integerValue]);
self.orientation = nil;
self.deviceOrientation = nil;
if (useFastMode) {
[self onPictureSaved:@{@"data": response, @"id": options[@"id"]}];
} else {
@ -477,20 +483,23 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
- (void)recordWithOrientation:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject{
[self.sensorOrientationChecker getDeviceOrientationWithBlock:^(UIInterfaceOrientation orientation) {
NSMutableDictionary *tmpOptions = [options mutableCopy];
tmpOptions[@"orientation"]=[NSNumber numberWithInteger:[self.sensorOrientationChecker convertToAVCaptureVideoOrientation: orientation]];
if ([tmpOptions valueForKey:@"orientation"] == nil) {
tmpOptions[@"orientation"] = [NSNumber numberWithInteger:[self.sensorOrientationChecker convertToAVCaptureVideoOrientation: orientation]];
}
self.deviceOrientation = [NSNumber numberWithInteger:orientation];
self.orientation = [NSNumber numberWithInteger:[tmpOptions[@"orientation"] integerValue]];
[self record:tmpOptions resolve:resolve reject:reject];
}];
}
- (void)record:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
int orientation;
if ([options[@"orientation"] integerValue]) {
orientation = [options[@"orientation"] integerValue];
} else {
if (!self.deviceOrientation) {
[self recordWithOrientation:options resolve:resolve reject:reject];
return;
}
NSInteger orientation = [options[@"orientation"] integerValue];
if (_movieFileOutput == nil) {
// At the time of writing AVCaptureMovieFileOutput and AVCaptureVideoDataOutput (> GMVDataOutput)
// cannot coexist on the same AVSession (see: https://stackoverflow.com/a/4986032/1123156).
@ -522,7 +531,9 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
[self updateSessionAudioIsMuted:[options[@"mute"] boolValue]];
if ([options valueForKey:@"mute"] != nil) {
[self updateSessionAudioIsMuted:[options[@"mute"] boolValue]];
}
AVCaptureConnection *connection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if (self.videoStabilizationMode != 0) {
@ -959,21 +970,34 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
if (success && self.videoRecordedResolve != nil) {
if (@available(iOS 10, *)) {
AVVideoCodecType videoCodec = self.videoCodecType;
if (videoCodec == nil) {
videoCodec = [self.movieFileOutput.availableVideoCodecTypes firstObject];
}
if ([connections[0] isVideoMirrored]) {
[self mirrorVideo:outputFileURL completion:^(NSURL *mirroredURL) {
self.videoRecordedResolve(@{ @"uri": mirroredURL.absoluteString, @"codec":videoCodec });
}];
} else {
self.videoRecordedResolve(@{ @"uri": outputFileURL.absoluteString, @"codec":videoCodec });
}
} else {
self.videoRecordedResolve(@{ @"uri": outputFileURL.absoluteString });
}
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
void (^resolveBlock)(void) = ^() {
self.videoRecordedResolve(result);
};
result[@"uri"] = outputFileURL.absoluteString;
result[@"videoOrientation"] = @([self.orientation integerValue]);
result[@"deviceOrientation"] = @([self.deviceOrientation integerValue]);
if (@available(iOS 10, *)) {
AVVideoCodecType videoCodec = self.videoCodecType;
if (videoCodec == nil) {
videoCodec = [self.movieFileOutput.availableVideoCodecTypes firstObject];
}
result[@"codec"] = videoCodec;
if ([connections[0] isVideoMirrored]) {
[self mirrorVideo:outputFileURL completion:^(NSURL *mirroredURL) {
result[@"uri"] = mirroredURL.absoluteString;
resolveBlock();
}];
return;
}
}
resolveBlock();
} else if (self.videoRecordedReject != nil) {
self.videoRecordedReject(@"E_RECORDING_FAILED", @"An error occurred while recording a video.", error);
}
@ -986,6 +1010,8 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
self.videoRecordedResolve = nil;
self.videoRecordedReject = nil;
self.videoCodecType = nil;
self.deviceOrientation = nil;
self.orientation = nil;
#if __has_include(<GoogleMobileVision/GoogleMobileVision.h>)
[self cleanupMovieFileCapture];

View File

@ -31,10 +31,11 @@ const styles = StyleSheet.create({
});
type Orientation = 'auto' | 'landscapeLeft' | 'landscapeRight' | 'portrait' | 'portraitUpsideDown';
type OrientationNumber = 1 | 2 | 3 | 4;
type PictureOptions = {
quality?: number,
orientation?: Orientation,
orientation?: Orientation | OrientationNumber,
base64?: boolean,
mirrorImage?: boolean,
exif?: boolean,
@ -164,6 +165,13 @@ export default class Camera extends React.Component<PropsType, StateType> {
FaceDetection: CameraManager.FaceDetection,
CameraStatus,
VideoStabilization: CameraManager.VideoStabilization,
Orientation: {
auto: 'auto',
landscapeLeft: 'landscapeLeft',
landscapeRight: 'landscapeRight',
portrait: 'portrait',
portraitUpsideDown: 'portraitUpsideDown',
},
};
// Values under keys from this object will be transformed to native options
@ -255,6 +263,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
_cameraHandle: ?number;
_lastEvents: { [string]: string };
_lastEventsTimes: { [string]: Date };
_isMounted: boolean;
constructor(props: PropsType) {
super(props);
@ -274,8 +283,16 @@ export default class Camera extends React.Component<PropsType, StateType> {
if (!options.quality) {
options.quality = 1;
}
if (options.orientation) {
options.orientation = CameraManager.Orientation[options.orientation];
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 (options.pauseAfterCapture === undefined) {
@ -294,6 +311,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
}
getAvailablePictureSizes = async (): string[] => {
//$FlowFixMe
return await CameraManager.getAvailablePictureSizes(this.props.ratio, this._cameraHandle);
};
@ -303,8 +321,15 @@ export default class Camera extends React.Component<PropsType, StateType> {
} else if (typeof options.quality === 'string') {
options.quality = Camera.Constants.VideoQuality[options.quality];
}
if (typeof options.orientation === 'string') {
options.orientation = CameraManager.Orientation[options.orientation];
if (options.orientation) {
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.`);
}
}
}
return await CameraManager.record(options, this._cameraHandle);
}

15
types/index.d.ts vendored
View File

@ -20,6 +20,7 @@ type Orientation = Readonly<{
portrait: any;
portraitUpsideDown: any;
}>;
type OrientationNumber = 1 | 2 | 3 | 4;
type AutoFocus = Readonly<{ on: any; off: any }>;
type FlashMode = Readonly<{ on: any; off: any; torch: any; auto: any }>;
type CameraType = Readonly<{ front: any; back: any }>;
@ -110,6 +111,13 @@ export interface Constants {
BarcodeType: GoogleVisionBarcodeType;
BarcodeMode: GoogleVisionBarcodeMode;
};
Orientation: {
auto: 'auto';
landscapeLeft: 'landscapeLeft';
landscapeRight: 'landscapeRight';
portrait: 'portrait';
portraitUpsideDown: 'portraitUpsideDown';
};
}
export interface RNCameraProps {
@ -224,7 +232,7 @@ interface TrackedTextFeature {
interface TakePictureOptions {
quality?: number;
orientation?: keyof Orientation;
orientation?: keyof Orientation | OrientationNumber;
base64?: boolean;
exif?: boolean;
width?: number;
@ -245,10 +253,13 @@ interface TakePictureResponse {
uri: string;
base64?: string;
exif?: { [name: string]: any };
pictureOrientation: number;
deviceOrientation: number;
}
interface RecordOptions {
quality?: keyof VideoQuality;
orientation?: keyof Orientation | OrientationNumber;
maxDuration?: number;
maxFileSize?: number;
mute?: boolean;
@ -262,6 +273,8 @@ interface RecordOptions {
interface RecordResponse {
/** Path to the video saved on your app's cache directory. */
uri: string;
videoOrientation: number;
deviceOrientation: number;
/** iOS only */
codec: VideoCodec[keyof VideoCodec];
}