admin 2025-12-01 23:05:15 加拿大女足世界杯

短视频录制

项目中集成了短视频录制,UI和映客很像,在这里贴下代码搬运流程[迷之微笑]

录制.png

播放.png

基于系统提供框架

#import

初始化必要属性

/**

* AVCaptureSession对象来执行输入设备和输出设备之间的数据传递

*/

@property (nonatomic, strong) AVCaptureSession* session;

/**

* 视频输入设备

*/

@property (nonatomic, strong) AVCaptureDeviceInput* videoInput;

/**

* 声音输入

*/

@property (nonatomic, strong) AVCaptureDeviceInput* audioInput;

/**

* 视频输出流

*/

@property(nonatomic,strong)AVCaptureMovieFileOutput *movieFileOutput;

/**

* 预览图层

*/

@property (nonatomic, strong) AVCaptureVideoPreviewLayer* previewLayer;

#pragma mark - private mehtod

- (void)initAVCaptureSession{

self.session = [[AVCaptureSession alloc] init];

//这里根据需要设置 可以设置4K

self.session.sessionPreset = AVCaptureSessionPreset1280x720;

NSError *error;

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

//更改这个设置的时候必须先锁定设备,修改完后再解锁,否则崩溃

[device lockForConfiguration:nil];

//设置闪光灯为自动

[device setFlashMode:AVCaptureFlashModeAuto];

[device unlockForConfiguration];

self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];

self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:nil];

if (error) {

DLog(@"error%@",error);

}

self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];

if ([self.session canAddInput:self.videoInput]) {

[self.session addInput:self.videoInput];

}

if ([self.session canAddInput:self.audioInput]) {

[self.session addInput:self.audioInput];

}

if ([self.session canAddOutput:self.movieFileOutput]) {

[self.session addOutput:self.movieFileOutput];

}

[self basicViewSet];

}

开始录制前先要获取下权限

AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];

if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)

{

return;

}

//判断用户是否允许访问麦克风权限

authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];

if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)

{

//无权限

[SVProgressHUD showWithStatus:@"未授予麦克风权限"];

return;

}

到了重点了-开始录制

- (void)startSession{

if (![self.session isRunning]) {

[self.session startRunning];

}

}

AVCaptureConnection *movieConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];

AVCaptureVideoOrientation avcaptureOrientation = AVCaptureVideoOrientationPortrait;

[movieConnection setVideoOrientation:avcaptureOrientation];

[movieConnection setVideoScaleAndCropFactor:1.0];

// 保存按URL

self.url = [[RAFileManager defaultManager] filePathUrlWithUrl:[self getVideoSaveFilePathString]];

[self.movieFileOutput startRecordingToOutputFileURL:self.url recordingDelegate:self];

按路径保存的方法是自己写的

- (NSURL*)filePathUrlWithUrl:(NSString*)url

{

return [NSURL fileURLWithPath:[self filePathWithUrl:url]];

}

呵呵 其实是调用的系统方法

到这里录制的部分已经完了,视频已经按路径保存了

如果录制完成后就做些操作的话可以用这个

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{}

这个代理

解释说是 调用此方法时,文件输出已经写完所有数据到一个文件的记录了。。。来自某道翻译

怪我咯.jpg

就先这样吧

除了视频的压缩上传、反复播放、圆环动画就没有了呼呼呼

有机会再接着搬。。。

老板要加短拍美颜-_-!,我就把我原生的代码都一块搬过来,做个纪念

#import "XNLMakVideoController.h"

#import

#import

#import "XNLVideoPlayerController.h"

#import "RAFileManager.h"

#define TIMER_INTERVAL 0.05 //定时器间隔

#define VIDEO_RECORDER_MAX_TIME 60 //视频最大时长 (单位/秒)

#define VIDEO_RECORDER_MIN_TIME 30 //最短视频时长 (单位/秒)

@interface XNLMakVideoController ()

@property (nonatomic) dispatch_queue_t sessionQueue;

/**

* AVCaptureSession对象来执行输入设备和输出设备之间的数据传递

*/

@property (nonatomic, strong) AVCaptureSession* session;

/**

* 视频输入设备

*/

@property (nonatomic, strong) AVCaptureDeviceInput* videoInput;

/**

* 声音输入

*/

@property (nonatomic, strong) AVCaptureDeviceInput* audioInput;

/**

* 视频输出流

*/

@property(nonatomic,strong)AVCaptureMovieFileOutput *movieFileOutput;

/**

* 预览图层

*/

@property (nonatomic, strong) AVCaptureVideoPreviewLayer* previewLayer;

/**

* 记录录制时间

*/

@property (nonatomic, strong) NSTimer* timer;

/**

录制完成的视频路径

*/

@property (nonatomic,strong)NSURL *url;

/**

开始View

*/

@property (nonatomic,strong) UIView *begView;

/**

圆环的底环及填充环

*/

@property (strong, nonatomic)CAShapeLayer *ovalSShapeLayer;

@property (strong, nonatomic)CAShapeLayer *redShapeLayer;

@property (nonatomic,strong)UIView *centerCircle;// 中心圆

@end

@implementation XNLMakVideoController{

//时间长度

CGFloat timeLength;

}

- (void)viewWillAppear:(BOOL)animated{

[super viewWillAppear:YES];

[self startSession];

//开启定时器

[self.timer setFireDate:[NSDate distantPast]];

}

//页面消失,进入后台不显示该页面,关闭定时器

-(void)viewDidDisappear:(BOOL)animated

{

[super viewDidDisappear:animated];

//关闭定时器

[self.timer setFireDate:[NSDate distantFuture]];

}

- (void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loading the view.

[self initAVCaptureSession];

}

#pragma mark - private mehtod

- (void)initAVCaptureSession{

self.session = [[AVCaptureSession alloc] init];

//这里根据需要设置 可以设置4K

if ([self.session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//设置分辨率

self.session.sessionPreset = AVCaptureSessionPreset1280x720;

}

NSError *error;

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

//更改这个设置的时候必须先锁定设备,修改完后再解锁,否则崩溃

[device lockForConfiguration:nil];

//设置闪光灯为自动

[device setFlashMode:AVCaptureFlashModeAuto];

[device unlockForConfiguration];

self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];

self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:nil];

if (error) {

DLog(@"error%@",error);

}

self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];

//它的默认值就是10秒。将这个值禁用后就好了--- 坑我不轻-_-!

self.movieFileOutput.movieFragmentInterval = kCMTimeInvalid;

if ([self.session canAddInput:self.videoInput]) {

[self.session addInput:self.videoInput];

}

if ([self.session canAddInput:self.audioInput]) {

[self.session addInput:self.audioInput];

}

if ([self.session canAddOutput:self.movieFileOutput]) {

[self.session addOutput:self.movieFileOutput];

}

[self basicViewSet];

}

- (void)startSession{

if (![self.session isRunning]) {

[self.session startRunning];

}

}

- (void)stopSession{

if ([self.session isRunning]) {

[self.session stopRunning];

}

}

#pragma mark - 视频输出

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{

if (CMTimeGetSeconds(captureOutput.recordedDuration) < VIDEO_RECORDER_MIN_TIME) {

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"时间少于30s,不予保存"

message:nil

delegate:self

cancelButtonTitle:@"确定"

otherButtonTitles:nil, nil];

[alert show];

return;

}

WS(weakSelf);

XNLVideoPlayerController *playerVC = [XNLVideoPlayerController new];

playerVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;

playerVC.url = self.url;

playerVC.videoURL = ^(){

// 圆环重置

weakSelf.redShapeLayer.strokeEnd = 0;

weakSelf.centerCircle.backgroundColor = kRGBA(255, 255, 255, 0.8);

// 删除视频

[[RAFileManager defaultManager] removeFileWithUrl:weakSelf.url block:^(BOOL success) {

DLog(@"----删除视频-----success-----%d",success);

}];

};

[self presentViewController:playerVC animated:NO completion:^{}];

}

#pragma mark -- 定时器设置

- (void)timerFired{

timeLength = 0;

self.timer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(timerRecord) userInfo:nil repeats:YES];

}

- (void)timerStop{

if ([self.timer isValid]) {

[self.timer invalidate];

self.timer = nil;

}

}

#pragma mark - response method 录制按钮点击触发

- (void)takeVideoButtonPress:(UILongPressGestureRecognizer *)sender {

AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];

if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)

{

[SMGlobalMethod showViewCenter:kKeyWindow.center message:@"未授予摄像头权限"];

return;

}

//判断用户是否允许访问麦克风权限

authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];

if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)

{

//无权限

[SVProgressHUD showWithStatus:@"未授予麦克风权限"];

return;

}

switch (sender.state) {

case UIGestureRecognizerStateBegan:

[self startVideoRecorder];

break;

case UIGestureRecognizerStateCancelled:

[self stopVideoRecorder];

break;

case UIGestureRecognizerStateEnded:

[self stopVideoRecorder];

break;

case UIGestureRecognizerStateFailed:

[self stopVideoRecorder];

break;

default:

break;

}

}

#pragma 视频名以当前日期为名

- (NSString*)getVideoSaveFilePathString

{

NSDateFormatter* formatter = [[NSDateFormatter alloc] init];

formatter.dateFormat = @"yyyyMMddHHmmss";

NSString* nowTimeStr = [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0]];

return nowTimeStr;

}

#pragma mark 开始录制和结束录制

- (void)startVideoRecorder{

AVCaptureConnection *movieConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];

AVCaptureVideoOrientation avcaptureOrientation = AVCaptureVideoOrientationPortrait;

[movieConnection setVideoOrientation:avcaptureOrientation];

[movieConnection setVideoScaleAndCropFactor:1.0];

// 保存按URL

self.url = [[RAFileManager defaultManager] filePathUrlWithUrl:[self getVideoSaveFilePathString]];

[self.movieFileOutput startRecordingToOutputFileURL:self.url recordingDelegate:self];

// 圆环弹性动画

[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:15 options:UIViewAnimationOptionLayoutSubviews animations:^{

self.begView.transform = CGAffineTransformScale(self.begView.transform, 1.2, 1.2);

} completion:^(BOOL finished) {

}];

self.redShapeLayer.strokeEnd = 0;

[self timerFired];

}

// 录制结束后的方法

- (void)stopVideoRecorder{

[self.movieFileOutput stopRecording];

[self timerStop];

self.begView.transform = CGAffineTransformIdentity;

//strokeStart为0时是从3点钟方向开始,故将其旋转270度从12点钟方向开始

self.begView.transform = CGAffineTransformMakeRotation((M_PI * 2) / 4 * 3);

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

// 定时器调用方法

- (void)timerRecord{

timeLength += (TIMER_INTERVAL/VIDEO_RECORDER_MAX_TIME);

self.redShapeLayer.strokeEnd = timeLength;

self.centerCircle.backgroundColor = kRGBA(255, 255*(1-timeLength), 255*(1-timeLength), 0.8);

if (timeLength >= 1.0) {

[self stopVideoRecorder];

[self timerStop];

}

}

#pragma mark -- 前后摄像头切换

- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

for (AVCaptureDevice *device in devices) {

if ([device position] == position) {

return device;

}

}

return nil;

}

- (void)swapFrontAndBackCameras {

// Assume the session is already running

NSArray *inputs = self.session.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 cameraWithPosition:AVCaptureDevicePositionBack];

else

newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];

newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];

// beginConfiguration ensures that pending changes are not applied immediately

[self.session beginConfiguration];

[self.session removeInput:input];

[self.session addInput:newInput];

// Changes take effect once the outermost commitConfiguration is invoked.

[self.session commitConfiguration];

break;

}

}

}

#pragma mark -- 关闭

- (void)shutDownClick

{

[self stopSession];

[self dismissViewControllerAnimated:YES completion:nil];

}

#pragma mark -- 几个控件的布局

- (void)basicViewSet

{

//初始化预览图层

self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];

// _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];

self.previewLayer.frame = CGRectMake(0, 0,kScreenWidth, kScreenHeight);

[self.view.layer insertSublayer:self.previewLayer atIndex:0];

// 圆环、圆的父View

self.begView = [[UIView alloc]initWithFrame:CGRectMake(kScreenWidth/2-48*kScreenW, kScreenHeight-130*kScreenH, 96*kScreenW, 96*kScreenW)];

[self.view addSubview:self.begView];

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(takeVideoButtonPress:)];

[self.begView addGestureRecognizer:longPress];

// 圆环

[self creatLayer];

// 摄像头切换

UIButton *fan = [UIButton buttonWithType:UIButtonTypeCustom];

[fan setImage:kGetImage(@"shortvideo_icon_down_switch") forState:UIControlStateNormal];

[self.view addSubview:fan];

[fan mas_makeConstraints:^(MASConstraintMaker *make) {

make.centerX.mas_equalTo(self.view).mas_offset(kScreenWidth/2 - 40*kScreenW);

make.bottom.mas_equalTo(self.view.mas_bottom).mas_offset(-20*kScreenH);

make.size.mas_equalTo(CGSizeMake(60*kScreenW, 60*kScreenW));

}];

[fan addTarget:self action:@selector(swapFrontAndBackCameras) forControlEvents:UIControlEventTouchUpInside];

// 关闭

UIButton *shutDown = [UIButton buttonWithType:UIButtonTypeCustom];

[shutDown setImage:kGetImage(@"XNLlive_yemian5_close") forState:UIControlStateNormal];

[self.view addSubview:shutDown];

[shutDown mas_makeConstraints:^(MASConstraintMaker *make) {

make.centerX.mas_equalTo(self.view).mas_offset(kScreenWidth/2 - 40*kScreenW);

make.top.mas_equalTo(self.view.mas_top).mas_offset(20*kScreenW);

make.size.mas_equalTo(CGSizeMake(50*kScreenW, 50*kScreenW));

}];

[shutDown addTarget:self action:@selector(shutDownClick) forControlEvents:UIControlEventTouchUpInside];

}

// 圆环的设置

-(void)creatLayer

{

//添加底层白色圆环

self.ovalSShapeLayer = [[CAShapeLayer alloc]init];

//圆环颜色

self.ovalSShapeLayer.strokeColor = DEFAULT_BACKGROUND_COLOR.CGColor;

//内部填充颜色

self.ovalSShapeLayer.fillColor = [UIColor clearColor].CGColor;

//线宽

self.ovalSShapeLayer.lineWidth = 6*kScreenW;

//半径

CGFloat ovalRadius = self.begView.frame.size.height/2;

self.ovalSShapeLayer.path =[UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.begView.frame.size.width/2 - ovalRadius, self.begView.frame.size.height/2 - ovalRadius, ovalRadius * 2, ovalRadius * 2)].CGPath;

self.ovalSShapeLayer.lineCap = kCALineCapRound;

[self.begView.layer addSublayer:self.ovalSShapeLayer];

//添加填充圆环

self.redShapeLayer = [[CAShapeLayer alloc]init];

self.redShapeLayer.strokeColor = [UIColor redColor].CGColor;

self.redShapeLayer.fillColor = [UIColor clearColor].CGColor;

self.redShapeLayer.lineWidth = 6*kScreenW;

self.redShapeLayer.cornerRadius = 3.0*kScreenW;

self.redShapeLayer.lineCap = kCALineCapRound;

self.redShapeLayer.path =[UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.begView.frame.size.width/2 - ovalRadius, self.begView.frame.size.height/2 - ovalRadius, ovalRadius * 2, ovalRadius * 2)].CGPath;

self.redShapeLayer.strokeStart = 0;

self.redShapeLayer.strokeEnd = 0;

[self.begView.layer addSublayer:self.redShapeLayer];

// 中心圆

self.centerCircle = [UIView new];

self.centerCircle.backgroundColor = kRGBA(255, 255, 255, 0.8);

self.centerCircle.layer.cornerRadius = 40*kScreenW;

self.centerCircle.clipsToBounds = YES;

[self.begView addSubview:self.centerCircle];

[self.centerCircle mas_makeConstraints:^(MASConstraintMaker *make) {

make.center.mas_equalTo(self.begView);

make.size.mas_equalTo(CGSizeMake(80*kScreenW, 80*kScreenW));

}];

self.begView.transform = CGAffineTransformMakeRotation((M_PI * 2) / 4 * 3);

}

其中应该有用到一些大神的代码,像RAFileManager.h,如有不对请联系我