#import "RCTCameraManager.h" #import "RCTCamera.h" #import "RCTBridge.h" #import "RCTEventDispatcher.h" #import "RCTUtils.h" #import "RCTLog.h" #import "UIView+React.h" #import "NSMutableDictionary+ImageMetadata.m" #import #import #import #import "RCTSensorOrientationChecker.h" @interface RCTCameraManager () @property (strong, nonatomic) RCTSensorOrientationChecker * sensorOrientationChecker; @end @implementation RCTCameraManager RCT_EXPORT_MODULE(); - (UIView *)viewWithProps:(__unused NSDictionary *)props { self.presetCamera = ((NSNumber *)props[@"type"]).integerValue; return [self view]; } - (UIView *)view { self.session = [AVCaptureSession new]; self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session]; self.previewLayer.needsDisplayOnBoundsChange = YES; return [[RCTCamera alloc] initWithManager:self bridge:self.bridge]; } - (NSDictionary *)constantsToExport { return @{ @"Aspect": @{ @"stretch": @(RCTCameraAspectStretch), @"fit": @(RCTCameraAspectFit), @"fill": @(RCTCameraAspectFill) }, @"BarCodeType": @{ @"upce": AVMetadataObjectTypeUPCECode, @"code39": AVMetadataObjectTypeCode39Code, @"code39mod43": AVMetadataObjectTypeCode39Mod43Code, @"ean13": AVMetadataObjectTypeEAN13Code, @"ean8": AVMetadataObjectTypeEAN8Code, @"code93": AVMetadataObjectTypeCode93Code, @"code138": AVMetadataObjectTypeCode128Code, @"pdf417": AVMetadataObjectTypePDF417Code, @"qr": AVMetadataObjectTypeQRCode, @"aztec": AVMetadataObjectTypeAztecCode #ifdef AVMetadataObjectTypeInterleaved2of5Code ,@"interleaved2of5": AVMetadataObjectTypeInterleaved2of5Code # endif #ifdef AVMetadataObjectTypeITF14Code ,@"itf14": AVMetadataObjectTypeITF14Code # endif #ifdef AVMetadataObjectTypeDataMatrixCode ,@"datamatrix": AVMetadataObjectTypeDataMatrixCode # endif }, @"Type": @{ @"front": @(RCTCameraTypeFront), @"back": @(RCTCameraTypeBack) }, @"CaptureMode": @{ @"still": @(RCTCameraCaptureModeStill), @"video": @(RCTCameraCaptureModeVideo) }, @"CaptureQuality": @{ @"low": AVCaptureSessionPresetLow, @"AVCaptureSessionPresetLow": AVCaptureSessionPresetLow, @"medium": AVCaptureSessionPresetMedium, @"AVCaptureSessionPresetMedium": AVCaptureSessionPresetMedium, @"high": AVCaptureSessionPresetHigh, @"AVCaptureSessionPresetHigh": AVCaptureSessionPresetHigh, @"photo": AVCaptureSessionPresetPhoto, @"AVCaptureSessionPresetPhoto": AVCaptureSessionPresetPhoto }, @"CaptureTarget": @{ @"memory": @(RCTCameraCaptureTargetMemory), @"disk": @(RCTCameraCaptureTargetDisk), @"temp": @(RCTCameraCaptureTargetTemp), @"cameraRoll": @(RCTCameraCaptureTargetCameraRoll) }, @"Orientation": @{ @"auto": @(RCTCameraOrientationAuto), @"landscapeLeft": @(RCTCameraOrientationLandscapeLeft), @"landscapeRight": @(RCTCameraOrientationLandscapeRight), @"portrait": @(RCTCameraOrientationPortrait), @"portraitUpsideDown": @(RCTCameraOrientationPortraitUpsideDown) }, @"FlashMode": @{ @"off": @(RCTCameraFlashModeOff), @"on": @(RCTCameraFlashModeOn), @"auto": @(RCTCameraFlashModeAuto) }, @"TorchMode": @{ @"off": @(RCTCameraTorchModeOff), @"on": @(RCTCameraTorchModeOn), @"auto": @(RCTCameraTorchModeAuto) } }; } RCT_EXPORT_VIEW_PROPERTY(orientation, NSInteger); RCT_EXPORT_VIEW_PROPERTY(defaultOnFocusComponent, BOOL); RCT_EXPORT_VIEW_PROPERTY(onFocusChanged, BOOL); RCT_EXPORT_VIEW_PROPERTY(onZoomChanged, BOOL); RCT_CUSTOM_VIEW_PROPERTY(aspect, NSInteger, RCTCamera) { NSInteger aspect = [RCTConvert NSInteger:json]; NSString *aspectString; switch (aspect) { default: case RCTCameraAspectFill: aspectString = AVLayerVideoGravityResizeAspectFill; break; case RCTCameraAspectFit: aspectString = AVLayerVideoGravityResizeAspect; break; case RCTCameraAspectStretch: aspectString = AVLayerVideoGravityResize; break; } self.previewLayer.videoGravity = aspectString; } RCT_CUSTOM_VIEW_PROPERTY(type, NSInteger, RCTCamera) { NSInteger type = [RCTConvert NSInteger:json]; self.presetCamera = type; if (self.session.isRunning) { dispatch_async(self.sessionQueue, ^{ AVCaptureDevice *currentCaptureDevice = [self.videoCaptureDeviceInput device]; AVCaptureDevicePosition position = (AVCaptureDevicePosition)type; AVCaptureDevice *captureDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:(AVCaptureDevicePosition)position]; if (captureDevice == nil) { return; } self.presetCamera = type; NSError *error = nil; AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; if (error || captureDeviceInput == nil) { NSLog(@"%@", error); return; } [self.session beginConfiguration]; [self.session removeInput:self.videoCaptureDeviceInput]; if ([self.session canAddInput:captureDeviceInput]) { [self.session addInput:captureDeviceInput]; [NSNotificationCenter.defaultCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:currentCaptureDevice]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice]; self.videoCaptureDeviceInput = captureDeviceInput; } else { [self.session addInput:self.videoCaptureDeviceInput]; } [self.session commitConfiguration]; }); } [self initializeCaptureSessionInput:AVMediaTypeVideo]; } RCT_CUSTOM_VIEW_PROPERTY(flashMode, NSInteger, RCTCamera) { AVCaptureDevice *device = [self.videoCaptureDeviceInput device]; NSError *error = nil; NSInteger *flashMode = [RCTConvert NSInteger:json]; if (![device hasFlash]) return; if (![device lockForConfiguration:&error]) { NSLog(@"%@", error); return; } if (device.hasFlash && [device isFlashModeSupported:flashMode]) { NSError *error = nil; if ([device lockForConfiguration:&error]) { [device setFlashMode:flashMode]; [device unlockForConfiguration]; } else { NSLog(@"%@", error); } } [device unlockForConfiguration]; } RCT_CUSTOM_VIEW_PROPERTY(torchMode, NSInteger, RCTCamera) { dispatch_async(self.sessionQueue, ^{ NSInteger *torchMode = [RCTConvert NSInteger:json]; AVCaptureDevice *device = [self.videoCaptureDeviceInput device]; NSError *error = nil; if (![device hasTorch]) return; if (![device lockForConfiguration:&error]) { NSLog(@"%@", error); return; } [device setTorchMode: torchMode]; [device unlockForConfiguration]; }); } RCT_CUSTOM_VIEW_PROPERTY(keepAwake, BOOL, RCTCamera) { BOOL enabled = [RCTConvert BOOL:json]; [UIApplication sharedApplication].idleTimerDisabled = enabled; } RCT_CUSTOM_VIEW_PROPERTY(mirrorImage, BOOL, RCTCamera) { self.mirrorImage = [RCTConvert BOOL:json]; } RCT_CUSTOM_VIEW_PROPERTY(barCodeTypes, NSArray, RCTCamera) { self.barCodeTypes = [RCTConvert NSArray:json]; } RCT_CUSTOM_VIEW_PROPERTY(captureAudio, BOOL, RCTCamera) { BOOL captureAudio = [RCTConvert BOOL:json]; if (captureAudio) { RCTLog(@"capturing audio"); [self initializeCaptureSessionInput:AVMediaTypeAudio]; } } - (NSArray *)customDirectEventTypes { return @[ @"focusChanged", @"zoomChanged", ]; } - (id)init { if ((self = [super init])) { self.mirrorImage = false; self.sessionQueue = dispatch_queue_create("cameraManagerQueue", DISPATCH_QUEUE_SERIAL); self.sensorOrientationChecker = [RCTSensorOrientationChecker new]; } return self; } RCT_EXPORT_METHOD(checkDeviceAuthorizationStatus:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { __block NSString *mediaType = AVMediaTypeVideo; [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { if (!granted) { resolve(@(granted)); } else { mediaType = AVMediaTypeAudio; [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { resolve(@(granted)); }]; } }]; } RCT_EXPORT_METHOD(checkVideoAuthorizationStatus:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { __block NSString *mediaType = AVMediaTypeVideo; [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { resolve(@(granted)); }]; } RCT_EXPORT_METHOD(checkAudioAuthorizationStatus:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { __block NSString *mediaType = AVMediaTypeAudio; [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { resolve(@(granted)); }]; } RCT_EXPORT_METHOD(changeOrientation:(NSInteger)orientation) { [self setOrientation:orientation]; } RCT_EXPORT_METHOD(capture:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { NSInteger captureMode = [[options valueForKey:@"mode"] intValue]; NSInteger captureTarget = [[options valueForKey:@"target"] intValue]; [self setCaptureQuality:[options valueForKey:@"quality"]]; if (captureMode == RCTCameraCaptureModeStill) { [self captureStill:captureTarget options:options resolve:resolve reject:reject]; } else if (captureMode == RCTCameraCaptureModeVideo) { [self captureVideo:captureTarget options:options resolve:resolve reject:reject]; } } RCT_EXPORT_METHOD(stopCapture) { if (self.movieFileOutput.recording) { [self.movieFileOutput stopRecording]; } } RCT_EXPORT_METHOD(getFOV:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { NSArray *devices = [AVCaptureDevice devices]; AVCaptureDevice *frontCamera; AVCaptureDevice *backCamera; double frontFov = 0.0; double backFov = 0.0; for (AVCaptureDevice *device in devices) { NSLog(@"Device name: %@", [device localizedName]); if ([device hasMediaType:AVMediaTypeVideo]) { if ([device position] == AVCaptureDevicePositionBack) { NSLog(@"Device position : back"); backCamera = device; backFov = backCamera.activeFormat.videoFieldOfView; } else { NSLog(@"Device position : front"); frontCamera = device; frontFov = frontCamera.activeFormat.videoFieldOfView; } } } resolve(@{ [NSNumber numberWithInt:RCTCameraTypeBack]: [NSNumber numberWithDouble: backFov], [NSNumber numberWithInt:RCTCameraTypeFront]: [NSNumber numberWithDouble: frontFov] }); } RCT_EXPORT_METHOD(hasFlash:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { AVCaptureDevice *device = [self.videoCaptureDeviceInput device]; resolve(@(device.hasFlash)); } - (void)startSession { #if TARGET_IPHONE_SIMULATOR return; #endif dispatch_async(self.sessionQueue, ^{ if (self.presetCamera == AVCaptureDevicePositionUnspecified) { self.presetCamera = AVCaptureDevicePositionBack; } AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; if ([self.session canAddOutput:stillImageOutput]) { stillImageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG}; [self.session addOutput:stillImageOutput]; self.stillImageOutput = stillImageOutput; } AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; if ([self.session canAddOutput:movieFileOutput]) { [self.session addOutput:movieFileOutput]; self.movieFileOutput = movieFileOutput; } AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init]; if ([self.session canAddOutput:metadataOutput]) { [metadataOutput setMetadataObjectsDelegate:self queue:self.sessionQueue]; [self.session addOutput:metadataOutput]; [metadataOutput setMetadataObjectTypes:self.barCodeTypes]; self.metadataOutput = metadataOutput; } __weak RCTCameraManager *weakSelf = self; [self setRuntimeErrorHandlingObserver:[NSNotificationCenter.defaultCenter addObserverForName:AVCaptureSessionRuntimeErrorNotification object:self.session queue:nil usingBlock:^(NSNotification *note) { RCTCameraManager *strongSelf = weakSelf; dispatch_async(strongSelf.sessionQueue, ^{ // Manually restarting the session since it must have been stopped due to an error. [strongSelf.session startRunning]; }); }]]; [self.session startRunning]; }); } - (void)stopSession { #if TARGET_IPHONE_SIMULATOR return; #endif dispatch_async(self.sessionQueue, ^{ [self.previewLayer removeFromSuperlayer]; [self.session commitConfiguration]; [self.session stopRunning]; for(AVCaptureInput *input in self.session.inputs) { [self.session removeInput:input]; } for(AVCaptureOutput *output in self.session.outputs) { [self.session removeOutput:output]; } }); } - (void)initializeCaptureSessionInput:(NSString *)type { dispatch_async(self.sessionQueue, ^{ if (type == AVMediaTypeAudio) { for (AVCaptureDeviceInput* input in [self.session inputs]) { if ([input.device hasMediaType:AVMediaTypeAudio]) { // If an audio input has been configured we don't need to set it up again return; } } } [self.session beginConfiguration]; NSError *error = nil; AVCaptureDevice *captureDevice; if (type == AVMediaTypeAudio) { captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; } else if (type == AVMediaTypeVideo) { captureDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:self.presetCamera]; } if (captureDevice == nil) { return; } AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; if (error || captureDeviceInput == nil) { NSLog(@"%@", error); return; } if (type == AVMediaTypeVideo) { [self.session removeInput:self.videoCaptureDeviceInput]; } if ([self.session canAddInput:captureDeviceInput]) { [self.session addInput:captureDeviceInput]; if (type == AVMediaTypeAudio) { self.audioCaptureDeviceInput = captureDeviceInput; } else if (type == AVMediaTypeVideo) { self.videoCaptureDeviceInput = captureDeviceInput; } [self.metadataOutput setMetadataObjectTypes:self.metadataOutput.availableMetadataObjectTypes]; } [self.session commitConfiguration]; }); } - (void)captureStill:(NSInteger)target options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { AVCaptureVideoOrientation orientation = options[@"orientation"] != nil ? [options[@"orientation"] integerValue] : self.orientation; if (orientation == RCTCameraOrientationAuto) { #if TARGET_IPHONE_SIMULATOR [self captureStill:target options:options orientation:self.previewLayer.connection.videoOrientation resolve:resolve reject:reject]; #else [self.sensorOrientationChecker getDeviceOrientationWithBlock:^(UIInterfaceOrientation orientation) { [self captureStill:target options:options orientation:[self.sensorOrientationChecker convertToAVCaptureVideoOrientation: orientation] resolve:resolve reject:reject]; }]; #endif } else { [self captureStill:target options:options orientation:orientation resolve:resolve reject:reject]; } } - (void)captureStill:(NSInteger)target options:(NSDictionary *)options orientation:(AVCaptureVideoOrientation)orientation resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { dispatch_async(self.sessionQueue, ^{ #if TARGET_IPHONE_SIMULATOR CGSize size = CGSizeMake(720, 1280); UIGraphicsBeginImageContextWithOptions(size, YES, 0); // Thanks https://gist.github.com/kylefox/1689973 CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0 CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1]; [color setFill]; UIRectFill(CGRectMake(0, 0, size.width, size.height)); NSDate *currentDate = [NSDate date]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"dd.MM.YY HH:mm:ss"]; NSString *text = [dateFormatter stringFromDate:currentDate]; UIFont *font = [UIFont systemFontOfSize:40.0]; NSDictionary *attributes = [NSDictionary dictionaryWithObjects: @[font, [UIColor blackColor]] forKeys: @[NSFontAttributeName, NSForegroundColorAttributeName]]; [text drawAtPoint:CGPointMake(size.width/3, size.height/2) withAttributes:attributes]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData *imageData = UIImageJPEGRepresentation(image, 1.0); [self saveImage:imageData target:target metadata:nil resolve:resolve reject:reject]; #else [[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:orientation]; [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { if (imageDataSampleBuffer) { NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; // Create image source CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL); //get all the metadata in the image NSMutableDictionary *imageMetadata = [(NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL)) mutableCopy]; // create cgimage CGImageRef CGImage; CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL); // Rotate it CGImageRef rotatedCGImage; if ([options objectForKey:@"rotation"]) { float rotation = [[options objectForKey:@"rotation"] floatValue]; rotatedCGImage = [self newCGImageRotatedByAngle:CGImage angle:rotation]; } else { // Get metadata orientation int metadataOrientation = [[imageMetadata objectForKey:(NSString *)kCGImagePropertyOrientation] intValue]; if (metadataOrientation == 6) { rotatedCGImage = [self newCGImageRotatedByAngle:CGImage angle:270]; } else if (metadataOrientation == 1) { rotatedCGImage = [self newCGImageRotatedByAngle:CGImage angle:0]; } else if (metadataOrientation == 3) { rotatedCGImage = [self newCGImageRotatedByAngle:CGImage angle:180]; } else { rotatedCGImage = [self newCGImageRotatedByAngle:CGImage angle:0]; } } CGImageRelease(CGImage); // Erase metadata orientation [imageMetadata removeObjectForKey:(NSString *)kCGImagePropertyOrientation]; // Erase stupid TIFF stuff [imageMetadata removeObjectForKey:(NSString *)kCGImagePropertyTIFFDictionary]; // Add input metadata [imageMetadata mergeMetadata:[options objectForKey:@"metadata"]]; // Create destination thing NSMutableData *rotatedImageData = [NSMutableData data]; CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)rotatedImageData, CGImageSourceGetType(source), 1, NULL); CFRelease(source); // add the image to the destination, reattaching metadata CGImageDestinationAddImage(destination, rotatedCGImage, (CFDictionaryRef) imageMetadata); // And write CGImageDestinationFinalize(destination); CFRelease(destination); [self saveImage:rotatedImageData target:target metadata:imageMetadata resolve:resolve reject:reject]; CGImageRelease(rotatedCGImage); } else { reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); } }]; #endif }); } - (void)saveImage:(NSData*)imageData target:(NSInteger)target metadata:(NSDictionary *)metadata resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { NSString *responseString; if (target == RCTCameraCaptureTargetMemory) { resolve(@{@"data":[imageData base64EncodedStringWithOptions:0]}); return; } else if (target == RCTCameraCaptureTargetDisk) { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths firstObject]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *fullPath = [[documentsDirectory stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]] stringByAppendingPathExtension:@"jpg"]; [fileManager createFileAtPath:fullPath contents:imageData attributes:nil]; responseString = fullPath; } else if (target == RCTCameraCaptureTargetTemp) { NSString *fileName = [[NSProcessInfo processInfo] globallyUniqueString]; NSString *fullPath = [NSString stringWithFormat:@"%@%@.jpg", NSTemporaryDirectory(), fileName]; [imageData writeToFile:fullPath atomically:YES]; responseString = fullPath; } else if (target == RCTCameraCaptureTargetCameraRoll) { [[[ALAssetsLibrary alloc] init] writeImageDataToSavedPhotosAlbum:imageData metadata:metadata completionBlock:^(NSURL* url, NSError* error) { if (error == nil) { resolve(@{@"path":[url absoluteString]}); } else { reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); } }]; return; } resolve(@{@"path":responseString}); } - (CGImageRef)newCGImageRotatedByAngle:(CGImageRef)imgRef angle:(CGFloat)angle { CGFloat angleInRadians = angle * (M_PI / 180); CGFloat width = CGImageGetWidth(imgRef); CGFloat height = CGImageGetHeight(imgRef); CGRect imgRect = CGRectMake(0, 0, width, height); CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians); CGRect rotatedRect = CGRectApplyAffineTransform(imgRect, transform); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef bmContext = CGBitmapContextCreate(NULL, rotatedRect.size.width, rotatedRect.size.height, 8, 0, colorSpace, (CGBitmapInfo) kCGImageAlphaPremultipliedFirst); if (self.mirrorImage) { CGAffineTransform transform = CGAffineTransformMakeTranslation(rotatedRect.size.width, 0.0); transform = CGAffineTransformScale(transform, -1.0, 1.0); CGContextConcatCTM(bmContext, transform); } CGContextSetAllowsAntialiasing(bmContext, TRUE); CGContextSetInterpolationQuality(bmContext, kCGInterpolationNone); CGColorSpaceRelease(colorSpace); CGContextTranslateCTM(bmContext, +(rotatedRect.size.width/2), +(rotatedRect.size.height/2)); CGContextRotateCTM(bmContext, angleInRadians); CGContextTranslateCTM(bmContext, -(rotatedRect.size.width/2), -(rotatedRect.size.height/2)); CGContextDrawImage(bmContext, CGRectMake((rotatedRect.size.width-width)/2.0f, (rotatedRect.size.height-height)/2.0f, width, height), imgRef); CGImageRef rotatedImage = CGBitmapContextCreateImage(bmContext); CFRelease(bmContext); return rotatedImage; } -(void)captureVideo:(NSInteger)target options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { AVCaptureVideoOrientation orientation = options[@"orientation"] != nil ? [options[@"orientation"] integerValue] : self.orientation; if (orientation == RCTCameraOrientationAuto) { [self.sensorOrientationChecker getDeviceOrientationWithBlock:^(UIInterfaceOrientation orientation) { [self captureVideo:target options:options orientation:[self.sensorOrientationChecker convertToAVCaptureVideoOrientation: orientation] resolve:resolve reject:reject]; }]; } else { [self captureVideo:target options:options orientation:orientation resolve:resolve reject:reject]; } } -(void)captureVideo:(NSInteger)target options:(NSDictionary *)options orientation:(AVCaptureVideoOrientation)orientation resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { if (self.movieFileOutput.recording) { reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(@"Already recording")); return; } if ([[options valueForKey:@"audio"] boolValue]) { [self initializeCaptureSessionInput:AVMediaTypeAudio]; } Float64 totalSeconds = [[options valueForKey:@"totalSeconds"] floatValue]; if (totalSeconds > -1) { int32_t preferredTimeScale = [[options valueForKey:@"preferredTimeScale"] intValue]; CMTime maxDuration = CMTimeMakeWithSeconds(totalSeconds, preferredTimeScale); self.movieFileOutput.maxRecordedDuration = maxDuration; } dispatch_async(self.sessionQueue, ^{ [[self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:orientation]; //Create temporary URL to record to NSString *outputPath = [[NSString alloc] initWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"]; NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:outputPath]; NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:outputPath]) { NSError *error; if ([fileManager removeItemAtPath:outputPath error:&error] == NO) { reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } } //Start recording [self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self]; self.videoResolve = resolve; self.videoReject = reject; self.videoTarget = target; }); } - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error { BOOL recordSuccess = YES; if ([error code] != noErr) { // A problem occurred: Find out if the recording was successful. id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey]; if (value) { recordSuccess = [value boolValue]; } } if (!recordSuccess) { self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(@"Error while recording")); return; } AVURLAsset* videoAsAsset = [AVURLAsset URLAssetWithURL:outputFileURL options:nil]; AVAssetTrack* videoTrack = [[videoAsAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; float videoWidth; float videoHeight; CGSize videoSize = [videoTrack naturalSize]; CGAffineTransform txf = [videoTrack preferredTransform]; if ((txf.tx == videoSize.width && txf.ty == videoSize.height) || (txf.tx == 0 && txf.ty == 0)) { // Video recorded in landscape orientation videoWidth = videoSize.width; videoHeight = videoSize.height; } else { // Video recorded in portrait orientation, so have to swap reported width/height videoWidth = videoSize.height; videoHeight = videoSize.width; } NSMutableDictionary *videoInfo = [NSMutableDictionary dictionaryWithDictionary:@{ @"duration":[NSNumber numberWithFloat:CMTimeGetSeconds(videoAsAsset.duration)], @"width":[NSNumber numberWithFloat:videoWidth], @"height":[NSNumber numberWithFloat:videoHeight], @"size":[NSNumber numberWithLongLong:captureOutput.recordedFileSize], }]; if (self.videoTarget == RCTCameraCaptureTargetCameraRoll) { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputFileURL]) { [library writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) { if (error) { self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } [videoInfo setObject:[assetURL absoluteString] forKey:@"path"]; self.videoResolve(videoInfo); }]; } } else if (self.videoTarget == RCTCameraCaptureTargetDisk) { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths firstObject]; NSString *fullPath = [[documentsDirectory stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]] stringByAppendingPathExtension:@"mov"]; NSFileManager * fileManager = [NSFileManager defaultManager]; NSError * error = nil; //moving to destination if (!([fileManager moveItemAtPath:[outputFileURL path] toPath:fullPath error:&error])) { self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } [videoInfo setObject:fullPath forKey:@"path"]; self.videoResolve(videoInfo); } else if (self.videoTarget == RCTCameraCaptureTargetTemp) { NSString *fileName = [[NSProcessInfo processInfo] globallyUniqueString]; NSString *fullPath = [NSString stringWithFormat:@"%@%@.mov", NSTemporaryDirectory(), fileName]; NSFileManager * fileManager = [NSFileManager defaultManager]; NSError * error = nil; //moving to destination if (!([fileManager moveItemAtPath:[outputFileURL path] toPath:fullPath error:&error])) { self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } [videoInfo setObject:fullPath forKey:@"path"]; self.videoResolve(videoInfo); } else { self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(@"Target not supported")); } } - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { for (AVMetadataMachineReadableCodeObject *metadata in metadataObjects) { for (id barcodeType in self.barCodeTypes) { if ([metadata.type isEqualToString:barcodeType]) { // Transform the meta-data coordinates to screen coords AVMetadataMachineReadableCodeObject *transformed = (AVMetadataMachineReadableCodeObject *)[_previewLayer transformedMetadataObjectForMetadataObject:metadata]; NSDictionary *event = @{ @"type": metadata.type, @"data": metadata.stringValue, @"bounds": @{ @"origin": @{ @"x": [NSString stringWithFormat:@"%f", transformed.bounds.origin.x], @"y": [NSString stringWithFormat:@"%f", transformed.bounds.origin.y] }, @"size": @{ @"height": [NSString stringWithFormat:@"%f", transformed.bounds.size.height], @"width": [NSString stringWithFormat:@"%f", transformed.bounds.size.width], } } }; [self.bridge.eventDispatcher sendAppEventWithName:@"CameraBarCodeRead" body:event]; } } } } - (AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position { NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType]; AVCaptureDevice *captureDevice = [devices firstObject]; for (AVCaptureDevice *device in devices) { if ([device position] == position) { captureDevice = device; break; } } return captureDevice; } - (void)subjectAreaDidChange:(NSNotification *)notification { CGPoint devicePoint = CGPointMake(.5, .5); [self focusWithMode:AVCaptureFocusModeContinuousAutoFocus exposeWithMode:AVCaptureExposureModeContinuousAutoExposure atDevicePoint:devicePoint monitorSubjectAreaChange:NO]; } - (void)focusWithMode:(AVCaptureFocusMode)focusMode exposeWithMode:(AVCaptureExposureMode)exposureMode atDevicePoint:(CGPoint)point monitorSubjectAreaChange:(BOOL)monitorSubjectAreaChange { dispatch_async([self sessionQueue], ^{ AVCaptureDevice *device = [[self videoCaptureDeviceInput] device]; NSError *error = nil; if ([device lockForConfiguration:&error]) { if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:focusMode]) { [device setFocusMode:focusMode]; [device setFocusPointOfInterest:point]; } if ([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:exposureMode]) { [device setExposureMode:exposureMode]; [device setExposurePointOfInterest:point]; } [device setSubjectAreaChangeMonitoringEnabled:monitorSubjectAreaChange]; [device unlockForConfiguration]; } else { NSLog(@"%@", error); } }); } - (void)focusAtThePoint:(CGPoint) atPoint; { Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice"); if (captureDeviceClass != nil) { dispatch_async([self sessionQueue], ^{ AVCaptureDevice *device = [[self videoCaptureDeviceInput] device]; if([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { CGRect screenRect = [[UIScreen mainScreen] bounds]; double screenWidth = screenRect.size.width; double screenHeight = screenRect.size.height; double focus_x = atPoint.x/screenWidth; double focus_y = atPoint.y/screenHeight; if([device lockForConfiguration:nil]) { [device setFocusPointOfInterest:CGPointMake(focus_x,focus_y)]; [device setFocusMode:AVCaptureFocusModeAutoFocus]; if ([device isExposureModeSupported:AVCaptureExposureModeAutoExpose]){ [device setExposureMode:AVCaptureExposureModeAutoExpose]; } [device unlockForConfiguration]; } } }); } } - (void)zoom:(CGFloat)velocity reactTag:(NSNumber *)reactTag{ if (isnan(velocity)) { return; } const CGFloat pinchVelocityDividerFactor = 20.0f; // TODO: calibrate or make this component's property NSError *error = nil; AVCaptureDevice *device = [[self videoCaptureDeviceInput] device]; if ([device lockForConfiguration:&error]) { CGFloat zoomFactor = device.videoZoomFactor + atan(velocity / pinchVelocityDividerFactor); if (zoomFactor > device.activeFormat.videoMaxZoomFactor) { zoomFactor = device.activeFormat.videoMaxZoomFactor; } else if (zoomFactor < 1) { zoomFactor = 1.0f; } NSDictionary *event = @{ @"target": reactTag, @"zoomFactor": [NSNumber numberWithDouble:zoomFactor], @"velocity": [NSNumber numberWithDouble:velocity] }; [self.bridge.eventDispatcher sendInputEventWithName:@"zoomChanged" body:event]; device.videoZoomFactor = zoomFactor; [device unlockForConfiguration]; } else { NSLog(@"error: %@", error); } } - (void)setCaptureQuality:(NSString *)quality { if (quality) { [self.session beginConfiguration]; if ([self.session canSetSessionPreset:quality]) { self.session.sessionPreset = quality; } [self.session commitConfiguration]; } } @end