思路1:http://blog.sina.com.cn/s/blog_6d64a8df01018i5n.html
思路總結:http://www.jianshu.com/p/be8040eb4106
錄制螢幕操作,同時錄制聲音,合成視訊存入相冊:
https://github.com/Blazeice/ScreenAndAudioRecordDemoScreenAndAudioRecordDemo
本人在項目中先用ReplayKit實作了錄螢幕邏輯,根據https://github.com/cdpenggod/CDPReplay
項目改動而來,多謝作者
錄制代碼奉上
//
// ULCDPReplay.h
// UXLive
//
// Created by zhangxiwei on 2017/6/20.
// Copyright © 2017年 UXIN CO. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <Photos/Photos.h>
#import <UIKit/UIKit.h>
#import <ReplayKit/ReplayKit.h>
typedef void(^ULReplayPreviewBlock)(BOOL isSuccess,RPPreviewViewController *viewController);
/**
錄制回調delegate
*/
@protocol ULCDPReplayDelegate <NSObject>
@optional
/**
* 開始錄制回調
*/
- (void)replayRecordStart;
/**
錄制結束或錯誤回調
@param previewViewController 預覽viewController
@param errorInfo 錯誤資訊
*/
- (void)replayRecordFinishWithVC:(RPPreviewViewController *)previewViewController errorInfo:(NSString *)errorInfo;
/**
* 儲存到系統相冊成功回調
*/
- (void)saveSuccess;
@end
/**
系統版本需要是iOS9.0及以上才支援ReplayKit架構錄制
*/
@interface ULCDPReplay : NSObject
/**
* 代理
*/
@property (nonatomic,weak) id <ULCDPReplayDelegate> delegate;
/**
* 是否正在錄制
*/
@property (nonatomic,assign,readonly) BOOL isRecording;
/**
* 單例對象
*/
+ (instancetype)sharedReplay;
/**
開始錄制
@param block 回調
*/
- (void)startRecordBlock:(void(^)(BOOL successed, NSString *msg))block;
/**
結束錄制
@param isShow isShow 是否錄制完後自動展示視訊預覽頁
@param finishBlock 完成回調
@param previewBlock 預覽回調
*/
- (void)stopRecordAndShowVideoPreviewController:(BOOL)isShow
finishBlock:(void(^)(BOOL successed, NSURL *videoUrl ,NSString *msg))finishBlock
previewBlock:(ULReplayPreviewBlock)previewBlock;
/**
取消錄制
*/
- (void)cancelRecord;
@end
//
// ULCDPReplay.m
// UXLive
//
// Created by zhangxiwei on 2017/6/20.
// Copyright © 2017年 UXIN CO. All rights reserved.
//
#import "ULCDPReplay.h"
#import "ULAssertUtil.h"
@interface ULCDPReplay () <RPPreviewViewControllerDelegate>
@property (nonatomic, strong) NSDate *createDate;
@end
@implementation ULCDPReplay
/**
單例化對象
@return 執行個體
*/
+ (instancetype)sharedReplay {
static ULCDPReplay *replay=nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
replay=[[ULCDPReplay alloc] init];
});
return replay;
}
/**
是否正在錄制
@return 是否
*/
- (BOOL)isRecording {
return [RPScreenRecorder sharedRecorder].recording;
}
#pragma mark - 開始/結束錄制
- (void)startRecordBlock:(void (^)(BOOL success, NSString *message))block {
self.createDate = [NSDate date];
if ([RPScreenRecorder sharedRecorder].recording==YES) {
ULLogInfo(@"CDPReplay:已經開始錄制");
return;
}
if ([self systemVersionOK]) {
if ([[RPScreenRecorder sharedRecorder] isAvailable]) {
ULLogInfo(@"CDPReplay:錄制開始初始化");
[ULToast showProgress:@"錄屏初始化中..."];
[[RPScreenRecorder sharedRecorder] startRecordingWithMicrophoneEnabled:YES handler:^(NSError *error){
dispatch_main_sync_ulsafe(^{
[ULToast hideProgress];
});
if (error) {
ULLogInfo(@"CDPReplay:開始錄制error %@",error);
[[RPScreenRecorder sharedRecorder] discardRecordingWithHandler:^{
ULLogInfo(@"CDPReplay:discardRecordingWithHandler");
}];
if (block) {
block(NO, @"錄制失敗,請重試");
}
if ([_delegate respondsToSelector:@selector(replayRecordFinishWithVC:errorInfo:)]) {
[_delegate replayRecordFinishWithVC:nil errorInfo:[NSString stringWithFormat:@"CDPReplay:開始錄制error %@",error]];
}
} else {
ULLogInfo(@"CDPReplay:開始錄制");
if (block) {
block(YES, @"開始錄制");
}
if ([_delegate respondsToSelector:@selector(replayRecordStart)]) {
[_delegate replayRecordStart];
}
}
}];
} else {
if (block) {
block(NO, @"環境不支援螢幕錄制");
}
ULLogInfo(@"CDPReplay:環境不支援ReplayKit錄制");
if ([_delegate respondsToSelector:@selector(replayRecordFinishWithVC:errorInfo:)]) {
[_delegate replayRecordFinishWithVC:nil errorInfo:@"CDPReplay:環境不支援ReplayKit錄制"];
}
}
} else {
ULLogInfo(@"CDPReplay:系統版本需要是iOS9.0及以上才支援ReplayKit錄制");
if (block) {
block(NO, @"系統版本需要是iOS9.0及以上才支援螢幕錄制");
}
if ([_delegate respondsToSelector:@selector(replayRecordFinishWithVC:errorInfo:)]) {
[_delegate replayRecordFinishWithVC:nil errorInfo:@"CDPReplay:系統版本需要是iOS9.0及以上才支援ReplayKit錄制"];
}
}
}
- (void)stopRecordAndShowVideoPreviewController:(BOOL)isShow
finishBlock:(void (^)(BOOL, NSURL *, NSString *))finishBlock
previewBlock:(ULReplayPreviewBlock)previewBlock {
ULLogInfo(@"CDPReplay:正在結束錄制");
WS(weakSelf)
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController *previewViewController, NSError * error){
if (error) {
ULLogInfo(@"CDPReplay:結束錄制error %@", error);
[[RPScreenRecorder sharedRecorder] discardRecordingWithHandler:^{
ULLogInfo(@"CDPReplay:discardRecordingWithHandler");
}];
if ([_delegate respondsToSelector:@selector(replayRecordFinishWithVC:errorInfo:)]) {
[_delegate replayRecordFinishWithVC:nil errorInfo:[NSString stringWithFormat:@"CDPReplay:結束錄制error %@",error]];
}
if (finishBlock) {
finishBlock(NO, nil, @"錄制失敗,請稍後重試");
}
} else {
ULLogInfo(@"CDPReplay:錄制完成");
if ([_delegate respondsToSelector:@selector(replayRecordFinishWithVC:errorInfo:)]) {
[_delegate replayRecordFinishWithVC:previewViewController errorInfo:@""];
}
[previewViewController setPreviewControllerDelegate:weakSelf];
if (ULOSVersionFloat >= f && ULOSVersionFloat < f) {
if (previewBlock) {
previewBlock(YES, previewViewController);
}
} else {
NSURL *movieURL = [previewViewController valueForKey:@"movieURL"];
ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc]init];
[assetsLibrary writeVideoAtPathToSavedPhotosAlbum:movieURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
ULLogInfo(@"CDPReplay 儲存失敗 %@ %@", error, assetURL);
if (finishBlock) {
finishBlock(NO, assetURL, @"錄制成功");
}
} else {
ULLogInfo(@"CDPReplay 儲存成功 %@", assetURL);
if (finishBlock) {
finishBlock(YES, assetURL, @"錄制成功");
}
}
}];
}
}
}];
}
- (void)cancelRecord {
ULLogInfo(@"CDPReplay 取消錄制");
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController *previewViewController, NSError * error){
if (error) {
ULLogInfo(@"CDPReplay cancelRecord:結束錄制error %@", error);
} else {
ULLogInfo(@"CDPReplay cancelRecord:錄制完成");
}
[[RPScreenRecorder sharedRecorder] discardRecordingWithHandler:^{
}];
}];
}
#pragma mark - 其他方法
/**
判斷對應系統版本是否支援ReplayKit
@return 是否支援系統
*/
- (BOOL)systemVersionOK {
if (IS_IOS9_OR_LATER) {
return YES;
} else {
return NO;
}
}
@end
注意點:
- 系統版本在iOS10 以上
NSURL *movieURL = [previewViewController valueForKey:@"movieURL"];
通過私有變量能擷取到存儲位址,可以直接讀取視訊位址了,但是存在幾個坑,各位有好辦法也可以同步,我直接拿位址操作時不行的,以為位址的目錄是在ReplayKit的一個目錄下,個人了解應該是跨應用了,資料保護之類的(猜測),我做了一把直接存儲到相冊,然後再從相冊裡面讀取就能對檔案進行操作了。
- 系統 iOS9~iOS 10包含iOS9
該系統下通過previewViewController通過上述方法是找不到的,列印一下私有屬性就能證明,隻能通過提供的previewViewController來儲存做操作。我的方法是引導使用者去點選存儲,然後通過點選存儲回調從相冊裡面擷取最新的檔案,(坑出現)雖然給的回調是存儲回調,但是一般情況下這個時候檔案還沒有存儲成功,我延遲1s後擷取。因為我們一般都是限制60s以内的視訊,是以一般都沒有問題。
擷取最新的asset
+ (PHAsset *)latestAsset {
// 擷取所有資源的集合,并按資源的建立時間排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld",
PHAssetMediaTypeVideo];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
return [assetsFetchResults firstObject];
}