添加ios相关代码
This commit is contained in:
parent
2ee0c1a205
commit
3e89107a9f
plugin.xml
src/ios/SGRecord
30
plugin.xml
30
plugin.xml
@ -40,5 +40,35 @@
|
||||
</feature>
|
||||
</config-file>
|
||||
<source-file src="src/ios/CapturePlugin.m"/>
|
||||
<header-file src="src/ios/SGRecord/SGMotionManager.h"/>
|
||||
<source-file src="src/ios/SGRecord/SGMotionManager.m"/>
|
||||
<header-file src="src/ios/SGRecord/SGRecordEncoder.h"/>
|
||||
<source-file src="src/ios/SGRecord/SGRecordEncoder.m"/>
|
||||
<header-file src="src/ios/SGRecord/SGRecordManager.h"/>
|
||||
<source-file src="src/ios/SGRecord/SGRecordManager.m"/>
|
||||
<header-file src="src/ios/SGRecord/SGRecordProgressView.h"/>
|
||||
<source-file src="src/ios/SGRecord/SGRecordProgressView.m"/>
|
||||
<header-file src="src/ios/SGRecord/SGRecordSuccessPreview.h"/>
|
||||
<source-file src="src/ios/SGRecord/SGRecordSuccessPreview.m"/>
|
||||
<header-file src="src/ios/SGRecord/SGRecordViewController.h"/>
|
||||
<source-file src="src/ios/SGRecord/SGRecordViewController.m"/>
|
||||
<header-file src="src/ios/SGRecord/UIButton+Convenience.h"/>
|
||||
<source-file src="src/ios/SGRecord/UIButton+Convenience.m"/>
|
||||
<framework src="AVFoundation.framework"/>
|
||||
<framework src="AVKit.framework"/>
|
||||
<framework src="CoreMotion.framework"/>
|
||||
<framework src="MobileCoreServices.framework"/>
|
||||
<preference name="CAMERA_USAGE_DESCRIPTION" default="This app requires access to your camera to take pictures" />
|
||||
<config-file target="*-Info.plist" parent="NSCameraUsageDescription">
|
||||
<string>$CAMERA_USAGE_DESCRIPTION</string>
|
||||
</config-file>
|
||||
<preference name="MICROPHONE_USAGE_DESCRIPTION" default="This app requires access to your microphone to take pictures" />
|
||||
<config-file target="*-Info.plist" parent="NSMicrophoneUsageDescription">
|
||||
<string>$MICROPHONE_USAGE_DESCRIPTION</string>
|
||||
</config-file>
|
||||
<preference name="PHOTO_LIBRARY_ADD_USAGE_DESCRIPTION" default="This app requires access to your photo library to save your pictures" />
|
||||
<config-file target="*-Info.plist" parent="NSPhotoLibraryAddUsageDescription">
|
||||
<string>$PHOTO_LIBRARY_ADD_USAGE_DESCRIPTION</string>
|
||||
</config-file>
|
||||
</platform>
|
||||
</plugin>
|
||||
|
44
src/ios/SGRecord/SGMotionManager.h
Normal file
44
src/ios/SGRecord/SGMotionManager.h
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// SGMotionManager.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/24.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
@protocol SGMotionManagerDeviceOrientationDelegate<NSObject>
|
||||
@optional
|
||||
- (void)motionManagerDeviceOrientation:(UIDeviceOrientation)deviceOrientation;
|
||||
@end
|
||||
@interface SGMotionManager : NSObject
|
||||
@property (nonatomic ,assign) UIDeviceOrientation deviceOrientation;
|
||||
@property (nonatomic ,assign) AVCaptureVideoOrientation videoOrientation;
|
||||
@property (nonatomic ,weak) id<SGMotionManagerDeviceOrientationDelegate>delegate;
|
||||
|
||||
/**
|
||||
获取SGMotionManager实例
|
||||
|
||||
@return 返回SGMotionManager实例
|
||||
*/
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
/**
|
||||
开始方向监测
|
||||
*/
|
||||
- (void)startDeviceMotionUpdates;
|
||||
|
||||
/**
|
||||
结束方向监测
|
||||
*/
|
||||
- (void)stopDeviceMotionUpdates;
|
||||
|
||||
/**
|
||||
设置设备取向
|
||||
|
||||
@return 返回视频捕捉方向
|
||||
*/
|
||||
- (AVCaptureVideoOrientation)currentVideoOrientation;
|
||||
@end
|
106
src/ios/SGRecord/SGMotionManager.m
Normal file
106
src/ios/SGRecord/SGMotionManager.m
Normal file
@ -0,0 +1,106 @@
|
||||
//
|
||||
// SGMotionManager.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/24.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SGMotionManager.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#define MOTION_UPDATE_INTERVAL 1/15.0
|
||||
@interface SGMotionManager()
|
||||
@property (nonatomic ,strong) CMMotionManager *motionManager;
|
||||
@end
|
||||
@implementation SGMotionManager
|
||||
+ (instancetype)sharedManager{
|
||||
static SGMotionManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[SGMotionManager alloc]init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
- (instancetype)init{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self motionManager];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (CMMotionManager *)motionManager{
|
||||
if (!_motionManager) {
|
||||
_motionManager = [[CMMotionManager alloc]init];
|
||||
_motionManager.deviceMotionUpdateInterval = MOTION_UPDATE_INTERVAL;
|
||||
}
|
||||
return _motionManager;
|
||||
}
|
||||
// 开始
|
||||
- (void)startDeviceMotionUpdates{
|
||||
if (_motionManager.deviceMotionAvailable) {
|
||||
[_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
|
||||
[self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
|
||||
}];
|
||||
}
|
||||
}
|
||||
// 结束
|
||||
- (void)stopDeviceMotionUpdates{
|
||||
[_motionManager stopDeviceMotionUpdates];
|
||||
}
|
||||
- (void)handleDeviceMotion:(CMDeviceMotion *)deviceMotion{
|
||||
double x = deviceMotion.gravity.x;
|
||||
double y = deviceMotion.gravity.y;
|
||||
if (fabs(y) >= fabs(x))
|
||||
{
|
||||
if (y >= 0){
|
||||
_deviceOrientation = UIDeviceOrientationPortraitUpsideDown;
|
||||
_videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
|
||||
//NSLog(@"UIDeviceOrientationPortraitUpsideDown--AVCaptureVideoOrientationPortraitUpsideDown");
|
||||
}
|
||||
else{
|
||||
_deviceOrientation = UIDeviceOrientationPortrait;
|
||||
_videoOrientation = AVCaptureVideoOrientationPortrait;
|
||||
//NSLog(@"UIDeviceOrientationPortrait--AVCaptureVideoOrientationPortrait");
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (x >= 0){
|
||||
_deviceOrientation = UIDeviceOrientationLandscapeRight;
|
||||
_videoOrientation = AVCaptureVideoOrientationLandscapeRight;
|
||||
//NSLog(@"UIDeviceOrientationLandscapeRight--AVCaptureVideoOrientationLandscapeRight");
|
||||
}
|
||||
else{
|
||||
_deviceOrientation = UIDeviceOrientationLandscapeLeft;
|
||||
_videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
|
||||
// NSLog(@"UIDeviceOrientationLandscapeLeft--AVCaptureVideoOrientationLandscapeLeft");
|
||||
}
|
||||
}
|
||||
;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(motionManagerDeviceOrientation:)]) {
|
||||
[_delegate motionManagerDeviceOrientation:_deviceOrientation];
|
||||
}
|
||||
}
|
||||
// 调整设备取向
|
||||
- (AVCaptureVideoOrientation)currentVideoOrientation{
|
||||
AVCaptureVideoOrientation orientation;
|
||||
switch ([SGMotionManager sharedManager].deviceOrientation) {
|
||||
case UIDeviceOrientationPortrait:
|
||||
orientation = AVCaptureVideoOrientationPortrait;
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeRight:
|
||||
orientation = AVCaptureVideoOrientationLandscapeLeft;
|
||||
break;
|
||||
case UIDeviceOrientationPortraitUpsideDown:
|
||||
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
|
||||
break;
|
||||
default:
|
||||
orientation = AVCaptureVideoOrientationLandscapeRight;
|
||||
break;
|
||||
}
|
||||
return orientation;
|
||||
}
|
||||
- (void)dealloc{
|
||||
NSLog(@"%s",__func__);
|
||||
}
|
||||
|
||||
@end
|
63
src/ios/SGRecord/SGRecordEncoder.h
Normal file
63
src/ios/SGRecord/SGRecordEncoder.h
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// SGRecordEncoder.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/19.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
@interface SGRecordEncoder : NSObject
|
||||
@property (nonatomic, readonly) NSString *path;
|
||||
|
||||
/**
|
||||
SGRecordEncoder遍历构造器
|
||||
|
||||
@param path 媒体存发路径
|
||||
@param cy 视频分辨率的高
|
||||
@param cx 视频分辨率的宽
|
||||
@param ch 音频通道
|
||||
@param rate 音频的采样比率
|
||||
@return SGRecordEncoder实例
|
||||
*/
|
||||
+ (SGRecordEncoder*)encoderForPath:(NSString*) path Height:(NSInteger) cy width:(NSInteger) cx channels: (int) ch samples:(Float64) rate;
|
||||
|
||||
/**
|
||||
初始化方法
|
||||
|
||||
@param path 媒体存发路径
|
||||
@param cy 视频分辨率的高
|
||||
@param cx 视频分辨率的宽
|
||||
@param ch 音频通道
|
||||
@param rate 音频的采样率
|
||||
@return SGRecordEncoder实例
|
||||
*/
|
||||
- (instancetype)initPath:(NSString*)path Height:(NSInteger)cy width:(NSInteger)cx channels: (int)ch samples:(Float64)rate;
|
||||
|
||||
/**
|
||||
完成视频录制时调用
|
||||
|
||||
@param handler 完成的回调block
|
||||
*/
|
||||
- (void)finishWithCompletionHandler:(void (^)(void))handler;
|
||||
|
||||
/**
|
||||
* 通过这个方法写入数据
|
||||
*
|
||||
* @param sampleBuffer 写入的数据
|
||||
* @param isVideo 是否写入的是视频
|
||||
*
|
||||
* @return 写入是否成功
|
||||
*/
|
||||
|
||||
/**
|
||||
写入数据
|
||||
|
||||
@param sampleBuffer 写入的数据
|
||||
@param isVideo 是否写入的是视频
|
||||
@return 是否写入成功
|
||||
*/
|
||||
- (BOOL)encodeFrame:(CMSampleBufferRef)sampleBuffer isVideo:(BOOL)isVideo;
|
||||
@end
|
168
src/ios/SGRecord/SGRecordEncoder.m
Normal file
168
src/ios/SGRecord/SGRecordEncoder.m
Normal file
@ -0,0 +1,168 @@
|
||||
//
|
||||
// SGRecordEncoder.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/19.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SGRecordEncoder.h"
|
||||
#import "SGMotionManager.h"
|
||||
@interface SGRecordEncoder()
|
||||
@property (nonatomic, strong) AVAssetWriter *writer;//媒体写入对象
|
||||
@property (nonatomic, strong) AVAssetWriterInput *videoInput;//视频写入
|
||||
@property (nonatomic, strong) AVAssetWriterInput *audioInput;//音频写入
|
||||
@property (nonatomic, strong) NSString *path;//写入路径
|
||||
@end
|
||||
@implementation SGRecordEncoder
|
||||
|
||||
+ (SGRecordEncoder*)encoderForPath:(NSString*) path Height:(NSInteger) cy width:(NSInteger) cx channels: (int) ch samples:(Float64) rate{
|
||||
SGRecordEncoder* enc = [SGRecordEncoder alloc];
|
||||
return [enc initPath:path Height:cy width:cx channels:ch samples:rate];
|
||||
}
|
||||
|
||||
- (instancetype)initPath:(NSString*)path Height:(NSInteger)cy width:(NSInteger)cx channels:(int)ch samples:(Float64) rate{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.path = path;
|
||||
//先把路径下的文件给删除掉,保证录制的文件是最新的
|
||||
[[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
|
||||
NSURL* url = [NSURL fileURLWithPath:self.path];
|
||||
//初始化写入媒体类型为MP4类型
|
||||
_writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil];
|
||||
//使其更适合在网络上播放
|
||||
_writer.shouldOptimizeForNetworkUse = YES;
|
||||
//初始化视频输出
|
||||
[self initVideoInputHeight:cy width:cx];
|
||||
//确保采集到rate和ch
|
||||
if (rate != 0 && ch != 0) {
|
||||
//初始化音频输出
|
||||
[self initAudioInputChannels:ch samples:rate];
|
||||
}
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//初始化视频输入
|
||||
- (void)initVideoInputHeight:(NSInteger)cy width:(NSInteger)cx {
|
||||
//录制视频的一些配置,分辨率,编码方式等等
|
||||
NSInteger numPixels = cx * cy;
|
||||
//每像素比特
|
||||
CGFloat bitsPerPixel = 6.0;
|
||||
NSInteger bitsPerSecond = numPixels * bitsPerPixel;
|
||||
|
||||
// 码率和帧率设置
|
||||
NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey:@(bitsPerSecond),
|
||||
AVVideoExpectedSourceFrameRateKey:@(30),
|
||||
AVVideoMaxKeyFrameIntervalKey:@(30),
|
||||
AVVideoProfileLevelKey:AVVideoProfileLevelH264BaselineAutoLevel };
|
||||
NSDictionary* settings = @{AVVideoCodecKey:AVVideoCodecH264,
|
||||
AVVideoScalingModeKey:AVVideoScalingModeResizeAspectFill,
|
||||
AVVideoWidthKey:@(cx),
|
||||
AVVideoHeightKey:@(cy),
|
||||
AVVideoCompressionPropertiesKey:compressionProperties };
|
||||
|
||||
//初始化视频写入类
|
||||
_videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
|
||||
_videoInput.transform = [self transformFromCurrentVideoOrientationToOrientation:AVCaptureVideoOrientationPortrait];
|
||||
//表明输入是否应该调整其处理为实时数据源的数据
|
||||
_videoInput.expectsMediaDataInRealTime = YES;
|
||||
//将视频输入源加入
|
||||
if ([_writer canAddInput:_videoInput]) {
|
||||
[_writer addInput:_videoInput];
|
||||
}
|
||||
}
|
||||
|
||||
//初始化音频输入
|
||||
- (void)initAudioInputChannels:(int)ch samples:(Float64)rate {
|
||||
//音频的一些配置包括音频各种这里为AAC,音频通道、采样率和音频的比特率
|
||||
NSDictionary *settings = @{AVEncoderBitRatePerChannelKey:@(28000),
|
||||
AVFormatIDKey:@(kAudioFormatMPEG4AAC),
|
||||
AVNumberOfChannelsKey:@(1),
|
||||
AVSampleRateKey:@(22050) };
|
||||
//初始化音频写入类
|
||||
_audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:settings];
|
||||
//表明输入是否应该调整其处理为实时数据源的数据
|
||||
_audioInput.expectsMediaDataInRealTime = YES;
|
||||
//将音频输入源加入
|
||||
[_writer addInput:_audioInput];
|
||||
}
|
||||
|
||||
//完成视频录制时调用
|
||||
- (void)finishWithCompletionHandler:(void (^)(void))handler {
|
||||
[_writer finishWritingWithCompletionHandler: handler];
|
||||
}
|
||||
|
||||
//通过这个方法写入数据
|
||||
- (BOOL)encodeFrame:(CMSampleBufferRef) sampleBuffer isVideo:(BOOL)isVideo {
|
||||
//数据是否准备写入
|
||||
if (CMSampleBufferDataIsReady(sampleBuffer)) {
|
||||
//写入状态为未知,保证视频先写入
|
||||
if (_writer.status == AVAssetWriterStatusUnknown && isVideo) {
|
||||
//获取开始写入的CMTime
|
||||
CMTime startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
|
||||
//开始写入
|
||||
[_writer startWriting];
|
||||
[_writer startSessionAtSourceTime:startTime];
|
||||
}
|
||||
//写入失败
|
||||
if (_writer.status == AVAssetWriterStatusFailed) {
|
||||
NSLog(@"writer error %@", _writer.error.localizedDescription);
|
||||
return NO;
|
||||
}
|
||||
//判断是否是视频
|
||||
if (isVideo) {
|
||||
//视频输入是否准备接受更多的媒体数据
|
||||
if (_videoInput.readyForMoreMediaData == YES) {
|
||||
//拼接数据
|
||||
[_videoInput appendSampleBuffer:sampleBuffer];
|
||||
return YES;
|
||||
}
|
||||
}else {
|
||||
//音频输入是否准备接受更多的媒体数据
|
||||
if (_audioInput.readyForMoreMediaData) {
|
||||
//拼接数据
|
||||
[_audioInput appendSampleBuffer:sampleBuffer];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
//旋转视频方向
|
||||
- (CGAffineTransform)transformFromCurrentVideoOrientationToOrientation:(AVCaptureVideoOrientation)orientation
|
||||
{
|
||||
CGFloat orientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:orientation];
|
||||
CGFloat videoOrientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:[SGMotionManager sharedManager].videoOrientation];
|
||||
|
||||
CGFloat angleOffset;
|
||||
angleOffset = orientationAngleOffset - videoOrientationAngleOffset;
|
||||
CGAffineTransform transform = CGAffineTransformMakeRotation(angleOffset);
|
||||
return transform;
|
||||
}
|
||||
|
||||
- (CGFloat)angleOffsetFromPortraitOrientationToOrientation:(AVCaptureVideoOrientation)orientation
|
||||
{
|
||||
CGFloat angle = 0.0;
|
||||
switch (orientation)
|
||||
{
|
||||
case AVCaptureVideoOrientationPortrait:
|
||||
angle = 0.0;
|
||||
break;
|
||||
case AVCaptureVideoOrientationPortraitUpsideDown:
|
||||
angle = M_PI;
|
||||
break;
|
||||
case AVCaptureVideoOrientationLandscapeRight:
|
||||
angle = -M_PI_2;
|
||||
break;
|
||||
case AVCaptureVideoOrientationLandscapeLeft:
|
||||
angle = M_PI_2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
@end
|
107
src/ios/SGRecord/SGRecordManager.h
Normal file
107
src/ios/SGRecord/SGRecordManager.h
Normal file
@ -0,0 +1,107 @@
|
||||
//
|
||||
// SGRecordManager.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/19.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVCaptureVideoPreviewLayer.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
@protocol SGRecordEngineDelegate <NSObject>
|
||||
|
||||
- (void)recordProgress:(CGFloat)progress;
|
||||
|
||||
@end
|
||||
@interface SGRecordManager : NSObject
|
||||
@property (atomic, assign, readonly) BOOL isCapturing;//正在录制
|
||||
@property (atomic, assign, readonly) BOOL isPaused;//是否暂停
|
||||
@property (atomic, assign, readonly) CGFloat currentRecordTime;//当前录制时间
|
||||
@property (atomic, assign) CGFloat maxRecordTime;//录制最长时间
|
||||
@property (weak, nonatomic) id<SGRecordEngineDelegate>delegate;
|
||||
@property (atomic, strong) NSString *videoPath;//视频路径
|
||||
@property (nonatomic, readonly,getter=isRunning) BOOL isRunning; // 捕捉图像
|
||||
|
||||
/**
|
||||
捕获到的视频呈现的layer
|
||||
|
||||
@return AVCaptureVideoPreviewLayer
|
||||
*/
|
||||
- (AVCaptureVideoPreviewLayer *)previewLayer;
|
||||
|
||||
/**
|
||||
拍照
|
||||
|
||||
@param callback 返回图片
|
||||
*/
|
||||
- (void)takePhoto:(void(^)(UIImage *image))callback;
|
||||
|
||||
/**
|
||||
开启摄像头
|
||||
*/
|
||||
- (void)startUp;
|
||||
|
||||
/**
|
||||
关闭摄像头
|
||||
*/
|
||||
- (void)shutdown;
|
||||
|
||||
/**
|
||||
开始录制
|
||||
*/
|
||||
- (void) startCapture;
|
||||
|
||||
/**
|
||||
暂停录制
|
||||
*/
|
||||
- (void) pauseCapture;
|
||||
|
||||
/**
|
||||
停止录制
|
||||
|
||||
@param isSuccess 是否是成功的录制操作
|
||||
@param handler 返回视频第一帧图像和视频在磁盘中的路径
|
||||
*/
|
||||
- (void) stopCaptureWithStatus:(BOOL)isSuccess handler:(void (^)(UIImage *movieImage,NSString *path))handler;
|
||||
|
||||
/**
|
||||
继续录制
|
||||
*/
|
||||
- (void) resumeCapture;
|
||||
|
||||
/**
|
||||
开启闪光灯
|
||||
*/
|
||||
- (void)openFlashLight;
|
||||
|
||||
/**
|
||||
关闭闪光灯
|
||||
*/
|
||||
- (void)closeFlashLight;
|
||||
|
||||
/**
|
||||
切换前后置摄像头
|
||||
|
||||
@param isFront YES: 前置, NO: 后置
|
||||
*/
|
||||
- (void)changeCameraInputDeviceisFront:(BOOL)isFront;
|
||||
|
||||
/**
|
||||
将mov的视频转成mp4
|
||||
|
||||
@param mediaURL 视频地址
|
||||
@param handler 返回视频第一帧图像和视频在磁盘中的路径
|
||||
*/
|
||||
- (void)changeMovToMp4:(NSURL *)mediaURL dataBlock:(void (^)(UIImage *movieImage,NSString *path))handler;
|
||||
|
||||
/**
|
||||
设置对焦点和曝光度
|
||||
|
||||
@param focusMode 对焦模式
|
||||
@param exposureMode 曝光模式
|
||||
@param point 点击的位置
|
||||
*/
|
||||
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point;
|
||||
@end
|
658
src/ios/SGRecord/SGRecordManager.m
Normal file
658
src/ios/SGRecord/SGRecordManager.m
Normal file
@ -0,0 +1,658 @@
|
||||
//
|
||||
// SGRecordManager.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/19.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SGRecordManager.h"
|
||||
#import "SGRecordEncoder.h"
|
||||
#import <Photos/Photos.h>
|
||||
#import "SGMotionManager.h"
|
||||
#define VIDEO_WIDTH 360
|
||||
#define VIDEO_HEIGHT 640
|
||||
#define MAX_TIME 10
|
||||
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
|
||||
@interface SGRecordManager ()<AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureAudioDataOutputSampleBufferDelegate, CAAnimationDelegate> {
|
||||
CMTime _timeOffset;//录制的偏移CMTime
|
||||
CMTime _lastVideo;//记录上一次视频数据文件的CMTime
|
||||
CMTime _lastAudio;//记录上一次音频数据文件的CMTime
|
||||
|
||||
NSInteger _cx;//视频分辨的宽
|
||||
NSInteger _cy;//视频分辨的高
|
||||
int _channels;//音频通道
|
||||
Float64 _samplerate;//音频采样率
|
||||
}
|
||||
@property (strong, nonatomic) SGRecordEncoder *recordEncoder;//录制编码
|
||||
@property (strong, nonatomic) AVCaptureSession *recordSession;//捕获视频的会话
|
||||
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *previewLayer;//捕获到的视频呈现的layer
|
||||
@property (strong, nonatomic) AVCaptureDeviceInput *backCameraInput;//后置摄像头输入
|
||||
@property (strong, nonatomic) AVCaptureDeviceInput *frontCameraInput;//前置摄像头输入
|
||||
@property (strong, nonatomic) AVCaptureDeviceInput *audioMicInput;//麦克风输入
|
||||
@property (copy , nonatomic) dispatch_queue_t captureQueue;//录制的队列
|
||||
@property (strong, nonatomic) AVCaptureConnection *audioConnection;//音频录制连接
|
||||
@property (strong, nonatomic) AVCaptureConnection *videoConnection;//视频录制连接
|
||||
@property (strong, nonatomic) AVCaptureVideoDataOutput *videoOutput;//视频输出
|
||||
@property (strong, nonatomic) AVCaptureAudioDataOutput *audioOutput;//音频输出
|
||||
@property (strong, nonatomic) AVCaptureStillImageOutput *stillImageOutput;//图片输出
|
||||
@property (atomic, assign) BOOL isCapturing;//正在录制
|
||||
@property (atomic, assign) BOOL isPaused;//是否暂停
|
||||
@property (atomic, assign) BOOL discont;//是否中断
|
||||
@property (atomic, assign) CMTime startTime;//开始录制的时间
|
||||
@property (atomic, assign) CGFloat currentRecordTime;//当前录制时间
|
||||
|
||||
@end
|
||||
|
||||
@implementation SGRecordManager
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.maxRecordTime = MAX_TIME;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - 操作方法
|
||||
//拍照
|
||||
- (void)takePhoto:(void(^)(UIImage *image))callback{
|
||||
AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
|
||||
if (connection.isVideoOrientationSupported) {
|
||||
connection.videoOrientation = [[SGMotionManager sharedManager] currentVideoOrientation];
|
||||
}
|
||||
id takePictureSuccess = ^(CMSampleBufferRef sampleBuffer,NSError *error){
|
||||
if (sampleBuffer == NULL) {
|
||||
return ;
|
||||
}
|
||||
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
|
||||
UIImage *image = [[UIImage alloc]initWithData:imageData];
|
||||
callback(image);
|
||||
};
|
||||
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:takePictureSuccess];
|
||||
}
|
||||
//启动录制功能
|
||||
- (void)startUp {
|
||||
NSLog(@"启动录制功能");
|
||||
self.startTime = CMTimeMake(0, 0);
|
||||
self.isCapturing = NO;
|
||||
self.isPaused = NO;
|
||||
self.discont = NO;
|
||||
[self.recordSession startRunning];
|
||||
}
|
||||
//关闭录制功能
|
||||
- (void)shutdown {
|
||||
_startTime = CMTimeMake(0, 0);
|
||||
if (_recordSession) {
|
||||
[_recordSession stopRunning];
|
||||
}
|
||||
[_recordEncoder finishWithCompletionHandler:^{
|
||||
NSLog(@"录制完成");
|
||||
}];
|
||||
}
|
||||
|
||||
//开始录制
|
||||
- (void) startCapture {
|
||||
@synchronized(self) {
|
||||
if (!self.isCapturing) {
|
||||
NSLog(@"开始录制");
|
||||
self.recordEncoder = nil;
|
||||
self.isPaused = NO;
|
||||
self.discont = NO;
|
||||
_timeOffset = CMTimeMake(0, 0);
|
||||
self.isCapturing = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
//暂停录制
|
||||
- (void) pauseCapture {
|
||||
@synchronized(self) {
|
||||
if (self.isCapturing) {
|
||||
NSLog(@"暂停录制");
|
||||
self.isPaused = YES;
|
||||
self.discont = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
//继续录制
|
||||
- (void) resumeCapture {
|
||||
@synchronized(self) {
|
||||
if (self.isPaused) {
|
||||
NSLog(@"继续录制");
|
||||
self.isPaused = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
//停止录制
|
||||
- (void) stopCaptureWithStatus:(BOOL)isSuccess handler:(void (^)(UIImage *movieImage,NSString *path))handler {
|
||||
@synchronized(self) {
|
||||
if (self.isCapturing) {
|
||||
NSString* path = self.recordEncoder.path;
|
||||
// NSURL* url = [NSURL fileURLWithPath:path];
|
||||
self.isCapturing = NO;
|
||||
dispatch_async(_captureQueue, ^{
|
||||
[self.recordEncoder finishWithCompletionHandler:^{
|
||||
self.isCapturing = NO;
|
||||
self.recordEncoder = nil;
|
||||
self.startTime = CMTimeMake(0, 0);
|
||||
self.currentRecordTime = 0;
|
||||
if (!isSuccess) {
|
||||
NSError *error;
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
|
||||
NSLog(@"录制时间小于3秒,自动清理视频路径path:%@;error:%@",path,error);
|
||||
return ;
|
||||
}
|
||||
if ([self.delegate respondsToSelector:@selector(recordProgress:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime];
|
||||
});
|
||||
}
|
||||
// 保持至相册
|
||||
// [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
// [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
|
||||
// } completionHandler:^(BOOL success, NSError * _Nullable error) {
|
||||
// NSLog(@"保存成功");
|
||||
// }];
|
||||
[self movieToImageHandler:handler];
|
||||
}];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取视频第一帧的图片
|
||||
- (void)movieToImageHandler:(void (^)(UIImage *movieImage,NSString *path))handler {
|
||||
NSURL *url = [NSURL fileURLWithPath:self.videoPath];
|
||||
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
|
||||
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
|
||||
generator.appliesPreferredTrackTransform = TRUE;
|
||||
CMTime thumbTime = CMTimeMakeWithSeconds(0, 60);
|
||||
generator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;
|
||||
AVAssetImageGeneratorCompletionHandler generatorHandler =
|
||||
^(CMTime requestedTime, CGImageRef im, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error){
|
||||
if (result == AVAssetImageGeneratorSucceeded) {
|
||||
UIImage *thumbImg = [UIImage imageWithCGImage:im];
|
||||
if (handler) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
handler(thumbImg,self.videoPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
[generator generateCGImagesAsynchronouslyForTimes:
|
||||
[NSArray arrayWithObject:[NSValue valueWithCMTime:thumbTime]] completionHandler:generatorHandler];
|
||||
}
|
||||
|
||||
#pragma mark - set、get方法
|
||||
//捕获视频的会话
|
||||
- (AVCaptureSession *)recordSession {
|
||||
if (_recordSession == nil) {
|
||||
_recordSession = [[AVCaptureSession alloc] init];
|
||||
_recordSession.sessionPreset = AVCaptureSessionPresetHigh;
|
||||
//添加后置摄像头的输出
|
||||
if ([_recordSession canAddInput:self.backCameraInput]) {
|
||||
[_recordSession addInput:self.backCameraInput];
|
||||
}
|
||||
//添加后置麦克风的输出
|
||||
if ([_recordSession canAddInput:self.audioMicInput]) {
|
||||
[_recordSession addInput:self.audioMicInput];
|
||||
}
|
||||
//添加视频输出
|
||||
if ([_recordSession canAddOutput:self.videoOutput]) {
|
||||
[_recordSession addOutput:self.videoOutput];
|
||||
_cx = VIDEO_WIDTH;
|
||||
_cy = VIDEO_HEIGHT;
|
||||
}
|
||||
//添加音频输出
|
||||
if ([_recordSession canAddOutput:self.audioOutput]) {
|
||||
[_recordSession addOutput:self.audioOutput];
|
||||
}
|
||||
// 静态图像输出
|
||||
if ([_recordSession canAddOutput:self.stillImageOutput]) {
|
||||
[_recordSession addOutput:self.stillImageOutput];
|
||||
}
|
||||
//设置视频录制的方向
|
||||
self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
|
||||
}
|
||||
return _recordSession;
|
||||
}
|
||||
|
||||
//后置摄像头输入
|
||||
- (AVCaptureDeviceInput *)backCameraInput {
|
||||
if (_backCameraInput == nil) {
|
||||
NSError *error;
|
||||
_backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamara] error:&error];
|
||||
if (error) {
|
||||
NSLog(@"获取后置摄像头失败~");
|
||||
}
|
||||
}
|
||||
return _backCameraInput;
|
||||
}
|
||||
|
||||
//前置摄像头输入
|
||||
- (AVCaptureDeviceInput *)frontCameraInput {
|
||||
if (_frontCameraInput == nil) {
|
||||
NSError *error;
|
||||
_frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamara] error:&error];
|
||||
if (error) {
|
||||
NSLog(@"获取前置摄像头失败~");
|
||||
}
|
||||
}
|
||||
return _frontCameraInput;
|
||||
}
|
||||
|
||||
//麦克风输入
|
||||
- (AVCaptureDeviceInput *)audioMicInput {
|
||||
if (_audioMicInput == nil) {
|
||||
AVCaptureDevice *mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
|
||||
NSError *error;
|
||||
_audioMicInput = [AVCaptureDeviceInput deviceInputWithDevice:mic error:&error];
|
||||
if (error) {
|
||||
NSLog(@"获取麦克风失败~");
|
||||
}
|
||||
}
|
||||
return _audioMicInput;
|
||||
}
|
||||
|
||||
//视频输出
|
||||
- (AVCaptureVideoDataOutput *)videoOutput {
|
||||
if (_videoOutput == nil) {
|
||||
_videoOutput = [[AVCaptureVideoDataOutput alloc] init];
|
||||
[_videoOutput setSampleBufferDelegate:self queue:self.captureQueue];
|
||||
NSDictionary* setcapSettings = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey,
|
||||
nil];
|
||||
_videoOutput.videoSettings = setcapSettings;
|
||||
}
|
||||
return _videoOutput;
|
||||
}
|
||||
|
||||
//音频输出
|
||||
- (AVCaptureAudioDataOutput *)audioOutput {
|
||||
if (_audioOutput == nil) {
|
||||
_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
|
||||
[_audioOutput setSampleBufferDelegate:self queue:self.captureQueue];
|
||||
}
|
||||
return _audioOutput;
|
||||
}
|
||||
|
||||
//静态图像输出
|
||||
- (AVCaptureStillImageOutput *)stillImageOutput{
|
||||
if (_stillImageOutput == nil) {
|
||||
_stillImageOutput = [[AVCaptureStillImageOutput alloc]init];
|
||||
_stillImageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
|
||||
}
|
||||
return _stillImageOutput;
|
||||
}
|
||||
//视频连接
|
||||
- (AVCaptureConnection *)videoConnection {
|
||||
_videoConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
|
||||
return _videoConnection;
|
||||
}
|
||||
|
||||
//音频连接
|
||||
- (AVCaptureConnection *)audioConnection {
|
||||
if (_audioConnection == nil) {
|
||||
_audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio];
|
||||
}
|
||||
return _audioConnection;
|
||||
}
|
||||
|
||||
//捕获到的视频呈现的layer
|
||||
- (AVCaptureVideoPreviewLayer *)previewLayer {
|
||||
if (_previewLayer == nil) {
|
||||
//通过AVCaptureSession初始化
|
||||
AVCaptureVideoPreviewLayer *preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.recordSession];
|
||||
//设置比例为铺满全屏
|
||||
preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
|
||||
_previewLayer = preview;
|
||||
}
|
||||
return _previewLayer;
|
||||
}
|
||||
|
||||
//录制的队列
|
||||
- (dispatch_queue_t)captureQueue {
|
||||
if (_captureQueue == nil) {
|
||||
_captureQueue = dispatch_queue_create("cn.qiuyouqun.im.wclrecordengine.capture", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return _captureQueue;
|
||||
}
|
||||
- (BOOL)isRunning{
|
||||
return _recordSession.isRunning;
|
||||
}
|
||||
#pragma mark - 切换动画
|
||||
- (void)changeCameraAnimation {
|
||||
CATransition *changeAnimation = [CATransition animation];
|
||||
changeAnimation.delegate = self;
|
||||
changeAnimation.duration = 0.45;
|
||||
changeAnimation.type = @"oglFlip";
|
||||
changeAnimation.subtype = kCATransitionFromRight;
|
||||
changeAnimation.timingFunction = UIViewAnimationCurveEaseInOut;
|
||||
[self.previewLayer addAnimation:changeAnimation forKey:@"changeAnimation"];
|
||||
}
|
||||
|
||||
- (void)animationDidStart:(CAAnimation *)anim {
|
||||
self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
|
||||
[self.recordSession startRunning];
|
||||
}
|
||||
|
||||
#pragma -mark 将mov文件转为MP4文件
|
||||
- (void)changeMovToMp4:(NSURL *)mediaURL dataBlock:(void (^)(UIImage *movieImage,NSString *path))handler {
|
||||
AVAsset *video = [AVAsset assetWithURL:mediaURL];
|
||||
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPreset1280x720];
|
||||
exportSession.shouldOptimizeForNetworkUse = YES;
|
||||
exportSession.outputFileType = AVFileTypeMPEG4;
|
||||
NSString * basePath=[self getVideoCachePath];
|
||||
|
||||
self.videoPath = [basePath stringByAppendingPathComponent:[self getUploadFile_type:@"video" fileType:@"mp4"]];
|
||||
exportSession.outputURL = [NSURL fileURLWithPath:self.videoPath];
|
||||
[exportSession exportAsynchronouslyWithCompletionHandler:^{
|
||||
[self movieToImageHandler:handler];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 视频相关
|
||||
//返回前置摄像头
|
||||
- (AVCaptureDevice *)frontCamara {
|
||||
return [self cameraWithPosition:AVCaptureDevicePositionFront];
|
||||
}
|
||||
|
||||
//返回后置摄像头
|
||||
- (AVCaptureDevice *)backCamara {
|
||||
return [self cameraWithPosition:AVCaptureDevicePositionBack];
|
||||
}
|
||||
|
||||
//切换前后置摄像头
|
||||
- (void)changeCameraInputDeviceisFront:(BOOL)isFront {
|
||||
// 把配置类释放
|
||||
[self.recordSession beginConfiguration];
|
||||
if (isFront) {
|
||||
[self.recordSession removeInput:self.backCameraInput];
|
||||
if ([self.recordSession canAddInput:self.frontCameraInput]) {
|
||||
// [self changeCameraAnimation];
|
||||
[self.recordSession addInput:self.frontCameraInput];
|
||||
}
|
||||
}else {
|
||||
[self.recordSession removeInput:self.frontCameraInput];
|
||||
if ([self.recordSession canAddInput:self.backCameraInput]) {
|
||||
// [self changeCameraAnimation];
|
||||
[self.recordSession addInput:self.backCameraInput];
|
||||
}
|
||||
}
|
||||
if (self.videoConnection.isVideoMirroringSupported) {
|
||||
self.videoConnection.videoMirrored = isFront;
|
||||
}
|
||||
self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
|
||||
[self.recordSession commitConfiguration];
|
||||
}
|
||||
|
||||
//用来返回是前置摄像头还是后置摄像头
|
||||
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {
|
||||
//返回和视频录制相关的所有默认设备
|
||||
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
//遍历这些设备返回跟position相关的设备
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
if ([device position] == position) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -闪光灯
|
||||
#pragma mark -
|
||||
#pragma mark -对焦
|
||||
- (void)focus:(CGPoint)point{
|
||||
CGPoint camaraPoint = [self.previewLayer captureDevicePointOfInterestForPoint:point];
|
||||
[self focusWithMode:AVCaptureFocusModeContinuousAutoFocus exposureMode:AVCaptureExposureModeContinuousAutoExposure atPoint:camaraPoint];
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -openFlashlight
|
||||
/**
|
||||
打开闪光灯
|
||||
*/
|
||||
- (void)openFlashlight{
|
||||
AVCaptureDevice *backCamara = [self backCamara];
|
||||
if (!backCamara.flashAvailable) {
|
||||
return;
|
||||
}
|
||||
if (backCamara.torchMode == AVCaptureTorchModeOff) {
|
||||
[backCamara lockForConfiguration:nil];
|
||||
backCamara.torchMode = AVCaptureTorchModeOn;
|
||||
backCamara.flashMode = AVCaptureFlashModeOn;
|
||||
[backCamara unlockForConfiguration];
|
||||
}
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -closeFlashlight
|
||||
/**
|
||||
关闭闪关灯
|
||||
*/
|
||||
- (void)closeFlashlight{
|
||||
AVCaptureDevice *backCamara = [self backCamara];
|
||||
if (!backCamara.flashAvailable) {
|
||||
return;
|
||||
}
|
||||
if (backCamara.torchMode == AVCaptureTorchModeOn) {
|
||||
[backCamara lockForConfiguration:nil];
|
||||
backCamara.torchMode = AVCaptureTorchModeOff;
|
||||
backCamara.flashMode = AVCaptureFlashModeOff;
|
||||
[backCamara unlockForConfiguration];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
设置闪光灯模式
|
||||
|
||||
@param flashMode 闪光灯模式
|
||||
*/
|
||||
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
|
||||
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
|
||||
if ([captureDevice isFlashModeSupported:flashMode]) {
|
||||
[captureDevice setFlashMode:flashMode];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
设置对焦模式
|
||||
|
||||
@param focusMode 对焦模式
|
||||
*/
|
||||
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
|
||||
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
|
||||
if ([captureDevice isFocusModeSupported:focusMode]) {
|
||||
[captureDevice setFocusMode:focusMode];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
设置曝光模式
|
||||
|
||||
@param exposureMode 曝光模式
|
||||
*/
|
||||
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
|
||||
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
|
||||
if ([captureDevice isExposureModeSupported:exposureMode]) {
|
||||
[captureDevice setExposureMode:exposureMode];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
设置对焦点和曝光度
|
||||
|
||||
@param focusMode 对焦模式
|
||||
@param exposureMode 曝光模式
|
||||
@param point 点击的位置
|
||||
*/
|
||||
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
|
||||
|
||||
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
|
||||
if ([captureDevice isFocusModeSupported:focusMode]) {
|
||||
[captureDevice setFocusMode:focusMode];
|
||||
}
|
||||
if ([captureDevice isFocusPointOfInterestSupported]) {
|
||||
[captureDevice setFocusPointOfInterest:point];
|
||||
}
|
||||
if ([captureDevice isExposureModeSupported:exposureMode]) {
|
||||
[captureDevice setExposureMode:exposureMode];
|
||||
}
|
||||
if ([captureDevice isExposurePointOfInterestSupported]) {
|
||||
[captureDevice setExposurePointOfInterest:point];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
改变设备属性的统一操作方法
|
||||
|
||||
@param propertyChange callback
|
||||
*/
|
||||
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
|
||||
AVCaptureDevice *captureDevice= [self backCamara];
|
||||
NSError *error;
|
||||
/*
|
||||
In order to set hardware properties on an AVCaptureDevice, such as focusMode and exposureMode, clients must first acquire a lock on the device. Clients should only hold the device lock if they require settable device properties to remain unchanged. Holding the device lock unnecessarily may degrade capture quality in other applications sharing the device.
|
||||
*/
|
||||
if ([captureDevice lockForConfiguration:&error]) {
|
||||
propertyChange(captureDevice);
|
||||
[captureDevice unlockForConfiguration];
|
||||
}else{
|
||||
NSLog(@"设置设备属性过程发生错误:error:%@",error.localizedDescription);
|
||||
}
|
||||
}
|
||||
//获得视频存放地址
|
||||
- (NSString *)getVideoCachePath {
|
||||
NSString *videoCache = [NSTemporaryDirectory() stringByAppendingPathComponent:@"videos"] ;
|
||||
BOOL isDir = NO;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
BOOL existed = [fileManager fileExistsAtPath:videoCache isDirectory:&isDir];
|
||||
if ( !(isDir == YES && existed == YES) ) {
|
||||
[fileManager createDirectoryAtPath:videoCache withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
};
|
||||
return videoCache;
|
||||
}
|
||||
|
||||
- (NSString *)getUploadFile_type:(NSString *)type fileType:(NSString *)fileType {
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
||||
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateFormat:@"HHmmss"];
|
||||
NSDate * NowDate = [NSDate dateWithTimeIntervalSince1970:now];
|
||||
;
|
||||
NSString * timeStr = [formatter stringFromDate:NowDate];
|
||||
NSString *fileName = [NSString stringWithFormat:@"%@_%@.%@",type,timeStr,fileType];
|
||||
return fileName;
|
||||
}
|
||||
|
||||
#pragma mark - 写入数据
|
||||
- (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
|
||||
BOOL isVideo = YES;
|
||||
@synchronized(self) {
|
||||
if (!self.isCapturing || self.isPaused) {
|
||||
return;
|
||||
}
|
||||
if (captureOutput != self.videoOutput) {
|
||||
isVideo = NO;
|
||||
}
|
||||
//初始化编码器,当有音频和视频参数时创建编码器
|
||||
if ((self.recordEncoder == nil) && !isVideo) {
|
||||
CMFormatDescriptionRef fmt = CMSampleBufferGetFormatDescription(sampleBuffer);
|
||||
[self setAudioFormat:fmt];
|
||||
NSString *videoName = [self getUploadFile_type:@"video" fileType:@"mp4"];
|
||||
self.videoPath = [[self getVideoCachePath] stringByAppendingPathComponent:videoName];
|
||||
self.recordEncoder = [SGRecordEncoder encoderForPath:self.videoPath Height:_cy width:_cx channels:_channels samples:_samplerate];
|
||||
}
|
||||
//判断是否中断录制过
|
||||
if (self.discont) {
|
||||
if (isVideo) {
|
||||
return;
|
||||
}
|
||||
self.discont = NO;
|
||||
// 计算暂停的时间
|
||||
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
|
||||
CMTime last = isVideo ? _lastVideo : _lastAudio;
|
||||
if (last.flags & kCMTimeFlags_Valid) {
|
||||
if (_timeOffset.flags & kCMTimeFlags_Valid) {
|
||||
pts = CMTimeSubtract(pts, _timeOffset);
|
||||
}
|
||||
CMTime offset = CMTimeSubtract(pts, last);
|
||||
if (_timeOffset.value == 0) {
|
||||
_timeOffset = offset;
|
||||
}else {
|
||||
_timeOffset = CMTimeAdd(_timeOffset, offset);
|
||||
}
|
||||
}
|
||||
_lastVideo.flags = 0;
|
||||
_lastAudio.flags = 0;
|
||||
}
|
||||
// 增加sampleBuffer的引用计时,这样我们可以释放这个或修改这个数据,防止在修改时被释放
|
||||
CFRetain(sampleBuffer);
|
||||
if (_timeOffset.value > 0) {
|
||||
CFRelease(sampleBuffer);
|
||||
//根据得到的timeOffset调整
|
||||
sampleBuffer = [self adjustTime:sampleBuffer by:_timeOffset];
|
||||
}
|
||||
// 记录暂停上一次录制的时间
|
||||
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
|
||||
CMTime dur = CMSampleBufferGetDuration(sampleBuffer);
|
||||
if (dur.value > 0) {
|
||||
pts = CMTimeAdd(pts, dur);
|
||||
}
|
||||
if (isVideo) {
|
||||
_lastVideo = pts;
|
||||
}else {
|
||||
_lastAudio = pts;
|
||||
}
|
||||
}
|
||||
CMTime dur = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
|
||||
if (self.startTime.value == 0) {
|
||||
self.startTime = dur;
|
||||
}
|
||||
CMTime sub = CMTimeSubtract(dur, self.startTime);
|
||||
self.currentRecordTime = CMTimeGetSeconds(sub);
|
||||
if (self.currentRecordTime > self.maxRecordTime) {
|
||||
if (self.currentRecordTime - self.maxRecordTime < 0.1) {
|
||||
if ([self.delegate respondsToSelector:@selector(recordProgress:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime];
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ([self.delegate respondsToSelector:@selector(recordProgress:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime];
|
||||
});
|
||||
}
|
||||
// 进行数据编码
|
||||
[self.recordEncoder encodeFrame:sampleBuffer isVideo:isVideo];
|
||||
CFRelease(sampleBuffer);
|
||||
}
|
||||
|
||||
//设置音频格式
|
||||
- (void)setAudioFormat:(CMFormatDescriptionRef)fmt {
|
||||
const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(fmt);
|
||||
_samplerate = asbd->mSampleRate;
|
||||
_channels = asbd->mChannelsPerFrame;
|
||||
|
||||
}
|
||||
|
||||
//调整媒体数据的时间
|
||||
- (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset {
|
||||
CMItemCount count;
|
||||
CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
|
||||
CMSampleTimingInfo* pInfo = malloc(sizeof(CMSampleTimingInfo) * count);
|
||||
CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count);
|
||||
for (CMItemCount i = 0; i < count; i++) {
|
||||
pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset);
|
||||
pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset);
|
||||
}
|
||||
CMSampleBufferRef sout;
|
||||
CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout);
|
||||
free(pInfo);
|
||||
return sout;
|
||||
}
|
||||
@end
|
15
src/ios/SGRecord/SGRecordProgressView.h
Normal file
15
src/ios/SGRecord/SGRecordProgressView.h
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// SGRecordProgressView.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/23.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SGRecordProgressView : UIButton
|
||||
@property (nonatomic ,assign) CGFloat progress;
|
||||
- (void)resetScale;
|
||||
- (void)setScale;
|
||||
@end
|
74
src/ios/SGRecord/SGRecordProgressView.m
Normal file
74
src/ios/SGRecord/SGRecordProgressView.m
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// SGRecordProgressView.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/23.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SGRecordProgressView.h"
|
||||
#define SG_LINE_WIDTH 4
|
||||
#define SPRING_DAMPING 50
|
||||
#define SPRING_VELOCITY 29
|
||||
@interface SGRecordProgressView()
|
||||
@property (nonatomic ,strong) CALayer *centerlayer;
|
||||
@end
|
||||
@implementation SGRecordProgressView
|
||||
- (instancetype)initWithFrame:(CGRect)frame{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)setupUI{
|
||||
|
||||
self.layer.cornerRadius = self.bounds.size.height / 2;
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
// 中间的白圆
|
||||
self.backgroundColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.24];
|
||||
CALayer *centerlayer = [CALayer layer];
|
||||
centerlayer.backgroundColor = [UIColor whiteColor].CGColor;
|
||||
centerlayer.position = self.center;
|
||||
centerlayer.bounds = CGRectMake(0, 0, 116/2, 116/2);
|
||||
centerlayer.cornerRadius = 116/4;
|
||||
centerlayer.masksToBounds = YES;
|
||||
[self.layer addSublayer:centerlayer];
|
||||
_centerlayer = centerlayer;
|
||||
}
|
||||
- (void)resetScale{
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_centerlayer.transform = CATransform3DIdentity;
|
||||
self.transform = CGAffineTransformIdentity;
|
||||
}];
|
||||
}
|
||||
- (void)setScale{
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_centerlayer.transform = CATransform3DScale(_centerlayer.transform, 30/58.0, 30/58.0, 1);
|
||||
self.transform = CGAffineTransformScale(self.transform, 172/156.0, 172/156.0);
|
||||
}];
|
||||
}
|
||||
-(void)setProgress:(CGFloat)progress{
|
||||
_progress = progress;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
// Only override drawRect: if you perform custom drawing.
|
||||
// An empty implementation adversely affects performance during animation.
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
// Drawing code
|
||||
CGContextRef contexRef = UIGraphicsGetCurrentContext();// 获取上下文
|
||||
CGPoint ceterPoint = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); // 设置圆心
|
||||
CGFloat radius = self.bounds.size.height / 2 - SG_LINE_WIDTH/2;// 设置半径
|
||||
CGFloat startA = -M_PI_2; // 设置起始点
|
||||
CGFloat endA = -M_PI_2 + M_PI * 2 *_progress; // 设置结束点
|
||||
|
||||
// 贝塞尔曲线(圆)
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:ceterPoint radius:radius startAngle:startA endAngle:endA clockwise:YES];
|
||||
CGContextSetLineWidth(contexRef, SG_LINE_WIDTH);// 设置线宽度
|
||||
[[UIColor colorWithRed:255/255.0 green:214/255.0 blue:34/255.0 alpha:1] setStroke];// 设置线颜色
|
||||
CGContextAddPath(contexRef, path.CGPath);// 把贝塞尔曲线添加到上下问题
|
||||
CGContextStrokePath(contexRef);// 渲染
|
||||
}
|
||||
|
||||
@end
|
25
src/ios/SGRecord/SGRecordSuccessPreview.h
Normal file
25
src/ios/SGRecord/SGRecordSuccessPreview.h
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// SGRecordSuccessPreview.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/22.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AVKit/AVKit.h>
|
||||
@interface SGRecordSuccessPreview : UIView
|
||||
@property (nonatomic ,copy) void (^sendBlock) (UIImage *image, NSString *videoPath);
|
||||
@property (nonatomic ,copy) void (^cancelBlcok) (void);
|
||||
|
||||
/**
|
||||
设置图片或视频
|
||||
|
||||
@param image 图片
|
||||
@param videoPath 视频地址
|
||||
@param orientation 方向
|
||||
*/
|
||||
- (void)setImage:(UIImage *)image videoPath:(NSString *)videoPath captureVideoOrientation:(AVCaptureVideoOrientation)orientation;
|
||||
@end
|
115
src/ios/SGRecord/SGRecordSuccessPreview.m
Normal file
115
src/ios/SGRecord/SGRecordSuccessPreview.m
Normal file
@ -0,0 +1,115 @@
|
||||
//
|
||||
// SGRecordSuccessPreview.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/22.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SGRecordSuccessPreview.h"
|
||||
#import "UIButton+Convenience.h"
|
||||
@interface SGRecordSuccessPreview(){
|
||||
float _width;
|
||||
float _distance;
|
||||
}
|
||||
@property (nonatomic ,strong) UIButton *cancelButton;
|
||||
@property (nonatomic ,strong) UIButton *sendButton;
|
||||
@property (nonatomic ,strong) UIImage *image;// 拍摄的图片
|
||||
@property (nonatomic ,copy) NSString *videoPath; // 拍摄的视频地址
|
||||
@property (nonatomic ,assign) BOOL isPhoto;// 是否是图片
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_8_4
|
||||
@property (nonatomic ,strong) AVPlayerViewController *avPlayer;
|
||||
#endif
|
||||
@property (nonatomic ,assign) AVCaptureVideoOrientation orientation;
|
||||
@end
|
||||
@implementation SGRecordSuccessPreview
|
||||
- (void)setImage:(UIImage *)image videoPath:(NSString *)videoPath captureVideoOrientation:(AVCaptureVideoOrientation)orientation{
|
||||
_image = image;
|
||||
_videoPath = videoPath;
|
||||
_orientation = orientation;
|
||||
self.backgroundColor = [UIColor blackColor];
|
||||
if (_image && !videoPath) {
|
||||
_isPhoto = YES;
|
||||
}
|
||||
[self setupUI];
|
||||
}
|
||||
- (void)setupUI{
|
||||
|
||||
if (_isPhoto) {
|
||||
UIImageView *imageview = [[UIImageView alloc]initWithImage:_image];
|
||||
imageview.frame = self.bounds;
|
||||
if (_orientation == AVCaptureVideoOrientationLandscapeRight || _orientation ==AVCaptureVideoOrientationLandscapeLeft) {
|
||||
imageview.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
[self addSubview:imageview];
|
||||
} else {
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
|
||||
MPMoviePlayerController *mpPlayer = [[MPMoviePlayerController alloc]initWithContentURL:[NSURL fileURLWithPath:_videoPath]];
|
||||
mpPlayer.view.frame = self.bounds;
|
||||
mpPlayer.controlStyle = MPMovieControlStyleNone;
|
||||
mpPlayer.movieSourceType = MPMovieSourceTypeFile;
|
||||
mpPlayer.repeatMode = MPMovieRepeatModeOne;
|
||||
[mpPlayer prepareToPlay];
|
||||
[mpPlayer play];
|
||||
[self addSubview:mpPlayer.view];
|
||||
#else
|
||||
AVPlayerViewController *avPlayer = [[AVPlayerViewController alloc]init];
|
||||
avPlayer.view.frame = self.bounds;
|
||||
avPlayer.showsPlaybackControls = NO;
|
||||
avPlayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
||||
avPlayer.player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:_videoPath]];
|
||||
[avPlayer.player play];
|
||||
[self addSubview:avPlayer.view];
|
||||
_avPlayer = avPlayer;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(replay) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
#endif
|
||||
}
|
||||
_width = 148/2;
|
||||
_distance = 120/2;
|
||||
// 取消
|
||||
UIButton *cancelButton = [UIButton image:@"短视频_重拍" target:self action:@selector(cancel)];
|
||||
cancelButton.bounds = CGRectMake(0, 0, _width, _width);
|
||||
cancelButton.center = CGPointMake(self.center.x, self.bounds.size.height -_distance - _width/2);
|
||||
[self addSubview:cancelButton];
|
||||
_cancelButton = cancelButton;
|
||||
|
||||
// 发送
|
||||
UIButton *sendButton = [UIButton image:@"短视频_完成" target:self action:@selector(send)];
|
||||
sendButton.bounds = CGRectMake(0, 0, _width, _width);
|
||||
sendButton.center = CGPointMake(self.center.x, self.bounds.size.height - _distance - _width/2);
|
||||
[self addSubview:sendButton];
|
||||
_sendButton = sendButton;
|
||||
}
|
||||
-(void)layoutSubviews{
|
||||
[super layoutSubviews];
|
||||
NSLog(@"预览图");
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_cancelButton.bounds = CGRectMake(0, 0, _width, _width);
|
||||
_cancelButton.center = CGPointMake(self.bounds.size.width / 4, self.bounds.size.height -_distance - _width/2);
|
||||
_sendButton.bounds = CGRectMake(0, 0, _width, _width);
|
||||
_sendButton.center = CGPointMake(self.bounds.size.width / 4 * 3, self.bounds.size.height - _distance - _width/2);
|
||||
}];
|
||||
}
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_8_4
|
||||
- (void)replay{
|
||||
if (_avPlayer) {
|
||||
[_avPlayer.player seekToTime:CMTimeMake(0, 1)];
|
||||
[_avPlayer.player play];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
- (void)cancel{
|
||||
if (self.cancelBlcok) {
|
||||
self.cancelBlcok();
|
||||
}
|
||||
}
|
||||
- (void)send{
|
||||
if (self.sendBlock) {
|
||||
self.sendBlock(_image, _videoPath);
|
||||
}
|
||||
}
|
||||
- (void)dealloc{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
NSLog(@"%s",__func__);
|
||||
}
|
||||
@end
|
13
src/ios/SGRecord/SGRecordViewController.h
Normal file
13
src/ios/SGRecord/SGRecordViewController.h
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// SGRecordViewController.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/19.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SGRecordViewController : UIViewController
|
||||
|
||||
@end
|
459
src/ios/SGRecord/SGRecordViewController.m
Normal file
459
src/ios/SGRecord/SGRecordViewController.m
Normal file
@ -0,0 +1,459 @@
|
||||
//
|
||||
// SGRecordViewController.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/19.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SGRecordViewController.h"
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import "SGRecordManager.h"
|
||||
#import "SGRecordSuccessPreview.h"
|
||||
#import "SGRecordProgressView.h"
|
||||
#import "UIButton+Convenience.h"
|
||||
#import "SGMotionManager.h"
|
||||
#define WEAKSELF __weak typeof(self) weakSelf = self;
|
||||
#define STRONGSELF __strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
#define TIMER_INTERVAL 0.5 //定时器时间间隔
|
||||
#define RECORD_TIME 0.5 //开始录制视频的时间
|
||||
#define VIDEO_MIN_TIME 3 // 录制视频最短时间
|
||||
@interface SGRecordViewController ()<SGRecordEngineDelegate,UIGestureRecognizerDelegate,SGMotionManagerDeviceOrientationDelegate>
|
||||
@property (nonatomic ,strong) SGRecordManager *recordManger;
|
||||
@property (nonatomic ,assign) BOOL allowRecord;//允许录制
|
||||
@property (nonatomic ,strong) NSTimer *timer;// 定时器
|
||||
@property (nonatomic ,assign) NSTimeInterval timeInterval;// 时长
|
||||
@property (nonatomic ,assign) BOOL isEndRecord;// 录制结束
|
||||
@property (nonatomic ,strong) UIImageView *focusView;// 对焦图片
|
||||
@property (nonatomic ,strong) SGRecordSuccessPreview *preview;// 拍摄成功预览视图
|
||||
@property (nonatomic ,strong) SGRecordProgressView *recordButton;// 录制按钮
|
||||
@property (nonatomic ,strong) UILabel *tipLabel;// 提示标签
|
||||
@property (nonatomic ,strong) UIButton *exitButton;// 退出按钮
|
||||
@property (nonatomic ,strong) UIButton *switchButton;// 切换摄像头按钮
|
||||
@property (nonatomic ,assign) UIDeviceOrientation lastDeviceOrientation;// 记录屏幕当前方向
|
||||
@property (nonatomic ,strong) UILabel *alartLabel;// (提示)拍摄时间太短,不少于3s
|
||||
@end
|
||||
|
||||
@implementation SGRecordViewController
|
||||
#pragma mark -
|
||||
#pragma mark -Life Cycle
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.view.backgroundColor = [UIColor grayColor];
|
||||
self.title = @"拍摄或录像";
|
||||
self.allowRecord = YES;
|
||||
[self setupUI];
|
||||
}
|
||||
- (void)viewDidAppear:(BOOL)animated{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// 监听设备方向
|
||||
[[SGMotionManager sharedManager] startDeviceMotionUpdates];
|
||||
[SGMotionManager sharedManager].delegate = self;;
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES];
|
||||
#endif
|
||||
|
||||
if (_recordManger == nil) {
|
||||
[self.recordManger previewLayer].frame = self.view.bounds;
|
||||
[self.view.layer insertSublayer:[self.recordManger previewLayer] atIndex:0];
|
||||
}
|
||||
[self.recordManger startUp];
|
||||
}
|
||||
- (void)viewDidDisappear:(BOOL)animated{
|
||||
[super viewDidDisappear:animated];
|
||||
// 停止监听设备方向
|
||||
[SGMotionManager sharedManager].delegate = nil;
|
||||
[[SGMotionManager sharedManager] stopDeviceMotionUpdates];
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO];
|
||||
#endif
|
||||
[self removeTimer];
|
||||
[self.recordManger shutdown];
|
||||
}
|
||||
- (void)exitRecordController{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_8_4
|
||||
- (BOOL)prefersStatusBarHidden{
|
||||
return YES;
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -拍照
|
||||
- (void)takephoto{
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[self.recordManger takePhoto:^(UIImage *image) {
|
||||
NSLog(@"拍照结束:%@",image);
|
||||
//UIImageWriteToSavedPhotosAlbum(image, nil, nil, NULL);
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[strongSelf.recordManger shutdown];
|
||||
[strongSelf.preview setImage:image videoPath:nil captureVideoOrientation:[[SGMotionManager sharedManager] currentVideoOrientation]];
|
||||
});
|
||||
}];
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -录制视频
|
||||
- (void)startRecord{
|
||||
if (self.recordManger.isCapturing) {
|
||||
[self.recordManger resumeCapture];
|
||||
}else {
|
||||
[self.recordManger startCapture];
|
||||
}
|
||||
}
|
||||
- (void)stopRecord:(BOOL)isSuccess{
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
_isEndRecord = NO;
|
||||
[self.recordButton setProgress:0];
|
||||
if (isSuccess) {
|
||||
[self hideAllOperationViews];
|
||||
} else {
|
||||
[self showExitAndSwitchViews];
|
||||
}
|
||||
[self.recordManger stopCaptureWithStatus:isSuccess handler:^(UIImage *movieImage,NSString *filePath) {
|
||||
NSLog(@"第一帧:image:%@",movieImage);
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[strongSelf.recordManger shutdown];
|
||||
[strongSelf.preview setImage:nil videoPath:filePath captureVideoOrientation:[[SGMotionManager sharedManager] currentVideoOrientation]];
|
||||
});
|
||||
}];
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark - 发送 or 重拍
|
||||
// 点击发送
|
||||
- (void)sendWithImage:(UIImage *)image videoPath:(NSString *)videoPath{
|
||||
NSLog(@"发送");
|
||||
[self exitRecordController];
|
||||
}
|
||||
// 点击重拍
|
||||
- (void)cancel{
|
||||
NSLog(@"重拍");
|
||||
if (_preview) {
|
||||
[_preview removeFromSuperview];
|
||||
_preview = nil;
|
||||
}
|
||||
[self.recordButton resetScale];
|
||||
[self.recordButton setEnabled:YES];
|
||||
[self showAllOperationViews];
|
||||
[self.recordManger startUp];
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark - set、get方法
|
||||
- (SGRecordManager *)recordManger {
|
||||
if (!_recordManger) {
|
||||
_recordManger = [[SGRecordManager alloc] init];
|
||||
_recordManger.delegate = self;
|
||||
}
|
||||
return _recordManger;
|
||||
}
|
||||
- (NSTimer *)timer{
|
||||
if (!_timer) {
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(caculateTime) userInfo:nil repeats:YES];
|
||||
}
|
||||
return _timer;
|
||||
}
|
||||
- (UIImageView *)focusView{
|
||||
if (!_focusView) {
|
||||
_focusView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"camera_focus_red"]];
|
||||
_focusView.bounds = CGRectMake(0, 0, 40, 40);
|
||||
[self.view addSubview:_focusView];
|
||||
}
|
||||
return _focusView;
|
||||
}
|
||||
- (SGRecordSuccessPreview *)preview{
|
||||
if (!_preview) {
|
||||
_preview = [[SGRecordSuccessPreview alloc]initWithFrame:self.view.bounds];
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[_preview setSendBlock:^(UIImage *image,NSString *videoPath){
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf sendWithImage:image videoPath:videoPath];
|
||||
}];
|
||||
[_preview setCancelBlcok:^{
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf cancel];
|
||||
}];
|
||||
[self.view addSubview:_preview];
|
||||
}
|
||||
return _preview;
|
||||
}
|
||||
- (UILabel *)alartLabel{
|
||||
if (!_alartLabel) {
|
||||
_alartLabel = [[UILabel alloc]init];
|
||||
_alartLabel.text = @"拍摄时间太短,不少于3s";
|
||||
_alartLabel.font = [UIFont systemFontOfSize:15];
|
||||
_alartLabel.textColor = [UIColor whiteColor];
|
||||
_alartLabel.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6];
|
||||
_alartLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_alartLabel.layer.cornerRadius = 19;
|
||||
_alartLabel.clipsToBounds = YES;
|
||||
CGFloat width = [_alartLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, 76/2) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15]} context:nil].size.width;
|
||||
_alartLabel.bounds = CGRectMake(0, 0, width + 30, 76/2);
|
||||
_alartLabel.center = CGPointMake(self.view.center.x, _tipLabel.center.y - _tipLabel.bounds.size.height/2 - 48/2 - _tipLabel.bounds.size.height/2);
|
||||
[self.view addSubview:_alartLabel];
|
||||
}
|
||||
return _alartLabel;
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -Set Up UI
|
||||
- (void)setupUI{
|
||||
|
||||
// 退出按钮
|
||||
UIButton *exitButton = [UIButton image:@"短视频_关闭" target:self action:@selector(exitRecordController)];
|
||||
exitButton.frame = CGRectMake(5, 10, 44,44);
|
||||
[self.view addSubview:exitButton];
|
||||
_exitButton = exitButton;
|
||||
|
||||
// 录制按钮
|
||||
SGRecordProgressView *recordButton = [[SGRecordProgressView alloc]initWithFrame:CGRectMake(0, 0, 156/2, 156/2)];
|
||||
recordButton.center = CGPointMake(self.view.center.x, self.view.bounds.size.height - 97);
|
||||
[recordButton addTarget:self action:@selector(toucheUpInsideOrOutSide:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
|
||||
[recordButton addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown];
|
||||
[self.view addSubview:recordButton];
|
||||
_recordButton = recordButton;
|
||||
|
||||
// 提示文字:点击拍照,长按拍摄
|
||||
UILabel *tipLabel = [[UILabel alloc]init];
|
||||
tipLabel.bounds = CGRectMake(0, 0, 200, 20);
|
||||
tipLabel.center = CGPointMake(self.view.center.x, self.view.bounds.size.height - 160 - 13/2);
|
||||
tipLabel.text = @"点击拍照,长按拍摄";
|
||||
tipLabel.font = [UIFont systemFontOfSize:13];
|
||||
tipLabel.textAlignment = NSTextAlignmentCenter;
|
||||
tipLabel.textColor = [UIColor whiteColor];
|
||||
[self.view addSubview:tipLabel];
|
||||
_tipLabel = tipLabel;
|
||||
|
||||
// 切换摄像头按钮
|
||||
UIButton *switchButton = [UIButton image:@"短视频_翻转"target:self action:@selector(switchCamara:)];
|
||||
switchButton.frame = CGRectMake(self.view.bounds.size.width - 44 - 5 , 10, 44, 44);
|
||||
[self.view addSubview:switchButton];
|
||||
_switchButton = switchButton;
|
||||
|
||||
// 对焦手势
|
||||
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapGesture:)];
|
||||
tapGesture.delegate = self;
|
||||
[self.view addGestureRecognizer:tapGesture];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -对焦,闪光灯,曝光处理,切换摄像头
|
||||
- (void)tapGesture:(UITapGestureRecognizer *)tapGesture{
|
||||
NSLog(@"点击屏幕");
|
||||
if (!self.recordManger.isRunning) return;
|
||||
CGPoint point = [tapGesture locationInView:self.view];
|
||||
[self setFocusCursorWithPoint:point];
|
||||
CGPoint camaraPoint = [self.recordManger.previewLayer captureDevicePointOfInterestForPoint:point];
|
||||
[self.recordManger focusWithMode:AVCaptureFocusModeContinuousAutoFocus exposureMode:AVCaptureExposureModeContinuousAutoExposure atPoint:camaraPoint];
|
||||
}
|
||||
/**
|
||||
设置对焦光标位置
|
||||
|
||||
@param point 光标位置
|
||||
*/
|
||||
-(void)setFocusCursorWithPoint:(CGPoint)point{
|
||||
self.focusView.center=point;
|
||||
self.focusView.transform=CGAffineTransformMakeScale(1.5, 1.5);
|
||||
self.focusView.alpha=1.0;
|
||||
[UIView animateWithDuration:1.0 animations:^{
|
||||
self.focusView.transform=CGAffineTransformIdentity;
|
||||
} completion:^(BOOL finished) {
|
||||
self.focusView.alpha=0;
|
||||
|
||||
}];
|
||||
}
|
||||
// 切换摄像头
|
||||
- (void)switchCamara:(UIButton *)button{
|
||||
button.selected = !button.selected;
|
||||
[self.recordManger changeCameraInputDeviceisFront:button.selected];
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -显示或隐藏界面
|
||||
// 显示所有操作按钮
|
||||
- (void)showAllOperationViews{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.recordButton setHidden:NO];
|
||||
[self.exitButton setHidden:NO];
|
||||
[self.tipLabel setHidden:NO];
|
||||
[self.switchButton setHidden:NO];
|
||||
});
|
||||
}
|
||||
// 隐藏所有操作按钮
|
||||
- (void)hideAllOperationViews{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.recordButton setHidden:YES];
|
||||
[self.exitButton setHidden:YES];
|
||||
[self.tipLabel setHidden:YES];
|
||||
[self.switchButton setHidden:YES];
|
||||
});
|
||||
}
|
||||
// 拍摄结束后显示退出按钮和切换摄像头按钮
|
||||
- (void)showExitAndSwitchViews{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.exitButton setHidden:NO];
|
||||
[self.switchButton setHidden:NO];
|
||||
});
|
||||
}
|
||||
// 开始拍摄时隐藏退出和切换摄像头按钮
|
||||
- (void)hideExitAndSwitchViews{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.exitButton setHidden:YES];
|
||||
[self.switchButton setHidden:YES];
|
||||
});
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -定时器
|
||||
// 计时
|
||||
- (void)caculateTime{
|
||||
|
||||
_timeInterval += TIMER_INTERVAL;
|
||||
NSLog(@"计时器:_timeInterval:%f",_timeInterval);
|
||||
if (_timeInterval == RECORD_TIME) {
|
||||
NSLog(@"开始录制视频");
|
||||
[self.recordButton setScale];
|
||||
[self startRecord];
|
||||
} else if (_timeInterval >= RECORD_TIME + VIDEO_MIN_TIME) {
|
||||
[self removeTimer];
|
||||
}
|
||||
}
|
||||
// 按钮按下事件
|
||||
- (void)touchDown:(UIButton *)button{
|
||||
NSLog(@"按下按钮");
|
||||
[self hideExitAndSwitchViews];
|
||||
[self removeTimer];
|
||||
[self timer];
|
||||
}
|
||||
// 按钮抬起
|
||||
- (void)toucheUpInsideOrOutSide:(UIButton *)button{
|
||||
NSLog(@"抬起按钮:__timeInterval==:%f",_timeInterval);
|
||||
[self removeTimer];
|
||||
if (_timeInterval >= RECORD_TIME && _timeInterval < RECORD_TIME + VIDEO_MIN_TIME) {
|
||||
// 录制时间太短
|
||||
NSLog(@"录制时间太短");
|
||||
[self stopRecord:NO];
|
||||
[self alart];//提示用户
|
||||
[self.recordButton resetScale];
|
||||
} else if (_timeInterval < RECORD_TIME) {
|
||||
// 拍照
|
||||
NSLog(@"拍照");
|
||||
[self.recordButton setEnabled:NO];
|
||||
[self hideAllOperationViews];
|
||||
[self takephoto];
|
||||
} else {
|
||||
// 拍摄视频
|
||||
NSLog(@"结束录制");
|
||||
if (!_isEndRecord) {
|
||||
[self.recordButton setEnabled:NO];
|
||||
[self stopRecord:YES];
|
||||
}
|
||||
}
|
||||
_timeInterval = 0;
|
||||
}
|
||||
// 移除定时器
|
||||
- (void)removeTimer{
|
||||
if (_timer) {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -相机,麦克风权限
|
||||
- (void)authorizationStatus{
|
||||
AVAuthorizationStatus videoStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
NSLog(@"允许访问相机权限");
|
||||
} else {
|
||||
NSLog(@"不允许相机访问");
|
||||
}
|
||||
}];
|
||||
|
||||
AVAuthorizationStatus audioStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
NSLog(@"允许麦克风权限");
|
||||
} else {
|
||||
NSLog(@"不允麦克风访问");
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -SGRecordEngineDelegate(录制进度回调)
|
||||
- (void)recordProgress:(CGFloat)progress{
|
||||
NSLog(@"progress:%f",progress);
|
||||
if (progress >= 0) {
|
||||
[_recordButton setProgress:progress];
|
||||
}
|
||||
if ((int)progress == 1) {
|
||||
_isEndRecord = YES;
|
||||
[self stopRecord:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -UIGestureRecognizerDelegate
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
|
||||
CGPoint point = [touch locationInView:self.view];
|
||||
if (point.y >= self.view.bounds.size.height - 190) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -设备不支持自动选择
|
||||
-(BOOL)shouldAutorotate{
|
||||
return NO;
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -SGMotionManagerDeviceOrientationDelegate --> 控制按钮方向
|
||||
-(void)motionManagerDeviceOrientation:(UIDeviceOrientation)deviceOrientation{
|
||||
|
||||
if (_lastDeviceOrientation == deviceOrientation) return;
|
||||
CGFloat angle = 0;
|
||||
switch (deviceOrientation) {
|
||||
case UIDeviceOrientationPortrait:
|
||||
angle = 0;
|
||||
break;
|
||||
case UIDeviceOrientationPortraitUpsideDown:
|
||||
angle = M_PI;
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeLeft:
|
||||
angle = M_PI_2;
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeRight:
|
||||
angle = -M_PI_2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_exitButton.transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle);
|
||||
_switchButton.transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle);
|
||||
}];
|
||||
_lastDeviceOrientation = deviceOrientation;
|
||||
NSLog(@"deviceOrientation:%ld",(long)deviceOrientation);
|
||||
}
|
||||
- (void)alart{
|
||||
[self.view bringSubviewToFront:self.alartLabel];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (_alartLabel) {
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_alartLabel.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[_alartLabel removeFromSuperview];
|
||||
_alartLabel = nil;
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark -dealloc
|
||||
- (void)dealloc{
|
||||
NSLog(@"%s",__func__);
|
||||
}
|
||||
@end
|
32
src/ios/SGRecord/UIButton+Convenience.h
Normal file
32
src/ios/SGRecord/UIButton+Convenience.h
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// UIButton+Convenience.h
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/22.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIButton (Convenience)
|
||||
|
||||
/**
|
||||
便利构造函数
|
||||
|
||||
@param imageName 图片名称
|
||||
@param target 代理
|
||||
@param action 响应事件
|
||||
@return UIButton 实例
|
||||
*/
|
||||
+ (UIButton *)image:(NSString *)imageName target:(id)target action:(SEL)action;
|
||||
|
||||
/**
|
||||
便利构造函数
|
||||
|
||||
@param title 提示问题
|
||||
@param target 代理
|
||||
@param action 响应事件
|
||||
@return UIButton 实例
|
||||
*/
|
||||
+ (UIButton *)title:(NSString *)title target:(id)target action:(SEL)action;
|
||||
@end
|
24
src/ios/SGRecord/UIButton+Convenience.m
Normal file
24
src/ios/SGRecord/UIButton+Convenience.m
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// UIButton+Convenience.m
|
||||
// 短视频录制
|
||||
//
|
||||
// Created by lihaohao on 2017/5/22.
|
||||
// Copyright © 2017年 低调的魅力. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIButton+Convenience.h"
|
||||
|
||||
@implementation UIButton (Convenience)
|
||||
+ (UIButton *)image:(NSString *)imageName target:(id)target action:(SEL)action{
|
||||
UIButton *button = [self buttonWithType:UIButtonTypeCustom];
|
||||
[button setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
|
||||
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
|
||||
return button;
|
||||
}
|
||||
+ (UIButton *)title:(NSString *)title target:(id)target action:(SEL)action{
|
||||
UIButton *button = [self buttonWithType:UIButtonTypeCustom];
|
||||
[button setTitle:title forState:UIControlStateNormal];
|
||||
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
|
||||
return button;
|
||||
}
|
||||
@end
|
Loading…
x
Reference in New Issue
Block a user