// Created by Tencent on 2023/06/09. // Copyright © 2023 Tencent. All rights reserved. #import "TUICameraViewController.h" #import #import #import #import #import "TUICameraView.h" #import "TUICaptureImagePreviewController.h" #import "TUICaptureVideoPreviewViewController.h" #import #import #import "TUICameraManager.h" #import "TUICaptureTimer.h" #import "TUIMotionManager.h" #import "TUIMovieManager.h" @interface TUICameraViewController () { AVCaptureSession *_session; AVCaptureDeviceInput *_deviceInput; AVCaptureConnection *_videoConnection; AVCaptureConnection *_audioConnection; AVCaptureVideoDataOutput *_videoOutput; AVCaptureStillImageOutput *_imageOutput; BOOL _recording; } @property(nonatomic, strong) TUICameraView *cameraView; @property(nonatomic, strong) TUIMovieManager *movieManager; @property(nonatomic, strong) TUICameraManager *cameraManager; @property(nonatomic, strong) TUIMotionManager *motionManager; @property(nonatomic, strong) AVCaptureDevice *activeCamera; @property(nonatomic, strong) AVCaptureDevice *inactiveCamera; @property(nonatomic) BOOL isFirstShow; @property(nonatomic) BOOL lastPageBarHidden; @end @implementation TUICameraViewController - (instancetype)init { self = [super init]; if (self) { _motionManager = [[TUIMotionManager alloc] init]; _cameraManager = [[TUICameraManager alloc] init]; _type = TUICameraMediaTypePhoto; _aspectRatio = TUICameraViewAspectRatio16x9; _videoMaximumDuration = 15.0; _videoMinimumDuration = 3.0; _isFirstShow = YES; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.cameraView = [[TUICameraView alloc] initWithFrame:self.view.bounds]; self.cameraView.type = self.type; self.cameraView.aspectRatio = self.aspectRatio; self.cameraView.delegate = self; self.cameraView.maxVideoCaptureTimeLimit = self.videoMaximumDuration; [self.view addSubview:self.cameraView]; NSError *error; [self setupSession:&error]; if (!error) { [self.cameraView.previewView setCaptureSessionsion:_session]; [self startCaptureSession]; } else { // NSAssert1(NO, @"Camera Initialize Failed : %@", error.localizedDescription); // [self showErrorStr:error.localizedDescription]; } } - (void)dealloc { [self stopCaptureSession]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (self.isFirstShow) { self.isFirstShow = NO; self.lastPageBarHidden = self.navigationController.navigationBarHidden; } self.navigationController.navigationBarHidden = YES; } - (void)willMoveToParentViewController:(UIViewController *)parent { [super willMoveToParentViewController:parent]; if (!parent) { self.navigationController.navigationBarHidden = self.lastPageBarHidden; } } #pragma mark - - Input Device - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *device in devices) { if (device.position == position) { return device; } } return nil; } - (AVCaptureDevice *)activeCamera { return _deviceInput.device; } - (AVCaptureDevice *)inactiveCamera { AVCaptureDevice *device = nil; if ([[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count] > 1) { if ([self activeCamera].position == AVCaptureDevicePositionBack) { device = [self cameraWithPosition:AVCaptureDevicePositionFront]; } else { device = [self cameraWithPosition:AVCaptureDevicePositionBack]; } } return device; } #pragma mark - - Configuration - (void)setupSession:(NSError **)error { _session = [[AVCaptureSession alloc] init]; _session.sessionPreset = AVCaptureSessionPresetHigh; [self setupSessionInputs:error]; [self setupSessionOutputs:error]; } - (void)setupSessionInputs:(NSError **)error { AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error]; if (videoInput) { if ([_session canAddInput:videoInput]) { [_session addInput:videoInput]; } } _deviceInput = videoInput; if (_type == TUICameraMediaTypeVideo) { AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; AVCaptureDeviceInput *audioIn = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error]; if ([_session canAddInput:audioIn]) { [_session addInput:audioIn]; } } } - (void)setupSessionOutputs:(NSError **)error { dispatch_queue_t captureQueue = dispatch_queue_create("com.tui.captureQueue", DISPATCH_QUEUE_SERIAL); AVCaptureVideoDataOutput *videoOut = [[AVCaptureVideoDataOutput alloc] init]; [videoOut setAlwaysDiscardsLateVideoFrames:YES]; [videoOut setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]}]; [videoOut setSampleBufferDelegate:self queue:captureQueue]; if ([_session canAddOutput:videoOut]) { [_session addOutput:videoOut]; } _videoOutput = videoOut; _videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo]; if (_type == TUICameraMediaTypeVideo) { AVCaptureAudioDataOutput *audioOut = [[AVCaptureAudioDataOutput alloc] init]; [audioOut setSampleBufferDelegate:self queue:captureQueue]; if ([_session canAddOutput:audioOut]) { [_session addOutput:audioOut]; } _audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio]; } AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init]; imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG}; if ([_session canAddOutput:imageOutput]) { [_session addOutput:imageOutput]; } _imageOutput = imageOutput; } #pragma mark - - Session Control - (void)startCaptureSession { if (!_session.isRunning) { [_session startRunning]; } } - (void)stopCaptureSession { if (_session.isRunning) { [_session stopRunning]; } } #pragma mark - - Camera Operation - (void)zoomAction:(TUICameraView *)cameraView factor:(CGFloat)factor { NSError *error = [_cameraManager zoom:[self activeCamera] factor:factor]; if (error) NSLog(@"%@", error); } - (void)focusAction:(TUICameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError *))handle { NSError *error = [_cameraManager focus:[self activeCamera] point:point]; handle(error); NSLog(@"%f", [self activeCamera].activeFormat.videoMaxZoomFactor); } - (void)exposAction:(TUICameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError *))handle { NSError *error = [_cameraManager expose:[self activeCamera] point:point]; handle(error); } - (void)autoFocusAndExposureAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle { NSError *error = [_cameraManager resetFocusAndExposure:[self activeCamera]]; handle(error); } - (void)flashLightAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle { BOOL on = [_cameraManager flashMode:[self activeCamera]] == AVCaptureFlashModeOn; AVCaptureFlashMode mode = on ? AVCaptureFlashModeOff : AVCaptureFlashModeOn; NSError *error = [_cameraManager changeFlash:[self activeCamera] mode:mode]; handle(error); } - (void)torchLightAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle { BOOL on = [_cameraManager torchMode:[self activeCamera]] == AVCaptureTorchModeOn; AVCaptureTorchMode mode = on ? AVCaptureTorchModeOff : AVCaptureTorchModeOn; NSError *error = [_cameraManager changeTorch:[self activeCamera] model:mode]; handle(error); } - (void)swicthCameraAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle { NSError *error; AVCaptureDevice *videoDevice = [self inactiveCamera]; AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; if (videoInput) { CATransition *animation = [CATransition animation]; animation.type = @"oglFlip"; animation.subtype = kCATransitionFromLeft; animation.duration = 0.5; [self.cameraView.previewView.layer addAnimation:animation forKey:@"flip"]; AVCaptureFlashMode mode = [_cameraManager flashMode:[self activeCamera]]; _deviceInput = [_cameraManager switchCamera:_session old:_deviceInput new:videoInput]; _videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo]; [_cameraManager changeFlash:[self activeCamera] mode:mode]; } handle(error); } #pragma mark - - Taking Photo - (void)takePhotoAction:(TUICameraView *)cameraView { AVCaptureConnection *connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo]; if (connection.isVideoOrientationSupported) { connection.videoOrientation = [self currentVideoOrientation]; } [_imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef _Nullable imageDataSampleBuffer, NSError *_Nullable error) { if (error) { [self showErrorStr:error.localizedDescription]; return; } NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; UIImage *image = [[UIImage alloc] initWithData:imageData]; TUICaptureImagePreviewController *vc = [[TUICaptureImagePreviewController alloc] initWithImage:image]; [self.navigationController pushViewController:vc animated:YES]; __weak __typeof(self) weakSelf = self; vc.commitBlock = ^{ __strong __typeof(weakSelf) strongSelf = weakSelf; UIGraphicsBeginImageContext(CGSizeMake(image.size.width, image.size.height)); [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; UIImage *convertToUpImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData *data = UIImageJPEGRepresentation(convertToUpImage, 0.75); [strongSelf.delegate cameraViewController:strongSelf didFinishPickingMediaWithImageData:data]; [strongSelf popViewControllerAnimated:YES]; }; vc.cancelBlock = ^{ __strong __typeof(weakSelf) strongSelf = weakSelf; [strongSelf.navigationController popViewControllerAnimated:YES]; }; }]; } - (void)cancelAction:(TUICameraView *)cameraView { [self.delegate cameraViewControllerDidCancel:self]; [self popViewControllerAnimated:YES]; } - (void)pictureLibAction:(TUICameraView *)cameraView { @weakify(self); [self.delegate cameraViewControllerDidPictureLib:self finishCallback:^{ @strongify(self); [self popViewControllerAnimated:NO]; }]; } #pragma mark - - Record - (void)startRecordVideoAction:(TUICameraView *)cameraView { /** * Recreate each time to avoid Crash caused by unreleased previous information */ _movieManager = [[TUIMovieManager alloc] init]; _recording = YES; _movieManager.currentDevice = [self activeCamera]; _movieManager.currentOrientation = [self currentVideoOrientation]; @weakify(self); [_movieManager start:^(NSError *_Nonnull error) { @strongify(self); @weakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @strongify(self); if (error) [self showErrorStr:error.localizedDescription]; }); }]; } - (void)stopRecordVideoAction:(TUICameraView *)cameraView RecordDuration:(CGFloat)duration { _recording = NO; @weakify(self); [_movieManager stop:^(NSURL *_Nonnull url, NSError *_Nonnull error) { @strongify(self); @weakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @strongify(self); if (duration < self.videoMinimumDuration) { [self showErrorStr:TIMCommonLocalizableString(TUIKitMoreVideoCaptureDurationTip)]; } else if (error) { [self showErrorStr:error.localizedDescription]; } else { TUICaptureVideoPreviewViewController *videoPreviewController = [[TUICaptureVideoPreviewViewController alloc] initWithVideoURL:url]; [self.navigationController pushViewController:videoPreviewController animated:YES]; @weakify(self); videoPreviewController.commitBlock = ^{ @strongify(self); [self.delegate cameraViewController:self didFinishPickingMediaWithVideoURL:url]; [self popViewControllerAnimated:YES]; }; videoPreviewController.cancelBlock = ^{ @strongify(self); [self.navigationController popViewControllerAnimated:YES]; }; } }); }]; } #pragma mark - - AVCaptureVideoDataOutputSampleBufferDelegate - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { if (_recording) { [_movieManager writeData:connection video:_videoConnection audio:_audioConnection buffer:sampleBuffer]; } } #pragma mark - - Others - (AVCaptureVideoOrientation)currentVideoOrientation { AVCaptureVideoOrientation orientation; switch (self.motionManager.deviceOrientation) { case UIDeviceOrientationPortrait: orientation = AVCaptureVideoOrientationPortrait; break; case UIDeviceOrientationLandscapeLeft: orientation = AVCaptureVideoOrientationLandscapeRight; break; case UIDeviceOrientationLandscapeRight: orientation = AVCaptureVideoOrientationLandscapeLeft; break; case UIDeviceOrientationPortraitUpsideDown: orientation = AVCaptureVideoOrientationPortraitUpsideDown; break; default: orientation = AVCaptureVideoOrientationPortrait; break; } return orientation; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)popViewControllerAnimated:(BOOL)animated { NSUInteger index = [self.navigationController.viewControllers indexOfObject:self]; index--; UIViewController *lastVC = nil; if (index > 0 && index < self.navigationController.viewControllers.count) { lastVC = self.navigationController.viewControllers[index]; } self.navigationController.navigationBarHidden = self.lastPageBarHidden; if (lastVC) { [self.navigationController popToViewController:lastVC animated:animated]; } else { [self.navigationController popViewControllerAnimated:animated]; } } - (void)showErrorStr:(NSString *)errStr { [TUITool makeToast:errStr duration:1 position:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0)]; } @end