Skip to content
This repository has been archived by the owner on Jun 16, 2023. It is now read-only.

Commit

Permalink
Merge branch 'master' into feature/addVideoOrientationOption
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonErm committed Aug 15, 2018
2 parents 03478f6 + da8e79c commit ee92cab
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 26 deletions.
30 changes: 30 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,33 @@
### 1.2.0-7 (2018-08-09)

##### Build System / Dependencies

* **change-log:** v1.1.5-2 ([e49e35a0](https://github.com/react-native-community/react-native-camera/commit/e49e35a085b1793cc8692d2c1600eb2e14ffbe75))

##### Documentation Changes

* **expo:** explain how to migrate to and from expo camera module ([#1605](https://github.com/react-native-community/react-native-camera/pull/1605)) ([4a9322cb](https://github.com/react-native-community/react-native-camera/commit/4a9322cb8b7d455fc28f7e67a15bff2fd9d7ea3e))

##### New Features

* **preview:**
* add android code ([497a7039](https://github.com/react-native-community/react-native-camera/commit/497a703964e925b6e3e62e39a54a9734a7ed6c40))
* add new props to JS ([9bf9a2e3](https://github.com/react-native-community/react-native-camera/commit/9bf9a2e3162b919d98cab104029250394b2dd3a8))
* add preview methods and more fixes ([b9fb708f](https://github.com/react-native-community/react-native-camera/commit/b9fb708ffc3fd6865191ce6e2bd0a2404a9c657c))

##### Bug Fixes

* **rn-camera:**
* fix codec backwards compat ([91f5bf45](https://github.com/react-native-community/react-native-camera/commit/91f5bf45672a8b83253ed17c3f90eee64b0f07bf))
* fix types, conversions and casts ([83d0618e](https://github.com/react-native-community/react-native-camera/commit/83d0618e988656dfd9a216b85394ceb5f3a05e9b))
* **picture-size:**
* create None default value ([ad87c8e3](https://github.com/react-native-community/react-native-camera/commit/ad87c8e3421f2ff1836674a01cb86deb619cdc4e))
* export method and change default value ([9efb7f14](https://github.com/react-native-community/react-native-camera/commit/9efb7f141f8970ad160c852fa837427a79f3d0dc))

##### Other Changes

* Implement video stabilization mode property for ios ([#1606](https://github.com/react-native-community/react-native-camera/pull/1606)) ([a090faa0](https://github.com/react-native-community/react-native-camera/commit/a090faa09b417afd41af3739ec2b895de9dca6b6))

#### 1.1.5-2 (2018-06-14)

##### Build System / Dependencies
Expand Down
4 changes: 4 additions & 0 deletions docs/RCTCamera.md
Expand Up @@ -256,6 +256,10 @@ By default a <ActivityIndicator> will be displayed while the component is waitin

If set to `true`, the image returned will be mirrored.

#### `mirrorVideo`

If set to `true`, the video returned will be mirrored.

#### `fixOrientation` (_deprecated_)

If set to `true`, the image returned will be rotated to the _right way up_. WARNING: It uses a significant amount of memory and my cause your application to crash if the device cannot provide enough RAM to perform the rotation.
Expand Down
3 changes: 3 additions & 0 deletions docs/RNCamera.md
Expand Up @@ -408,6 +408,9 @@ The promise will be fulfilled with an object with some of the following properti
- `RNCamera.Constants.VideoCodec['HVEC']` (`iOS >= 11`)
- `RNCamera.Constants.VideoCodec['AppleProRes422']` (`iOS >= 11`)
- `RNCamera.Constants.VideoCodec['AppleProRes4444']` (`iOS >= 11`)

- `mirrorVideo` (boolean true or false). Use this with `true` if you want the resulting video to be mirrored (inverted in the vertical axis). If no value is specified `mirrorVideo:false` is used.

- `maxDuration` (float greater than 0). Specifies the maximum duration of the video to be recorded in seconds. If nothing is specified, no time limit will be used.

- `maxFileSize` (int greater than 0). Specifies the maximum file size, in bytes, of the video to be recorded. For 1mb, for example, use 1\*1024\*1024. If nothing is specified, no size limit will be used.
Expand Down
1 change: 1 addition & 0 deletions ios/RCT/RCTCameraManager.h
Expand Up @@ -72,6 +72,7 @@ typedef NS_ENUM(NSInteger, RCTCameraTorchMode) {
@property (nonatomic, assign) NSInteger videoTarget;
@property (nonatomic, assign) NSInteger orientation;
@property (nonatomic, assign) BOOL mirrorImage;
@property (nonatomic, assign) BOOL mirrorVideo;
@property (nonatomic, assign) BOOL cropToPreview;
@property (nonatomic, strong) NSArray* barCodeTypes;
@property (nonatomic, strong) RCTPromiseResolveBlock videoResolve;
Expand Down
10 changes: 9 additions & 1 deletion ios/RCT/RCTCameraManager.m
Expand Up @@ -298,6 +298,10 @@ - (void)setFlashMode {
self.mirrorImage = [RCTConvert BOOL:json];
}

RCT_CUSTOM_VIEW_PROPERTY(mirrorVideo, BOOL, RCTCamera) {
self.mirrorVideo = [RCTConvert BOOL:json];
}

RCT_CUSTOM_VIEW_PROPERTY(cropToPreview, BOOL, RCTCamera) {
self.cropToPreview = [RCTConvert BOOL:json];
}
Expand Down Expand Up @@ -325,6 +329,7 @@ - (NSArray *)customBubblingEventTypes
- (id)init {
if ((self = [super init])) {
self.mirrorImage = false;
self.mirrorVideo = false;

self.sessionQueue = dispatch_queue_create("cameraManagerQueue", DISPATCH_QUEUE_SERIAL);

Expand Down Expand Up @@ -845,7 +850,10 @@ -(void)captureVideo:(NSInteger)target options:(NSDictionary *)options orientatio

dispatch_async(self.sessionQueue, ^{
[[self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:orientation];


if (self.mirrorVideo) {
[[self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoMirrored:YES];
}
//Create temporary URL to record to
NSString *outputPath = [[NSString alloc] initWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"];
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:outputPath];
Expand Down
80 changes: 73 additions & 7 deletions ios/RN/RNCamera.m
Expand Up @@ -172,8 +172,14 @@ - (void)updateFlashMode
{
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
if ([device isTorchModeSupported:AVCaptureTorchModeOff]) {
[device setTorchMode:AVCaptureTorchModeOff];
if (self.flashMode == RNCameraFlashModeOff) {
if ([device isTorchModeSupported:AVCaptureTorchModeOff]) {
[device setTorchMode:AVCaptureTorchModeOff];
}
} else {
if ([device isTorchModeSupported:AVCaptureTorchModeOn]) {
[device setTorchMode:AVCaptureTorchModeOn];
}
}
[device setFlashMode:self.flashMode];
[device unlockForConfiguration];
Expand Down Expand Up @@ -463,13 +469,11 @@ - (void)record:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve r
[self.movieFileOutput setOutputSettings:@{AVVideoCodecKey:videoCodecType} forConnection:connection];
self.videoCodecType = videoCodecType;
} else {
RCTLogWarn(@"%s: Video Codec '%@' is not supported on this device.", __func__, videoCodecType);
RCTLogWarn(@"%s: Setting videoCodec is only supported above iOS version 10.", __func__);
}
} else {
RCTLogWarn(@"%s: Setting videoCodec is only supported above iOS version 10.", __func__);
}
}

dispatch_async(self.sessionQueue, ^{
[self updateFlashMode];
NSString *path = nil;
Expand All @@ -479,6 +483,14 @@ - (void)record:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve r
else {
path = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"Camera"] withExtension:@".mov"];
}

if ([options[@"mirrorVideo"] boolValue]) {
if ([connection isVideoMirroringSupported]) {
[connection setAutomaticallyAdjustsVideoMirroring:NO];
[connection setVideoMirrored:YES];
}
}

NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
[self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
self.videoRecordedResolve = resolve;
Expand Down Expand Up @@ -829,13 +841,23 @@ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToO
if (videoCodec == nil) {
videoCodec = [self.movieFileOutput.availableVideoCodecTypes firstObject];
}
self.videoRecordedResolve(@{ @"uri": outputFileURL.absoluteString, @"codec":videoCodec });
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 });
}
} else if (self.videoRecordedReject != nil) {
self.videoRecordedReject(@"E_RECORDING_FAILED", @"An error occurred while recording a video.", error);
}

}

- (void)cleanupCamera {
self.videoRecordedResolve = nil;
self.videoRecordedReject = nil;
self.videoCodecType = nil;
Expand All @@ -853,6 +875,50 @@ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToO
}
}

- (void)mirrorVideo:(NSURL *)inputURL completion:(void (^)(NSURL* outputUR))completion {
AVAsset* videoAsset = [AVAsset assetWithURL:inputURL];
AVAssetTrack* clipVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];

AVMutableComposition* composition = [[AVMutableComposition alloc] init];
[composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];

AVMutableVideoComposition* videoComposition = [[AVMutableVideoComposition alloc] init];
videoComposition.renderSize = CGSizeMake(clipVideoTrack.naturalSize.height, clipVideoTrack.naturalSize.width);
videoComposition.frameDuration = CMTimeMake(1, 30);

AVMutableVideoCompositionLayerInstruction* transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:clipVideoTrack];

AVMutableVideoCompositionInstruction* instruction = [[AVMutableVideoCompositionInstruction alloc] init];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60, 30));

CGAffineTransform transform = CGAffineTransformMakeScale(-1.0, 1.0);
transform = CGAffineTransformTranslate(transform, -clipVideoTrack.naturalSize.width, 0);
transform = CGAffineTransformRotate(transform, M_PI/2.0);
transform = CGAffineTransformTranslate(transform, 0.0, -clipVideoTrack.naturalSize.width);

[transformer setTransform:transform atTime:kCMTimeZero];

[instruction setLayerInstructions:@[transformer]];
[videoComposition setInstructions:@[instruction]];

// Export
AVAssetExportSession* exportSession = [AVAssetExportSession exportSessionWithAsset:videoAsset presetName:AVAssetExportPreset640x480];
NSString* filePath = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingString:@"CameraFlip"] withExtension:@".mp4"];
NSURL* outputURL = [NSURL fileURLWithPath:filePath];
[exportSession setOutputURL:outputURL];
[exportSession setOutputFileType:AVFileTypeMPEG4];
[exportSession setVideoComposition:videoComposition];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(outputURL);
});
} else {
NSLog(@"Export failed %@", exportSession.error);
}
}];
}

# pragma mark - Face detector

- (id)createFaceDetectorManager
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "react-native-camera",
"description": "A Camera component for React Native. Also reads barcodes.",
"version": "1.1.5",
"version": "1.2.0",
"author": "Lochlan Wansbrough <lochie@live.com> (http://lwansbrough.com)",
"collective": {
"type": "opencollective",
Expand Down
3 changes: 3 additions & 0 deletions src/Camera.js
Expand Up @@ -115,6 +115,7 @@ export default class Camera extends Component {
onFocusChanged: PropTypes.func,
onZoomChanged: PropTypes.func,
mirrorImage: PropTypes.bool,
mirrorVideo: PropTypes.bool,
fixOrientation: PropTypes.bool,
barCodeTypes: PropTypes.array,
orientation: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
Expand Down Expand Up @@ -142,6 +143,7 @@ export default class Camera extends Component {
playSoundOnCapture: true,
torchMode: CameraManager.TorchMode.off,
mirrorImage: false,
mirrorVideo: false,
cropToPreview: false,
clearWindowBackground: false,
barCodeTypes: Object.values(CameraManager.BarCodeType),
Expand Down Expand Up @@ -305,6 +307,7 @@ export default class Camera extends Component {
title: '',
description: '',
mirrorImage: props.mirrorImage,
mirrorVideo: props.mirrorVideo,
fixOrientation: props.fixOrientation,
cropToPreview: props.cropToPreview,
...options,
Expand Down
4 changes: 3 additions & 1 deletion src/RNCamera.js
Expand Up @@ -70,7 +70,7 @@ type RecordingOptions = {
quality?: number | string,
codec?: string,
mute?: boolean,
path?: string
path?: string,
};

type EventCallbackArgumentsType = {
Expand Down Expand Up @@ -205,6 +205,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
playSoundOnCapture: PropTypes.bool,
videoStabilizationMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
pictureSize: PropTypes.string,
mirrorVideo: PropTypes.bool,
};

static defaultProps: Object = {
Expand Down Expand Up @@ -238,6 +239,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
playSoundOnCapture: false,
pictureSize: 'None',
videoStabilizationMode: 0,
mirrorVideo: false,
};

_cameraRef: ?Object;
Expand Down
26 changes: 11 additions & 15 deletions src/handlePermissions.js
Expand Up @@ -6,22 +6,18 @@ export const requestPermissions = async (hasVideoAndAudio, CameraManager, permis
? CameraManager.checkDeviceAuthorizationStatus
: CameraManager.checkVideoAuthorizationStatus;

if (check) {
const isAuthorized = await check();
return isAuthorized;
}
if (check)
return await check();
} else if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
title: permissionDialogTitle,
message: permissionDialogMessage,
});

// On devices before SDK version 23, the permissions are automatically granted if they appear in the manifest,
// so check and request should always be true.
// https://github.com/facebook/react-native-website/blob/master/docs/permissionsandroid.md
const isAuthorized = granted === PermissionsAndroid.RESULTS.GRANTED || granted === true;

return isAuthorized;
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;
}
1 change: 1 addition & 0 deletions types/index.d.ts
Expand Up @@ -198,6 +198,7 @@ interface RecordOptions {
maxDuration?: number;
maxFileSize?: number;
mute?: boolean;
mirrorVideo?: boolean;

/** iOS only */
codec?: keyof VideoCodec | VideoCodec[keyof VideoCodec];
Expand Down

0 comments on commit ee92cab

Please sign in to comment.