123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- //
- // KSVideoRecordManager.m
- // TeacherDaya
- //
- // Created by Kyle on 2021/8/16.
- // Copyright © 2021 DayaMusic. All rights reserved.
- //
- #import "KSVideoRecordManager.h"
- #import <AVFoundation/AVFoundation.h>
- #import <AssetsLibrary/AssetsLibrary.h>
- #import "TZImageManager.h"
- #import "KSVideoEditor.h"
- @interface KSVideoRecordManager ()<AVCaptureFileOutputRecordingDelegate>
- //会话 负责输入和输出设备之间的数据传递
- @property (nonatomic, strong) AVCaptureSession *captureSession;
- @property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput;
- @property (nonatomic, strong) AVCaptureDeviceInput *audioCaptureDeviceInput;
- // 视频流输出
- @property (nonatomic, strong) AVCaptureMovieFileOutput *captureMovieFileOutput;
- // 相机拍摄预览图层
- @property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
- @property (nonatomic, strong) NSURL *videoFileURL;
- @property (nonatomic, assign) BOOL recordEnable;
- // 是否正在录制
- @property (nonatomic, assign) BOOL isRecording;
- @property (nonatomic, copy) KSVideoRecordCallback callback;
- @property (nonatomic, strong) PHAsset *videoAsset;
- @property (nonatomic, strong) NSString *presentName;
- @property (strong, nonatomic) MBProgressHUD *HUD;
- @property (nonatomic, strong) dispatch_queue_t videoRecordQueue;
- @end
- @implementation KSVideoRecordManager
- - (instancetype)initSessionRecordCallback:(KSVideoRecordCallback)callback {
- self = [super init];
- if (self) {
- if (callback) {
- self.callback = callback;
- }
- }
- return self;
- }
- - (void)setIgnoreAudio:(BOOL)ignoreAudio {
- _ignoreAudio = ignoreAudio;
- [self resetSession];
- }
- - (BOOL)getSessionStatusisActive {
- if (self.captureSession && self.captureSession.isRunning) {
- return YES;
- }
- else {
- return NO;
- }
- }
- - (void)configSessiondisplayInView:(UIView *)containerView {
-
- _captureSession = [[AVCaptureSession alloc] init];
- // 设置YES 播放web伴奏会导致打断
- _captureSession.automaticallyConfiguresApplicationAudioSession = NO;
- // 初始化会话对象
- if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetInputPriority]) {
- _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority;
- }
- NSError *error = nil;
-
- // 获取视频输出对象
- AVCaptureDevice *videoCaptureDevice = [self cameraDeviceWithPosition:(AVCaptureDevicePositionFront)];
- if (!videoCaptureDevice) {
- if (self.callback) {
- self.callback(NO, @"获取后置摄像头失败!");
- }
- }
- _videoCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
- if (error) {
- if (self.callback) {
- self.callback(NO, @"获取视频设备输入出错!");
- }
- return;
- }
- if ([_captureSession canAddInput:_videoCaptureDeviceInput]) {
- [_captureSession addInput:_videoCaptureDeviceInput];
- }
- else {
- if (self.callback) {
- self.callback(NO, @"无法添加视频输入对象");
- }
- }
- if (_ignoreAudio == NO) {
- // 获取音频输入对象
- AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
- _audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
- if (error) {
- if (self.callback) {
- self.callback(NO, @"获取音频设备输入出错!");
- }
- return;
- }
- //将设备输入添加到会话中
- if ([_captureSession canAddInput:_audioCaptureDeviceInput]) {
- [_captureSession addInput:_audioCaptureDeviceInput];
- }
- else {
- if (self.callback) {
- self.callback(NO, @"无法添加音频输入对象!");
- }
- }
- }
-
- // 初始化设备输出对象
- _captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
- _captureMovieFileOutput.movieFragmentInterval = kCMTimeInvalid;
-
- //将设备输出添加到会话中
- if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
- AVCaptureConnection *captureConnection = [_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
- //防抖功能
- if ([captureConnection isVideoStabilizationSupported]) {
- captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
- }
- [_captureSession addOutput:_captureMovieFileOutput];
- }
-
- //创建视频预览图层
- _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
- containerView.layer.masksToBounds = YES;
- _captureVideoPreviewLayer.frame = containerView.bounds;
- _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
- _captureVideoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
- [containerView.layer addSublayer:_captureVideoPreviewLayer];
-
- // 一定要在添加了 input 和 output之后~
- AVCaptureConnection *captureConnection = [_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
- captureConnection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
- [self startSession];
- }
- - (void)removeDisplay {
- [self stopSession];
- if (_captureVideoPreviewLayer) {
- [_captureVideoPreviewLayer removeFromSuperlayer];
- }
- }
- - (void)resetSession {
- if (_ignoreAudio == NO) {
- [_captureSession beginConfiguration];
- if (_audioCaptureDeviceInput) {
- [_captureSession removeInput:_audioCaptureDeviceInput];
- }
-
- NSError *error = nil;
- // 获取音频输入对象
- AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
- _audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
- if (error) {
- if (self.callback) {
- self.callback(NO, @"获取音频设备输入出错!");
- }
- return;
- }
- if ([_captureSession canAddInput:_audioCaptureDeviceInput]) {
- [_captureSession addInput:_audioCaptureDeviceInput];
- }
- else {
- if (self.callback) {
- self.callback(NO, @"无法添加音频输入对象");
- }
- }
-
- [_captureSession commitConfiguration];
- }
- [self startSession];
- }
- - (void)startSession {
- @weakObj(self);
- dispatch_async(self.videoRecordQueue, ^{
- @strongObj(self);
- if (!self.captureSession.running) {
- [self.captureSession startRunning];
- }
- });
- }
- - (void)stopSession {
- if (_captureSession) {
- @weakObj(self);
- dispatch_async(self.videoRecordQueue, ^{
- @strongObj(self);
- if (self.captureSession.running) {
- [self.captureSession stopRunning];
- }
- });
- self.captureSession = nil;
- }
- }
- - (void)startRecord {
- if (_captureMovieFileOutput) {
- // 开始录制
- [self.captureMovieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:[self getRecordFilePath]] recordingDelegate:self];
- }
-
- }
- - (void)stopRecord {
- if (_captureMovieFileOutput) {
- [self.captureMovieFileOutput stopRecording];
- }
- [self resetSession];
- }
- - (void)removeVideoWithPath:(NSString *)videoUrl {
- NSFileManager *fileMamager = [NSFileManager defaultManager];
- if ([fileMamager fileExistsAtPath:videoUrl]) {
- [fileMamager removeItemAtPath:videoUrl error:nil];
- }
- }
- /**取得指定位置的摄像头*/
- - (AVCaptureDevice *)cameraDeviceWithPosition:(AVCaptureDevicePosition)position {
- NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
- for (AVCaptureDevice *camera in cameras) {
- if ([camera position] == position) {
- return camera;
- }
- }
- return nil;
- }
- // 切换摄像头
- - (void)swapCameras {
- // Assume the session is already running
- NSArray *inputs =self.captureSession.inputs;
- for (AVCaptureDeviceInput *input in inputs ) {
- AVCaptureDevice *device = input.device;
- if ( [device hasMediaType:AVMediaTypeVideo] ) {
- AVCaptureDevicePosition position = device.position;
- AVCaptureDevice *newCamera =nil;
- AVCaptureDeviceInput *newInput =nil;
-
- if (position ==AVCaptureDevicePositionFront)
- newCamera = [self cameraDeviceWithPosition:AVCaptureDevicePositionBack];
- else
- newCamera = [self cameraDeviceWithPosition:AVCaptureDevicePositionFront];
- newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
-
- // beginConfiguration ensures that pending changes are not applied immediately
- [self.captureSession beginConfiguration];
-
- [self.captureSession removeInput:input];
- [self.captureSession addInput:newInput];
-
- // Changes take effect once the outermost commitConfiguration is invoked.
- [self.captureSession commitConfiguration];
- break;
- }
- }
- }
- #pragma mark -------- AVCaptureFileOutputRecordingDelegate ----------
- - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections {
- NSLog(@"开始录制");
- _isRecording = YES;
- }
- - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {
- if (error) {
- NSLog(@"error desc :%@", error.description);
- }
- NSLog(@"录制结束");
- _isRecording = NO;
- @weakObj(self);
- dispatch_async(self.videoRecordQueue, ^{
- @strongObj(self);
- if (self.captureSession.running) {
- [self.captureSession stopRunning];
- }
- });
- // 暂时存储文件地址
- self.videoFileURL = outputFileURL;
- // 保存文件
- if (self.skipSaveRecord == NO) {
- if (_ignoreAudio == NO) {
- [self saveVideoToAsset:self.videoFileURL];
- }
- else {
- [self addBackgroundMuisc:self.audioUrl];
- }
- }
- self.skipSaveRecord = NO;
- }
- // 生成文件 合并音轨
- - (void)addBackgroundMuisc:(NSURL *)audioUrl {
- AVURLAsset* audioAsset =[AVURLAsset URLAssetWithURL:audioUrl options:nil];
-
- CMTime audioDuration = audioAsset.duration;
-
- float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
-
- NSLog(@"%f",audioDurationSeconds);
- [KSVideoEditor addBackgroundMiusicWithVideoUrlStr:self.videoFileURL audioUrl:audioUrl bgAudioUrl:self.bgAudioUrl start:0 end:audioDurationSeconds isOriginalSound:NO oriVolume:0 newVolume:100 completion:^(NSString * _Nonnull outPath, BOOL isSuccess) {
- if (isSuccess) {
- [self saveVideoToAsset:[NSURL fileURLWithPath:outPath]];
- }
- else {
-
- }
- }];
- }
- // 保存到相册
- - (void)saveVideoToAsset:(NSURL *)videoUrl {
- [MBProgressHUD ksShowHUDWithText:@"视频处理中..."];
- [[TZImageManager manager] saveVideoWithUrl:videoUrl completion:^(PHAsset *asset, NSError *error) {
- if (!error) {
- self.videoAsset = asset;
- dispatch_main_async_safe(^{
- [MBProgressHUD ksHideHUD];
- if (self.callback) {
- self.callback(YES, @"保存成功");
- }
-
- // 删除文件
- [self removeVideoWithPath:self.videoFileURL.path];
- [self removeVideoWithPath:videoUrl.path];
- // 重置
- [self resetSession];
-
- });
- }
- else {
- dispatch_main_async_safe(^{
- [MBProgressHUD ksHideHUD];
- if (self.callback) {
- self.callback(NO, @"保存视频错误");
- }
- // 删除文件
- [self removeVideoWithPath:self.videoFileURL.path];
- [self removeVideoWithPath:videoUrl.path];
- // 重置
- [self resetSession];
-
- });
- }
- }];
- }
- // 上传视频
- - (void)uploadRecordVideoSuccess:(void (^)(NSString * _Nonnull))success failure:(void (^)(NSString * _Nonnull))faliure {
- if (self.videoAsset) {
- dispatch_main_async_safe(^{
- [MBProgressHUD ksShowHUDWithText:@"视频导出中..."];
- });
- [[TZImageManager manager] getVideoOutputPathWithAsset:self.videoAsset presetName:self.presentName success:^(NSString *outputPath) {
- dispatch_main_async_safe(^{
- [MBProgressHUD ksHideHUD];
- });
- NSLog(@"视频导出到本地完成,沙盒路径为:%@",outputPath);
- NSData *outputData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:outputPath]]; //压缩后的视频
- NSLog(@"导出后的视频:%@",[NSString stringWithFormat:@"%.2fM",(CGFloat)outputData.length/(1024*1024)]);
- // 上传
- dispatch_main_async_safe(^{
- [self sendVideoActionWith:outputPath success:success failure:faliure];
- });
-
- } failure:^(NSString *errorMessage, NSError *error) {
- dispatch_main_async_safe(^{
- [MBProgressHUD ksHideHUD];
- faliure(@"视频导出失败");
- });
- NSLog(@"视频导出失败:%@,error:%@",errorMessage, error);
-
- }];
- }
- else {
- faliure(@"未找到视频资源");
- }
- }
- - (void)sendVideoActionWith:(NSString *)fileUrl success:(void (^)(NSString * _Nonnull))success failure:(void (^)(NSString * _Nonnull))faliure {
- [self hudTipWillShow:YES];
- NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:fileUrl]];
- NSString *suffix = [NSString stringWithFormat:@".%@",[fileUrl pathExtension]];
- [[KSUploadManager shareInstance] configBucketName:@"daya"];
- [[KSUploadManager shareInstance] videoUpload:fileData fileName:@"video" fileSuffix:suffix progress:^(int64_t bytesWritten, int64_t totalBytes) {
- // 显示进度
- if (self.HUD) {
- self.HUD.progress = bytesWritten / totalBytes;// progress是回调进度
- }
- } successCallback:^(NSMutableArray * _Nonnull fileUrlArray) {
- [self hudTipWillShow:NO];
- NSString *fileUrl = [fileUrlArray lastObject];
- success(fileUrl);
-
- } faliure:^(NSError * _Nullable error, NSString * _Nullable descMessaeg) {
- [self hudTipWillShow:NO];
- faliure(descMessaeg);
- }];
- }
- - (void)hudTipWillShow:(BOOL)willShow{
- if (willShow) {
-
- UIWindow *keyWindow = [NSObject getKeyWindow];
- if (!_HUD) {
- _HUD = [MBProgressHUD showHUDAddedTo:keyWindow animated:YES];
- _HUD.label.textColor = [UIColor whiteColor];
- _HUD.mode = MBProgressHUDModeDeterminateHorizontalBar;
- _HUD.label.text = @"正在上传视频...";
- _HUD.contentColor = [UIColor whiteColor];
- _HUD.removeFromSuperViewOnHide = YES;
- _HUD.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
- _HUD.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
- }else{
- _HUD.progress = 0;
- [keyWindow addSubview:_HUD];
- [_HUD showAnimated:YES];
- }
- }else{
- [_HUD hideAnimated:YES];
- }
- }
- #pragma mark ------ 设置录制地址
- - (NSString *)getRecordFilePath {
- NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"AccompanyVideoData"];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL isDir = FALSE;
- BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
- if(!(isDirExist && isDir)) {
- BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
- if(!bCreateDir){
- NSLog(@"创建文件夹失败!");
- }
- NSLog(@"创建文件夹成功,文件路径%@",path);
- }
- NSString *songName = @"recordSong";
- NSString *fileName = [NSString stringWithFormat:@"%@.mp4",songName];
- NSString *filePath = [path stringByAppendingPathComponent:fileName];
- return filePath;
- }
- - (dispatch_queue_t)videoRecordQueue {
- if (!_videoRecordQueue) {
- _videoRecordQueue = dispatch_queue_create("com.Colexiu.videoRecord", DISPATCH_QUEUE_SERIAL);
- }
- return _videoRecordQueue;
- }
- @end
|