|
@ -0,0 +1,806 @@
|
|
1
|
//
|
|
2
|
// CustomVideoViewController.m
|
|
3
|
// WadeMobileFunc
|
|
4
|
//
|
|
5
|
// Created by Mac on 2019/11/5.
|
|
6
|
// Copyright © 2019 asiainfo. All rights reserved.
|
|
7
|
//
|
|
8
|
|
|
9
|
#import "CustomVideoViewController.h"
|
|
10
|
#import <AVFoundation/AVFoundation.h>
|
|
11
|
#import <WadeMobile/WDCommonTool.h>
|
|
12
|
#import <WadeMobile/UIViewAdditions.h>
|
|
13
|
|
|
14
|
#define deviceHeight ([[UIScreen mainScreen] bounds].size.height)
|
|
15
|
#define deviceWidth ([[UIScreen mainScreen] bounds].size.width)
|
|
16
|
#define isIphoneX ([[UIScreen mainScreen] bounds].size.height==812?YES:NO)
|
|
17
|
#define widthScale(f) [CustomVideoViewController widthScale:f]
|
|
18
|
#define heightScale(f) [CustomVideoViewController heightScale:f]
|
|
19
|
|
|
20
|
typedef NS_ENUM(NSInteger,VideoStatus){
|
|
21
|
VideoStatusEnded = 0,
|
|
22
|
VideoStatusStarted
|
|
23
|
};
|
|
24
|
|
|
25
|
@interface CustomVideoViewController ()<AVCaptureFileOutputRecordingDelegate>
|
|
26
|
{
|
|
27
|
AVCaptureSession *captureSession;
|
|
28
|
AVCaptureDevice *videoDevice;//视频
|
|
29
|
AVCaptureDevice *audioDevice;//音频
|
|
30
|
AVCaptureDeviceInput *videoInput;//视频输入设备
|
|
31
|
AVCaptureDeviceInput *audioInput;//音频输入设备
|
|
32
|
AVCaptureMovieFileOutput *movieOutput;
|
|
33
|
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
|
|
34
|
|
|
35
|
NSBundle *bundle;//资源包
|
|
36
|
int seconds;//视频录制最大秒数
|
|
37
|
}
|
|
38
|
//拍摄区域
|
|
39
|
@property(nonatomic,strong) UIView *videoView;
|
|
40
|
|
|
41
|
@property(nonatomic,strong) UIView *bottomBaseView;
|
|
42
|
@property(nonatomic,strong) NSLayoutConstraint *progressWidth;//进度条约束
|
|
43
|
@property(nonatomic,strong) UIView *progressView;//进度条
|
|
44
|
@property(nonatomic,strong) UIView *recordView;//按住拍
|
|
45
|
@property(nonatomic,strong) UILabel *secondLbl;//当前录制秒数
|
|
46
|
@property(nonatomic,strong) UIButton *changeBtn;//前后摄像头切换
|
|
47
|
@property(nonatomic,strong) UIButton *backBtn;//返回
|
|
48
|
@property(nonatomic,strong) UIButton *confirmBtn;//确认录制完成
|
|
49
|
|
|
50
|
@property(nonatomic,assign) VideoStatus status;
|
|
51
|
@property(nonatomic,assign) BOOL canSave;//是否可以保存视频
|
|
52
|
|
|
53
|
@property(nonatomic,strong) CADisplayLink *link;//计时
|
|
54
|
|
|
55
|
@property(nonatomic,strong)NSString *validateData;
|
|
56
|
@end
|
|
57
|
|
|
58
|
@implementation CustomVideoViewController
|
|
59
|
|
|
60
|
-(id)initVCWithRecordSeconds:(int)senconds{
|
|
61
|
self = [super init];
|
|
62
|
if (self) {
|
|
63
|
seconds = senconds;
|
|
64
|
}
|
|
65
|
return self;
|
|
66
|
}
|
|
67
|
|
|
68
|
- (void)viewDidLoad {
|
|
69
|
[super viewDidLoad];
|
|
70
|
self.view.backgroundColor = [UIColor whiteColor];
|
|
71
|
|
|
72
|
NSString *bundlestring = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/WadeMobileFuncBundle.bundle"];
|
|
73
|
bundle = [NSBundle bundleWithPath:bundlestring];
|
|
74
|
|
|
75
|
//导航栏背景色
|
|
76
|
UIImage * img = [WDCommonTool imageWithColor:[WDCommonTool convertHexToColor:@"#1e96fa"] andSize:CGSizeMake(deviceWidth, 64)];
|
|
77
|
[self.navigationController.navigationBar setBackgroundImage:img forBarMetrics:UIBarMetricsDefault];
|
|
78
|
|
|
79
|
//返回按钮
|
|
80
|
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
81
|
backButton.frame = CGRectMake(0, 0, 30, 30);
|
|
82
|
UIImage *backImg = [UIImage imageNamed:@"back" inBundle:bundle compatibleWithTraitCollection:nil];
|
|
83
|
[backButton setImage:backImg forState:UIControlStateNormal];
|
|
84
|
[backButton addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
|
|
85
|
UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithCustomView:backButton];
|
|
86
|
self.navigationItem.leftBarButtonItem = back;
|
|
87
|
|
|
88
|
//标题
|
|
89
|
UILabel *titleLbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, deviceWidth, 30)];
|
|
90
|
titleLbl.font = [UIFont systemFontOfSize:18];
|
|
91
|
titleLbl.textAlignment = NSTextAlignmentCenter;
|
|
92
|
self.navigationItem.titleView = titleLbl;
|
|
93
|
titleLbl.text = @"自定义视频录制";
|
|
94
|
titleLbl.textColor = [WDCommonTool convertHexToColor:@"#edfeff"];
|
|
95
|
|
|
96
|
[self initUI];
|
|
97
|
[self getAuthorization];
|
|
98
|
}
|
|
99
|
#pragma mark - UI初始化
|
|
100
|
/*
|
|
101
|
UI初始化
|
|
102
|
*/
|
|
103
|
-(void)initUI{
|
|
104
|
//录制区域
|
|
105
|
self.videoView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, deviceWidth, deviceHeight-64)];
|
|
106
|
self.videoView.layer.masksToBounds = YES;
|
|
107
|
[self.view addSubview:self.videoView];
|
|
108
|
|
|
109
|
//操作区域
|
|
110
|
self.bottomBaseView = [[UIView alloc] initWithFrame:CGRectMake(0, deviceHeight-64-heightScale(100), deviceWidth, heightScale(100))];
|
|
111
|
self.bottomBaseView.backgroundColor = [UIColor blackColor];
|
|
112
|
self.bottomBaseView.alpha = 0.5;
|
|
113
|
[self.view addSubview:self.bottomBaseView];
|
|
114
|
|
|
115
|
self.progressView = [[UIView alloc] initWithFrame:CGRectMake(0, deviceHeight-heightScale(100)-64, 0, 2)];
|
|
116
|
self.progressView.backgroundColor = [UIColor whiteColor];
|
|
117
|
self.progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
118
|
[self.view addSubview:self.progressView];
|
|
119
|
|
|
120
|
self.progressWidth = [NSLayoutConstraint constraintWithItem:self.progressView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0];
|
|
121
|
[self.progressView addConstraint:self.progressWidth];
|
|
122
|
|
|
123
|
[self.progressView addConstraint:[NSLayoutConstraint constraintWithItem:self.progressView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:2]];
|
|
124
|
|
|
125
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.progressView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]];
|
|
126
|
|
|
127
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.progressView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:deviceHeight-heightScale(100)-64]];
|
|
128
|
|
|
129
|
|
|
130
|
UIButton *recordView11 = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
131
|
UIImage *recodeImg = [UIImage imageNamed:@"recode" inBundle:bundle compatibleWithTraitCollection:nil];
|
|
132
|
[recordView11 setImage:recodeImg forState:UIControlStateNormal];
|
|
133
|
recordView11.frame = CGRectMake(deviceWidth/2-widthScale(35), deviceHeight-64-self.bottomBaseView.height+10, widthScale(70), heightScale(70));
|
|
134
|
[self.view addSubview:recordView11];
|
|
135
|
self.recordView = [[UIView alloc] initWithFrame:CGRectMake(deviceWidth/2-widthScale(35), deviceHeight-64-self.bottomBaseView.height+10, widthScale(70), heightScale(70))];
|
|
136
|
self.recordView.backgroundColor = [UIColor clearColor];
|
|
137
|
self.recordView.userInteractionEnabled = YES;
|
|
138
|
[self.view addSubview:self.recordView];
|
|
139
|
|
|
140
|
self.backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
141
|
UIImage *backVideoImg = [UIImage imageNamed:@"backVideo" inBundle:bundle compatibleWithTraitCollection:nil];
|
|
142
|
[self.backBtn setImage:backVideoImg forState:UIControlStateNormal];
|
|
143
|
self.backBtn.frame = CGRectMake(self.recordView.left-widthScale(80), deviceHeight-64-self.bottomBaseView.height+10, widthScale(70), heightScale(70));
|
|
144
|
[self.backBtn addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
|
|
145
|
[self.view addSubview:self.backBtn];
|
|
146
|
self.backBtn.hidden = YES;
|
|
147
|
|
|
148
|
self.confirmBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
149
|
UIImage *confirmImg = [UIImage imageNamed:@"confirm" inBundle:bundle compatibleWithTraitCollection:nil];
|
|
150
|
[self.confirmBtn setImage:confirmImg forState:UIControlStateNormal];
|
|
151
|
self.confirmBtn.frame = CGRectMake(self.recordView.right+widthScale(10), deviceHeight-64-self.bottomBaseView.height+10, widthScale(70), heightScale(70));
|
|
152
|
[self.confirmBtn addTarget:self action:@selector(confirm:) forControlEvents:UIControlEventTouchUpInside];
|
|
153
|
[self.view addSubview:self.confirmBtn];
|
|
154
|
self.confirmBtn.hidden = YES;
|
|
155
|
|
|
156
|
self.secondLbl = [[UILabel alloc] initWithFrame:CGRectMake(self.backBtn.left-widthScale(40)-15, deviceHeight-64-self.bottomBaseView.height+10, widthScale(40), heightScale(70))];
|
|
157
|
self.secondLbl.font = [UIFont boldSystemFontOfSize:16];
|
|
158
|
self.secondLbl.textColor = [UIColor whiteColor];
|
|
159
|
self.secondLbl.text = @"";
|
|
160
|
self.secondLbl.textAlignment = NSTextAlignmentRight;
|
|
161
|
[self.view addSubview:self.secondLbl];
|
|
162
|
|
|
163
|
self.changeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
164
|
UIImage *cameraImg = [UIImage imageNamed:@"camera" inBundle:bundle compatibleWithTraitCollection:nil];
|
|
165
|
[self.changeBtn setImage:cameraImg forState:UIControlStateNormal];
|
|
166
|
self.changeBtn.frame = CGRectMake(self.confirmBtn.right+10, deviceHeight-64-self.bottomBaseView.height+10, widthScale(40), heightScale(70));
|
|
167
|
[self.changeBtn addTarget:self action:@selector(change:) forControlEvents:UIControlEventTouchUpInside];
|
|
168
|
[self.view addSubview:self.changeBtn];
|
|
169
|
|
|
170
|
if ([self.delegate respondsToSelector:@selector(customWithBaseView:VideoView:SourceBundle:)]) {
|
|
171
|
[self.delegate customWithBaseView:self.view VideoView:self.videoView SourceBundle:bundle];
|
|
172
|
}
|
|
173
|
|
|
174
|
[self addGenstureRecognizer];
|
|
175
|
}
|
|
176
|
|
|
177
|
|
|
178
|
#pragma mark - click action
|
|
179
|
/*
|
|
180
|
返回
|
|
181
|
*/
|
|
182
|
-(void)back:(UIButton *)sender{
|
|
183
|
[self dismissViewControllerAnimated:YES completion:^{
|
|
184
|
|
|
185
|
}];
|
|
186
|
}
|
|
187
|
/*
|
|
188
|
确认压缩上传
|
|
189
|
*/
|
|
190
|
-(void)confirm:(UIButton *)sender{
|
|
191
|
// NSLog(@"开始压缩,压缩前大小 %f MB",[self fileSize:[self outPutFileURL]]);
|
|
192
|
//媒体资源类
|
|
193
|
AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:[self outPutFileURL] options:nil];
|
|
194
|
//使用AVAssetExportSession导出一个新的资源副本以及元数据改动.
|
|
195
|
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
|
|
196
|
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
|
|
197
|
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPreset640x480];
|
|
198
|
[self removeFileWithUrl:[self compressedPath]];
|
|
199
|
exportSession.outputURL = [self compressedURL];
|
|
200
|
//优化网络
|
|
201
|
exportSession.shouldOptimizeForNetworkUse = true;
|
|
202
|
//转换后的格式
|
|
203
|
exportSession.outputFileType = AVFileTypeMPEG4;//AVFileTypeQuickTimeMovie
|
|
204
|
//异步导出
|
|
205
|
[exportSession exportAsynchronouslyWithCompletionHandler:^{
|
|
206
|
//如果导出的状态为完成
|
|
207
|
if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
|
|
208
|
NSLog(@"压缩完毕,压缩后大小 %f MB",[self fileSize:[self compressedURL]]);
|
|
209
|
|
|
210
|
if ([self.delegate respondsToSelector:@selector(confirmWithData:FilePath:)]) {
|
|
211
|
NSData *data = [NSData dataWithContentsOfURL:[self compressedURL]];
|
|
212
|
[self.delegate confirmWithData:data FilePath:[self compressedPath]];
|
|
213
|
}
|
|
214
|
[self dismissViewControllerAnimated:YES completion:^{
|
|
215
|
|
|
216
|
}];
|
|
217
|
}else{
|
|
218
|
NSLog(@"当前压缩进度:%f",exportSession.progress);
|
|
219
|
}
|
|
220
|
}];
|
|
221
|
}
|
|
222
|
}
|
|
223
|
- (CGFloat)fileSize:(NSURL *)path
|
|
224
|
{
|
|
225
|
return [[NSData dataWithContentsOfURL:path] length]/1024.00 /1024.00;
|
|
226
|
}
|
|
227
|
- (NSURL *)compressedURL
|
|
228
|
{
|
|
229
|
return [NSURL fileURLWithPath:[self compressedPath]];
|
|
230
|
}
|
|
231
|
- (NSString *)compressedPath
|
|
232
|
{
|
|
233
|
return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"compressed.mp4"]];
|
|
234
|
}
|
|
235
|
-(BOOL)removeFileWithUrl:(NSString *)url{
|
|
236
|
NSFileManager *defaultManager = [NSFileManager defaultManager];
|
|
237
|
NSError *error;
|
|
238
|
[defaultManager removeItemAtPath:url error:&error];
|
|
239
|
if (error) {
|
|
240
|
NSLog(@"error = %@", error);
|
|
241
|
return NO;
|
|
242
|
}
|
|
243
|
return YES;
|
|
244
|
}
|
|
245
|
/*
|
|
246
|
前后摄像头切换
|
|
247
|
*/
|
|
248
|
-(void)change:(UIButton *)sender{
|
|
249
|
switch (videoDevice.position) {
|
|
250
|
case AVCaptureDevicePositionBack:
|
|
251
|
videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionFront];
|
|
252
|
break;
|
|
253
|
case AVCaptureDevicePositionFront:
|
|
254
|
videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
|
|
255
|
break;
|
|
256
|
default:
|
|
257
|
return;
|
|
258
|
break;
|
|
259
|
}
|
|
260
|
|
|
261
|
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
|
|
262
|
NSError *error;
|
|
263
|
AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self->videoDevice error:&error];
|
|
264
|
|
|
265
|
if (newVideoInput != nil) {
|
|
266
|
//必选先 remove 才能询问 canAdd
|
|
267
|
[self->captureSession removeInput:self->videoInput];
|
|
268
|
if ([self->captureSession canAddInput:newVideoInput]) {
|
|
269
|
[self->captureSession addInput:newVideoInput];
|
|
270
|
self->videoInput = newVideoInput;
|
|
271
|
}else{
|
|
272
|
[self->captureSession addInput:self->videoInput];
|
|
273
|
}
|
|
274
|
} else if (error) {
|
|
275
|
NSLog(@"切换前/后摄像头失败, error = %@", error);
|
|
276
|
}
|
|
277
|
}];
|
|
278
|
}
|
|
279
|
|
|
280
|
#pragma mark - 录制相关的方法
|
|
281
|
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
282
|
{
|
|
283
|
UITouch *touch = [touches anyObject];
|
|
284
|
CGPoint point = [touch locationInView:self.view];
|
|
285
|
BOOL condition = [self isInBtnRect:point];//是否按住
|
|
286
|
if (condition) {
|
|
287
|
[self startAnimation];
|
|
288
|
}else{
|
|
289
|
[self stopAnimation];
|
|
290
|
}
|
|
291
|
}
|
|
292
|
|
|
293
|
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
294
|
{
|
|
295
|
[super touchesMoved:touches withEvent:event];
|
|
296
|
UITouch *touch = [touches anyObject];
|
|
297
|
CGPoint point = [touch locationInView:self.view];
|
|
298
|
BOOL condition = [self isInBtnRect:point];
|
|
299
|
if (!condition) {
|
|
300
|
[self stopAnimation];
|
|
301
|
}
|
|
302
|
}
|
|
303
|
|
|
304
|
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
305
|
{
|
|
306
|
UITouch *touch = [touches anyObject];
|
|
307
|
CGPoint point = [touch locationInView:self.view];
|
|
308
|
BOOL condition = [self isInBtnRect:point];
|
|
309
|
if (condition) {
|
|
310
|
if (self.progressWidth.constant > deviceWidth*0.17) {
|
|
311
|
[self recordComplete];//录制完成
|
|
312
|
}
|
|
313
|
}
|
|
314
|
[self stopAnimation];
|
|
315
|
}
|
|
316
|
|
|
317
|
- (BOOL)isInBtnRect:(CGPoint)point
|
|
318
|
{
|
|
319
|
CGFloat x = point.x;
|
|
320
|
CGFloat y = point.y;
|
|
321
|
return (x>self.recordView.left && x<=self.recordView.right) && (y>self.recordView.top && y<=self.recordView.bottom);
|
|
322
|
}
|
|
323
|
|
|
324
|
- (void)startAnimation
|
|
325
|
{
|
|
326
|
if (self.status == VideoStatusEnded) {
|
|
327
|
self.status = VideoStatusStarted;
|
|
328
|
[UIView animateWithDuration:0.5 animations:^{
|
|
329
|
self.progressView.alpha = 1.0;
|
|
330
|
} completion:^(BOOL finished) {
|
|
331
|
[self stopLink];
|
|
332
|
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
333
|
}];
|
|
334
|
}
|
|
335
|
}
|
|
336
|
- (CADisplayLink *)link
|
|
337
|
{
|
|
338
|
if (!_link) {
|
|
339
|
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(refresh:)];
|
|
340
|
self.progressWidth.constant = 0;
|
|
341
|
[self startRecord];
|
|
342
|
}
|
|
343
|
return _link;
|
|
344
|
}
|
|
345
|
- (void)refresh:(CADisplayLink *)link
|
|
346
|
{
|
|
347
|
if (self.progressWidth.constant >= deviceWidth) {
|
|
348
|
self.progressWidth.constant = deviceWidth;
|
|
349
|
[self recordComplete];
|
|
350
|
[self stopAnimation];
|
|
351
|
return;
|
|
352
|
}
|
|
353
|
self.progressWidth.constant += deviceWidth/360;
|
|
354
|
[self refreshSecond];
|
|
355
|
}
|
|
356
|
-(void)refreshSecond{//seconds
|
|
357
|
CGFloat perWidth = deviceWidth/seconds;
|
|
358
|
for (int i=1; i<seconds+1; i++) {
|
|
359
|
if (self.progressWidth.constant>=perWidth*i-2 && self.progressWidth.constant<=perWidth*i+2) {
|
|
360
|
self.secondLbl.text = [NSString stringWithFormat:@"%ds",i];
|
|
361
|
}else if (self.progressWidth.constant==0){
|
|
362
|
self.secondLbl.text = @"";
|
|
363
|
}
|
|
364
|
}
|
|
365
|
|
|
366
|
|
|
367
|
// if (self.progressWidth.constant>=deviceWidth/6-2 && self.progressWidth.constant<=deviceWidth/6+2) {
|
|
368
|
// self.secondLbl.text = @"1s";
|
|
369
|
// }
|
|
370
|
// else if (self.progressWidth.constant>=deviceWidth/3-2 && self.progressWidth.constant<=deviceWidth/3+2){
|
|
371
|
// self.secondLbl.text = @"2s";
|
|
372
|
// }
|
|
373
|
// else if (self.progressWidth.constant>=deviceWidth/2-2 && self.progressWidth.constant<=deviceWidth/2+2){
|
|
374
|
// self.secondLbl.text = @"3s";
|
|
375
|
// }
|
|
376
|
// else if (self.progressWidth.constant>=deviceWidth*2/3-2 && self.progressWidth.constant<=deviceWidth*2/3+2){
|
|
377
|
// self.secondLbl.text = @"4s";
|
|
378
|
// }
|
|
379
|
// else if (self.progressWidth.constant>=deviceWidth*5/6-2 && self.progressWidth.constant<=deviceWidth*5/6+2){
|
|
380
|
// self.secondLbl.text = @"5s";
|
|
381
|
// }
|
|
382
|
// else if (self.progressWidth.constant>=deviceWidth-2 && self.progressWidth.constant<=deviceWidth+2){
|
|
383
|
// self.secondLbl.text = @"6s";
|
|
384
|
// }
|
|
385
|
// else if (self.progressWidth.constant==0){
|
|
386
|
// self.secondLbl.text = @"";
|
|
387
|
// }
|
|
388
|
}
|
|
389
|
- (void)startRecord
|
|
390
|
{
|
|
391
|
[movieOutput startRecordingToOutputFileURL:[self outPutFileURL] recordingDelegate:self];
|
|
392
|
}
|
|
393
|
- (NSURL *)outPutFileURL
|
|
394
|
{
|
|
395
|
NSLog(@"执行------------------------outPutFileURL");
|
|
396
|
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"outPut.mp4"]];
|
|
397
|
}
|
|
398
|
- (void)stopLink
|
|
399
|
{
|
|
400
|
_link.paused = YES;
|
|
401
|
[_link invalidate];
|
|
402
|
_link = nil;
|
|
403
|
}
|
|
404
|
- (void)recordComplete
|
|
405
|
{
|
|
406
|
self.canSave = YES;
|
|
407
|
self.backBtn.hidden = NO;
|
|
408
|
self.confirmBtn.hidden = NO;
|
|
409
|
}
|
|
410
|
- (void)stopAnimation{
|
|
411
|
if (self.status == VideoStatusStarted) {
|
|
412
|
self.status = VideoStatusEnded;
|
|
413
|
[self stopLink];
|
|
414
|
[self stopRecord];
|
|
415
|
}
|
|
416
|
}
|
|
417
|
- (void)stopRecord
|
|
418
|
{
|
|
419
|
[movieOutput stopRecording];// 取消视频拍摄
|
|
420
|
}
|
|
421
|
|
|
422
|
|
|
423
|
#pragma mark - AVCaptureFileOutputRecordingDelegate
|
|
424
|
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
|
|
425
|
{
|
|
426
|
NSLog(@"---- 开始录制 ----");
|
|
427
|
}
|
|
428
|
|
|
429
|
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
|
|
430
|
{
|
|
431
|
NSLog(@"---- 录制结束 ---%@-%@ ",outputFileURL,captureOutput.outputFileURL);
|
|
432
|
|
|
433
|
if (outputFileURL.absoluteString.length == 0 && captureOutput.outputFileURL.absoluteString.length == 0 ) {
|
|
434
|
[self showMsgWithTitle:@"出错了" andContent:@"录制视频保存地址出错"];
|
|
435
|
return;
|
|
436
|
}
|
|
437
|
|
|
438
|
if (self.canSave) {
|
|
439
|
self.canSave = NO;
|
|
440
|
}
|
|
441
|
}
|
|
442
|
|
|
443
|
#pragma mark - 摄像头授权
|
|
444
|
/*
|
|
445
|
获取授权
|
|
446
|
*/
|
|
447
|
- (void)getAuthorization
|
|
448
|
{
|
|
449
|
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
|
|
450
|
{
|
|
451
|
case AVAuthorizationStatusAuthorized://已授权,可使用
|
|
452
|
{
|
|
453
|
NSLog(@"授权摄像头使用成功");
|
|
454
|
//视频录制初始化
|
|
455
|
[self setupAVCaptureInfo];
|
|
456
|
break;
|
|
457
|
}
|
|
458
|
case AVAuthorizationStatusNotDetermined://未进行授权选择
|
|
459
|
{
|
|
460
|
//则再次请求授权
|
|
461
|
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
|
|
462
|
if(granted){//用户授权成功
|
|
463
|
//视频录制初始化
|
|
464
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
465
|
[self setupAVCaptureInfo];
|
|
466
|
});
|
|
467
|
return;
|
|
468
|
}else{//用户拒绝授权
|
|
469
|
[self pop];
|
|
470
|
[self showMsgWithTitle:@"出错了" andContent:@"用户拒绝授权摄像头的使用权,返回上一页.请打开\n设置-->隐私/通用等权限设置"];
|
|
471
|
return;
|
|
472
|
}
|
|
473
|
}];
|
|
474
|
break;
|
|
475
|
}
|
|
476
|
default://用户拒绝授权/未授权
|
|
477
|
{
|
|
478
|
[self pop];
|
|
479
|
[self showMsgWithTitle:@"出错了" andContent:@"拒绝授权,返回上一页.请检查下\n设置-->隐私/通用等权限设置"];
|
|
480
|
break;
|
|
481
|
}
|
|
482
|
}
|
|
483
|
}
|
|
484
|
/*
|
|
485
|
视频录制初始化
|
|
486
|
*/
|
|
487
|
- (void)setupAVCaptureInfo
|
|
488
|
{
|
|
489
|
//创建会话,并设置视频分辨率
|
|
490
|
[self addSession];
|
|
491
|
[captureSession beginConfiguration];
|
|
492
|
//添加视频输入设备
|
|
493
|
[self addVideo];
|
|
494
|
//添加音频输入设备
|
|
495
|
[self addAudio];
|
|
496
|
//添加预览层
|
|
497
|
[self addPreviewLayer];
|
|
498
|
[captureSession commitConfiguration];
|
|
499
|
//开启会话-->注意,不等于开始录制
|
|
500
|
[captureSession startRunning];
|
|
501
|
}
|
|
502
|
/*
|
|
503
|
创建会话,并设置视频分辨率
|
|
504
|
*/
|
|
505
|
- (void)addSession
|
|
506
|
{
|
|
507
|
captureSession = [[AVCaptureSession alloc] init];
|
|
508
|
//设置视频分辨率
|
|
509
|
/* 通常支持如下格式
|
|
510
|
(
|
|
511
|
AVAssetExportPresetLowQuality,
|
|
512
|
AVAssetExportPreset960x540,
|
|
513
|
AVAssetExportPreset640x480,
|
|
514
|
AVAssetExportPresetMediumQuality,
|
|
515
|
AVAssetExportPreset1920x1080,
|
|
516
|
AVAssetExportPreset1280x720,
|
|
517
|
AVAssetExportPresetHighestQuality,
|
|
518
|
AVAssetExportPresetAppleM4A
|
|
519
|
)
|
|
520
|
*/
|
|
521
|
//注意,这个地方设置的模式/分辨率大小将影响你后面拍摄照片/视频的大小,
|
|
522
|
if ([captureSession canSetSessionPreset:AVCaptureSessionPreset352x288]) {
|
|
523
|
[captureSession setSessionPreset:AVCaptureSessionPreset352x288];
|
|
524
|
}
|
|
525
|
}
|
|
526
|
/*
|
|
527
|
添加视频输入设备
|
|
528
|
*/
|
|
529
|
- (void)addVideo
|
|
530
|
{
|
|
531
|
// 获取摄像头输入设备, 创建 AVCaptureDeviceInput 对象
|
|
532
|
/* MediaType
|
|
533
|
AVMediaTypeVideo //视频
|
|
534
|
AVMediaTypeAudio //音频
|
|
535
|
*/
|
|
536
|
|
|
537
|
/* AVCaptureDevicePosition
|
|
538
|
AVCaptureDevicePositionBack //后置摄像头
|
|
539
|
AVCaptureDevicePositionFront //前置摄像头
|
|
540
|
*/
|
|
541
|
videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionFront];
|
|
542
|
|
|
543
|
[self addVideoInput];
|
|
544
|
[self addMovieOutput];
|
|
545
|
}
|
|
546
|
/*
|
|
547
|
获取摄像头:前置或后置
|
|
548
|
*/
|
|
549
|
- (AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position
|
|
550
|
{
|
|
551
|
NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
|
|
552
|
AVCaptureDevice *captureDevice = devices.firstObject;
|
|
553
|
|
|
554
|
for ( AVCaptureDevice *device in devices ) {
|
|
555
|
if ( device.position == position ) {
|
|
556
|
captureDevice = device;
|
|
557
|
break;
|
|
558
|
}
|
|
559
|
}
|
|
560
|
return captureDevice;
|
|
561
|
}
|
|
562
|
/*
|
|
563
|
添加视频输入对象
|
|
564
|
*/
|
|
565
|
- (void)addVideoInput
|
|
566
|
{
|
|
567
|
NSError *videoError;
|
|
568
|
// 根据输入设备初始化输入对象,用户获取输入数据
|
|
569
|
videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:&videoError];
|
|
570
|
if (videoError) {
|
|
571
|
NSLog(@"---- 取得摄像头设备时出错 ------ %@",videoError);
|
|
572
|
return;
|
|
573
|
}
|
|
574
|
|
|
575
|
// 将视频输入对象添加到会话 (AVCaptureSession) 中
|
|
576
|
if ([captureSession canAddInput:videoInput]) {
|
|
577
|
[captureSession addInput:videoInput];
|
|
578
|
}
|
|
579
|
}
|
|
580
|
/*
|
|
581
|
添加拍摄视频输出对象
|
|
582
|
*/
|
|
583
|
- (void)addMovieOutput
|
|
584
|
{
|
|
585
|
// 初始化输出设备对象,用户获取输出数据
|
|
586
|
movieOutput = [[AVCaptureMovieFileOutput alloc] init];
|
|
587
|
if ([captureSession canAddOutput:movieOutput]) {
|
|
588
|
[captureSession addOutput:movieOutput];
|
|
589
|
AVCaptureConnection *captureConnection = [movieOutput connectionWithMediaType:AVMediaTypeVideo];
|
|
590
|
|
|
591
|
//设置视频旋转方向
|
|
592
|
/*
|
|
593
|
typedef NS_ENUM(NSInteger, AVCaptureVideoOrientation) {
|
|
594
|
AVCaptureVideoOrientationPortrait = 1,
|
|
595
|
AVCaptureVideoOrientationPortraitUpsideDown = 2,
|
|
596
|
AVCaptureVideoOrientationLandscapeRight = 3,
|
|
597
|
AVCaptureVideoOrientationLandscapeLeft = 4,
|
|
598
|
} NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
|
|
599
|
*/
|
|
600
|
// if ([captureConnection isVideoOrientationSupported]) {
|
|
601
|
// [captureConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
|
|
602
|
// }
|
|
603
|
|
|
604
|
// 视频稳定设置
|
|
605
|
if ([captureConnection isVideoStabilizationSupported]) {
|
|
606
|
captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
|
|
607
|
}
|
|
608
|
captureConnection.videoScaleAndCropFactor = captureConnection.videoMaxScaleAndCropFactor;
|
|
609
|
}
|
|
610
|
|
|
611
|
}
|
|
612
|
/*
|
|
613
|
添加音频输入设备
|
|
614
|
*/
|
|
615
|
- (void)addAudio
|
|
616
|
{
|
|
617
|
NSError *audioError;
|
|
618
|
// 添加一个音频输入设备
|
|
619
|
audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
|
|
620
|
// 音频输入对象
|
|
621
|
audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:&audioError];
|
|
622
|
if (audioError) {
|
|
623
|
NSLog(@"取得录音设备时出错 ------ %@",audioError);
|
|
624
|
return;
|
|
625
|
}
|
|
626
|
// 将音频输入对象添加到会话 (AVCaptureSession) 中
|
|
627
|
if ([captureSession canAddInput:audioInput]) {
|
|
628
|
[captureSession addInput:audioInput];
|
|
629
|
}
|
|
630
|
}
|
|
631
|
/*
|
|
632
|
创建预览层
|
|
633
|
*/
|
|
634
|
- (void)addPreviewLayer
|
|
635
|
{
|
|
636
|
[self.view layoutIfNeeded];
|
|
637
|
|
|
638
|
// 通过会话 (AVCaptureSession) 创建预览层
|
|
639
|
captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
|
|
640
|
captureVideoPreviewLayer.frame = self.videoView.layer.bounds;
|
|
641
|
/* 填充模式
|
|
642
|
AVLayerVideoGravityResize
|
|
643
|
AVLayerVideoGravityResizeAspect (default)
|
|
644
|
AVLayerVideoGravityResizeAspectFill
|
|
645
|
*/
|
|
646
|
//有时候需要拍摄完整屏幕大小的时候可以修改这个
|
|
647
|
captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
|
|
648
|
// 如果预览图层和视频方向不一致,可以修改这个
|
|
649
|
captureVideoPreviewLayer.connection.videoOrientation = [movieOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation;
|
|
650
|
captureVideoPreviewLayer.position = CGPointMake(self.videoView.width*0.5,self.videoView.height*0.5);
|
|
651
|
|
|
652
|
// 显示在视图表面的图层
|
|
653
|
CALayer *layer = self.videoView.layer;
|
|
654
|
layer.masksToBounds = true;
|
|
655
|
[self.view layoutIfNeeded];
|
|
656
|
[layer addSublayer:captureVideoPreviewLayer];
|
|
657
|
}
|
|
658
|
|
|
659
|
-(void)pop
|
|
660
|
{
|
|
661
|
if (self.navigationController) {
|
|
662
|
[self.navigationController popViewControllerAnimated:YES];
|
|
663
|
}
|
|
664
|
}
|
|
665
|
|
|
666
|
- (void)showMsgWithTitle:(NSString *)title andContent:(NSString *)content
|
|
667
|
{
|
|
668
|
[[[UIAlertView alloc] initWithTitle:title message:content delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil] show];
|
|
669
|
}
|
|
670
|
#pragma mark - 点按手势
|
|
671
|
/**
|
|
672
|
* 添加点按手势,点按时聚焦
|
|
673
|
*/
|
|
674
|
-(void)addGenstureRecognizer{
|
|
675
|
|
|
676
|
UITapGestureRecognizer *singleTapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(singleTap:)];
|
|
677
|
singleTapGesture.numberOfTapsRequired = 1;
|
|
678
|
singleTapGesture.delaysTouchesBegan = YES;
|
|
679
|
[self.videoView addGestureRecognizer:singleTapGesture];
|
|
680
|
|
|
681
|
UITapGestureRecognizer *doubleTapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTap:)];
|
|
682
|
doubleTapGesture.numberOfTapsRequired = 2;
|
|
683
|
doubleTapGesture.delaysTouchesBegan = YES;
|
|
684
|
[self.videoView addGestureRecognizer:doubleTapGesture];
|
|
685
|
|
|
686
|
//双击手势执行失败才会执行单击手势
|
|
687
|
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
|
|
688
|
}
|
|
689
|
/*
|
|
690
|
单击
|
|
691
|
*/
|
|
692
|
-(void)singleTap:(UITapGestureRecognizer *)tapGesture{
|
|
693
|
CGPoint point = [tapGesture locationInView:self.videoView];
|
|
694
|
//将UI坐标转化为摄像头坐标,摄像头聚焦点范围0~1
|
|
695
|
CGPoint cameraPoint= [captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
|
|
696
|
// //光圈动画
|
|
697
|
// [self setFocusCursorAnimationWithPoint:point];
|
|
698
|
//更改设备属性
|
|
699
|
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
|
|
700
|
/*
|
|
701
|
@constant AVCaptureFocusModeLocked 锁定在当前焦距
|
|
702
|
@constant AVCaptureFocusModeAutoFocus 自动对焦一次,然后切换到焦距锁定
|
|
703
|
@constant AVCaptureFocusModeContinuousAutoFocus
|
|
704
|
*/
|
|
705
|
//当需要时,自动调整焦距
|
|
706
|
if ([captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
|
|
707
|
[captureDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
|
|
708
|
NSLog(@"聚焦模式修改为%zd",AVCaptureFocusModeContinuousAutoFocus);
|
|
709
|
}else{
|
|
710
|
NSLog(@"聚焦模式修改失败");
|
|
711
|
}
|
|
712
|
//聚焦点的位置
|
|
713
|
if ([captureDevice isFocusPointOfInterestSupported]) {
|
|
714
|
[captureDevice setFocusPointOfInterest:cameraPoint];
|
|
715
|
}
|
|
716
|
/*
|
|
717
|
@constant AVCaptureExposureModeLocked 曝光锁定在当前值
|
|
718
|
@constant AVCaptureExposureModeAutoExpose 曝光自动调整一次然后锁定
|
|
719
|
@constant AVCaptureExposureModeContinuousAutoExposure 曝光自动调整
|
|
720
|
@constant AVCaptureExposureModeCustom 曝光只根据设定的值来
|
|
721
|
*/
|
|
722
|
//曝光模式
|
|
723
|
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
|
|
724
|
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
|
|
725
|
}else{
|
|
726
|
NSLog(@"曝光模式修改失败");
|
|
727
|
}
|
|
728
|
//曝光点的位置
|
|
729
|
if ([captureDevice isExposurePointOfInterestSupported]) {
|
|
730
|
[captureDevice setExposurePointOfInterest:cameraPoint];
|
|
731
|
}
|
|
732
|
}];
|
|
733
|
}
|
|
734
|
|
|
735
|
/*
|
|
736
|
更改设备属性
|
|
737
|
*/
|
|
738
|
-(void)changeDevicePropertySafety:(void (^)(AVCaptureDevice *captureDevice))propertyChange{
|
|
739
|
AVCaptureDevice *captureDevice = [videoInput device];
|
|
740
|
NSError *error;
|
|
741
|
//注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁,意义是---进行修改期间,先锁定,防止多处同时修改
|
|
742
|
BOOL lockAcquired = [captureDevice lockForConfiguration:&error];
|
|
743
|
if (!lockAcquired) {
|
|
744
|
NSLog(@"锁定设备过程error,错误信息:%@",error.localizedDescription);
|
|
745
|
}else{
|
|
746
|
[captureSession beginConfiguration];
|
|
747
|
propertyChange(captureDevice);
|
|
748
|
[captureDevice unlockForConfiguration];
|
|
749
|
[captureSession commitConfiguration];
|
|
750
|
}
|
|
751
|
}
|
|
752
|
/*
|
|
753
|
双击:设置焦距
|
|
754
|
*/
|
|
755
|
-(void)doubleTap:(UITapGestureRecognizer *)tapGesture{
|
|
756
|
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
|
|
757
|
if (captureDevice.videoZoomFactor == 1.0) {
|
|
758
|
CGFloat current = 1.5;
|
|
759
|
if (current < captureDevice.activeFormat.videoMaxZoomFactor) {
|
|
760
|
[captureDevice rampToVideoZoomFactor:current withRate:10];
|
|
761
|
}
|
|
762
|
}else{
|
|
763
|
[captureDevice rampToVideoZoomFactor:1.0 withRate:10];
|
|
764
|
}
|
|
765
|
}];
|
|
766
|
}
|
|
767
|
|
|
768
|
#pragma mark - other
|
|
769
|
- (void)didReceiveMemoryWarning {
|
|
770
|
[super didReceiveMemoryWarning];
|
|
771
|
|
|
772
|
}
|
|
773
|
|
|
774
|
#pragma mark - 屏幕适配,(320,568)大小为标准
|
|
775
|
+(CGFloat)widthScale:(CGFloat)num{
|
|
776
|
if (deviceWidth==375) {
|
|
777
|
return 375*num/320;
|
|
778
|
}
|
|
779
|
else if (deviceWidth==414){
|
|
780
|
return 414*num/320;
|
|
781
|
}
|
|
782
|
else{//320
|
|
783
|
return num;
|
|
784
|
}
|
|
785
|
}
|
|
786
|
|
|
787
|
+(CGFloat)heightScale:(CGFloat)num{
|
|
788
|
|
|
789
|
if (deviceHeight==480) {
|
|
790
|
return 480*num/568;
|
|
791
|
}
|
|
792
|
else if (deviceHeight==667){
|
|
793
|
return 667*num/568;
|
|
794
|
}
|
|
795
|
else if (deviceHeight==736){//plus
|
|
796
|
return 736*num/568;
|
|
797
|
}
|
|
798
|
else if (deviceHeight==812){//X
|
|
799
|
return 812*num/568;
|
|
800
|
}
|
|
801
|
else{//568
|
|
802
|
return num;
|
|
803
|
}
|
|
804
|
}
|
|
805
|
|
|
806
|
@end
|