#import
NS_ASSUME_NONNULL_BEGIN
@interface RecordManager : NSObject
@property (nonatomic, weak) UIViewController *baseVC;
- (void)startRecordWithForceRecord:(BOOL)forceRecord;
- (void)stopRecord;
@end
NS_ASSUME_NONNULL_END
#import "RecordManager.h"
#import
#import
@interface RecordManager ()
@property (strong, nonatomic) RPScreenRecorder *screenRecorder;
@property (strong, nonatomic) AVAssetWriter *assetWriter;
@property (strong, nonatomic) AVAssetWriterInput *videoInput;
@property (strong, nonatomic) AVAssetWriterInput *audioInput;
@property (assign, nonatomic) BOOL forceRecord;
@end
@implementation RecordManager
- (RPScreenRecorder *)screenRecorder {
if (!_screenRecorder) {
_screenRecorder = [RPScreenRecorder sharedRecorder];
}
return _screenRecorder;
}
- (void)startRecordWithForceRecord:(BOOL)forceRecord {
NSLog(@"ReplayKit只支持真机录屏,支持游戏录屏,不支持录avplayer播放的视频");
NSLog(@"检查机器和版本是否支持ReplayKit录制...");
if ([self.screenRecorder isAvailable]) {
NSLog(@"支持ReplayKit录制");
} else {
NSLog(@"!!不支持支持ReplayKit录制!!");
return;
}
if (self.screenRecorder.isRecording) {
NSLog(@"stop first");
return;
}
_forceRecord = forceRecord;
NSLog(@"开始录制");
if (@available(iOS 11.0, *) && !forceRecord) {
[self startCapture];
} else if (@available(iOS 10.0, *)) {
//在此可以设置是否允许麦克风(传YES即是使用麦克风,传NO则不是用麦克风)
self.screenRecorder.microphoneEnabled = YES;
[self.screenRecorder startRecordingWithHandler:^(NSError * _Nullable error) {
NSLog(@"录制开始...");
if (error) {
NSLog(@"错误信息 %@", error);
} else {
}
}];
} else {
//在此可以设置是否允许麦克风(传YES即是使用麦克风,传NO则不是用麦克风)
[self.screenRecorder startRecordingWithMicrophoneEnabled:YES handler:^(NSError *error){
NSLog(@"录制开始...");
if (error) {
NSLog(@"错误信息 %@", error);
} else {
}
}];
}
}
- (void)stopRecord {
NSLog(@"结束录制");
if (!self.screenRecorder.isRecording) {
return;
}
if (@available(iOS 11.0, *) && !_forceRecord) {
[self stopCapture];
} else {
[self.screenRecorder stopRecordingWithHandler:^(RPPreviewViewController *previewViewController, NSError * error){
if (error) {
NSLog(@"失败消息:%@", error);
} else {
NSLog(@"录制完成");
if (@available(iOS 10.0, *)) {
/*
iOS 10 这里我选择直接保存到相册,不使用 previewViewController 弹出来
*/
[self handleiOS10:previewViewController];
} else if (@available(iOS 9.0, *)) {
/*
在iOS9 里面取不到 movieURL 这个变量,程序会崩溃. 所以直接弹出这个 previewViewController 让用户来操作保存
*/
[self handleiOS9:previewViewController];
}
}
}];
}
}
- (void)startCapture {
if (self.screenRecorder.isRecording) {
return;
}
NSError *error = nil;
NSString *outputURL = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *fileName = [NSString stringWithFormat:@"Capture%@.mp4", [NSDate date]];
NSString *videoOutPath = [outputURL stringByAppendingPathComponent:fileName];
self.assetWriter = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:videoOutPath] fileType:AVFileTypeMPEG4 error:&error];
if (@available(iOS 11.0, *)) {
NSDictionary *compressionProperties = @{AVVideoProfileLevelKey : AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey : AVVideoH264EntropyModeCABAC,
AVVideoAverageBitRateKey : @(1000000),
AVVideoMaxKeyFrameIntervalKey : @1000,
AVVideoAllowFrameReorderingKey : @NO};
NSNumber* width= @1920;//@([UIScreen mainScreen].bounds.size.width);
NSNumber* height = @1440;//@([UIScreen mainScreen].bounds.size.height);
NSDictionary *videoSettings = @{AVVideoCompressionPropertiesKey : compressionProperties,
AVVideoCodecKey : AVVideoCodecTypeH264,
AVVideoWidthKey : width,
AVVideoHeightKey : height};
self.videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
AudioChannelLayout acl;
bzero(&acl, sizeof(acl));
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
NSDictionary *audioSettings = @{AVFormatIDKey : [ NSNumber numberWithInt: kAudioFormatMPEG4AAC],
AVNumberOfChannelsKey : @1,
AVSampleRateKey : @16000,
AVChannelLayoutKey : [ NSData dataWithBytes: &acl length: sizeof( AudioChannelLayout ) ]};
self.audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
} else {
// Fallback on earlier versions
}
if([self.assetWriter canAddInput:self.videoInput]) {
[self.assetWriter addInput:self.videoInput];
}
if([self.assetWriter canAddInput:self.audioInput]) {
[self.assetWriter addInput:self.audioInput];
}
// [self.assetWriter addInput:self.audioInput];
[self.videoInput setMediaTimeScale:60];
[self.assetWriter setMovieTimeScale:60];
[self.videoInput setExpectsMediaDataInRealTime:YES];
if (@available(iOS 11.0, *)) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (granted)
{
[self.screenRecorder setMicrophoneEnabled:YES];
__block BOOL start = NO;
[self.assetWriter startWriting];
// [self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
[self.screenRecorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if (CMSampleBufferDataIsReady(sampleBuffer)) {
if (!start) {
start = YES;
[self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
}
if (self.assetWriter.status == AVAssetWriterStatusFailed) {
NSLog(@"An error occured.");
//show alert
[self.screenRecorder stopCaptureWithHandler:^(NSError * _Nullable error) {}];
return;
}
if (bufferType == RPSampleBufferTypeVideo) {
if (self.videoInput.isReadyForMoreMediaData) {
[self.videoInput appendSampleBuffer:sampleBuffer];
}else{
NSLog(@"Not ready for video");
}
}else if(bufferType == RPSampleBufferTypeAudioMic){
if (self.audioInput.isReadyForMoreMediaData) {
[self.audioInput appendSampleBuffer:sampleBuffer];
}else{
NSLog(@"Not ready for audio");
}
}
}
} completionHandler:^(NSError * _Nullable error) {
if (!error) {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
// Start recording
NSLog(@"Recording started successfully.");
}else{
//show alert
}
}];
}
});
}];
} else {
// Fallback on earlier versions
}
}
- (void)stopCapture {
if (@available(iOS 11.0, *)) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.screenRecorder stopCaptureWithHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(@"Recording stopped successfully. Cleaning up...");
[self.videoInput markAsFinished];
[self.audioInput markAsFinished];
[self.assetWriter finishWritingWithCompletionHandler:^{
NSLog(@"File Url: %@",self.assetWriter.outputURL);
self.videoInput = nil;
self.audioInput = nil;
self.assetWriter = nil;
self.screenRecorder = nil;
}];
}
}];
});
} else {
// Fallback on earlier versions
NSLog(@"hello");
}
}
/// 处理 iOS 10
- (void)handleiOS10:(RPPreviewViewController *)previewViewController {
NSURL *videoURL = [previewViewController valueForKey:@"movieURL"];
NSLog(@"在previewViewController找到的视频路径 %@", videoURL.absoluteString);
if (videoURL) {
BOOL compatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([videoURL path]);
if (compatible) {
UISaveVideoAtPathToSavedPhotosAlbum([videoURL path], self, @selector(savedPhotoImage:didFinishSavingWithError:contextInfo:), nil);
}
} else {
NSLog(@"没有找到 movieURL");
}
}
#pragma mark - 处理 iOS 9
- (void)handleiOS9:(RPPreviewViewController *)previewViewController {
previewViewController.previewControllerDelegate = self;
[self.baseVC presentViewController:previewViewController animated:YES completion:nil];
}
// 视频预览页面 回调
//关闭的回调
- (void)previewControllerDidFinish:(RPPreviewViewController *)previewController {
[previewController dismissViewControllerAnimated:YES completion:nil];
}
//选择了某些功能的回调(如分享和保存)
- (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet *)activityTypes {
if ([activityTypes containsObject:@"com.apple.UIKit.activity.SaveToCameraRoll"]) {
NSLog(@"已经保存到系统相册");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self saveVideoToDocument];
});
}
if ([activityTypes containsObject:@"com.apple.UIKit.activity.CopyToPasteboard"]) {
NSLog(@"复制成功");
}
}
#pragma mark - iOS 10 保存视频的回调
//保存视频完成之后的回调
- (void)savedPhotoImage:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
if (error) {
NSLog(@"保存视频失败 == %@", error.description);
} else {
//取出这个视频并按创建日期排序
[self saveVideoToDocument];
}
}
//保存视频至沙盒
- (void)saveVideoToDocument {
//取出这个视频并按创建日期排序
PHFetchOptions * options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult * assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
PHAsset * phasset = [assetsFetchResults lastObject];
if (phasset) {
//视频文件
if (phasset.mediaType == PHAssetMediaTypeVideo) {
PHImageManager * manager = [PHImageManager defaultManager];
[manager requestAVAssetForVideo:phasset options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
AVURLAsset * urlAsset = (AVURLAsset *)asset;
NSString *fileName = [NSString stringWithFormat:@"Record%@.mp4", [NSDate date]];
NSString * outPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:fileName];
NSURL *videoURL = urlAsset.URL;
NSLog(@"在相册找到的视频路径 %@", videoURL.absoluteString);
[self extractVideoFromAlbum:videoURL outputURL:[NSURL fileURLWithPath:outPath] blockHandler:^(AVAssetExportSession * _Nonnull session) {
if (session.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"视频已处理好可以对其进行操作");
//处理完的视频是否需要删除?自行决定
} else {
NSLog(@"视频压缩出错...");
}
}];
}];
} else {
NSLog(@"未成功保存视频...");
}
} else {
NSLog(@"未成功保存视频...");
}
}
- (void)extractVideoFromAlbum:(NSURL *)inputURL
outputURL:(NSURL *)outputURL
blockHandler:(void (^)(AVAssetExportSession * _Nonnull))handler {
AVURLAsset * asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession * session = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
session.outputURL = outputURL;
session.outputFileType = AVFileTypeMPEG4;
[session exportAsynchronouslyWithCompletionHandler:^(void) {
if (handler) {
handler(session);
}
}];
}
@end