天天看點

iOS 錄制螢幕

思路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];
}