414 lines
16 KiB
Objective-C
414 lines
16 KiB
Objective-C
|
|
// Created by Tencent on 2023/06/09.
|
|
// Copyright © 2023 Tencent. All rights reserved.
|
|
|
|
#import "TUICameraViewController.h"
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <AssetsLibrary/AssetsLibrary.h>
|
|
#import <CoreMedia/CMMetadata.h>
|
|
#import <Photos/Photos.h>
|
|
|
|
#import "TUICameraView.h"
|
|
#import "TUICaptureImagePreviewController.h"
|
|
#import "TUICaptureVideoPreviewViewController.h"
|
|
|
|
#import <TIMCommon/TIMCommonModel.h>
|
|
#import <TIMCommon/TIMDefine.h>
|
|
#import "TUICameraManager.h"
|
|
#import "TUICaptureTimer.h"
|
|
#import "TUIMotionManager.h"
|
|
#import "TUIMovieManager.h"
|
|
|
|
@interface TUICameraViewController () <AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, TUICameraViewDelegate> {
|
|
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
|