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:
parent
26300870ad
commit
401c485db3
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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.");
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
15
types/index.d.ts
vendored
@ -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];
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user