【iOS】iOS系统录制屏幕框架 ReplayKit 使用教程

ReplayKit

Record or stream video from the screen, and audio from the app and microphone.

Overview

Using the ReplayKit framework, users can record video from the screen, and audio from the app and microphone. They can then share their recordings with other users through email, messages, and social media. You can build app extensions for live broadcasting your content to sharing services. ReplayKit is incompatible with AVPlayer content.

概述

使用 ReplayKit 框架,用户可以从屏幕上录制视频,并从应用程序内和麦克风录制音频。 然后他们也可以通过电子邮件、消息和社交媒体与其他用户共享他们的录音。 您还可以构建应用扩展程序,共享服务。 注意:ReplayKit 与 AVPlayer 内容不兼容。

ReplayKit 优点:

1、内存、CPU占用极小可以忽略不计
2、录制清晰度和流畅度非常高
3、不需要导入一些SDK包,对APP的负担几乎为0
4、录制代码实现简单

ReplayKit 缺点:

1、不支持AVPlayer播放的视频录制
2、不支持模拟器
3、无法自定义RPPreviewViewController预览视图
4、无法修改录制视频保存路径
5、录制的原始视频清晰度非常高,导致整个视频文件非常大
6、部分机型部分系统录制会有失败的情况,稳定性有待商榷
7、只支持 iOS9.0+ 版本

录屏过程:

1、检测是否有相册权限(后期不需要本地操作录屏可省略)
2、检测是否支持录屏(iOS9.0+)
3、开始录屏
4、结束录屏
5、将录屏保存到相册
6、拿出相册里当前录屏进行压缩处理(压缩规格可修改)
7、压缩后的录屏保存至沙盒后可进行任意操作

部分代码:

1、获取相册权限
- (void)startRecord {
    
    [MXReplayManager mx_getPHAuthorizationStatusBlock:^(BOOL success) {
        if (success) {
            [self mx_recorderAvailable];
        }
    }];
}
2、录屏是否可用
// 录屏是否可用
- (void)mx_recorderAvailable {
    
    if ([_recorder isAvailable]) {
        [self mx_startCapture];
    } else {
        NSLog(@"请允许App录制屏幕且使用麦克风(选择第一项),否则无法进行录屏");
    }
}
3、开始录屏
// 开始录屏(分系统)
- (void)mx_startCapture {
    
    //是否录麦克风的声音(如果只想要App内的声音,设置为NO即可)
    _recorder.microphoneEnabled = NO;

    if ([_recorder isRecording]) {
        NSLog(@"正在录制...");
    } else {
        if (@available(iOS 11.0, *)) {
            [_recorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
                //CMSampleBufferRef 视频+音频原始帧数据 (帧数据处理可参考部分开源直播SDk)
                switch (bufferType) {
                    case RPSampleBufferTypeVideo:     //视频
                        break;
                     case RPSampleBufferTypeAudioApp: //App内音频
                        break;
                    case RPSampleBufferTypeAudioMic:  //麦克风音频
                        break;
                    default:
                        break;
                }
            } completionHandler:^(NSError * _Nullable error) {
                
            }];
        } else if (@available(iOS 10.0, *)) {
            [_recorder startRecordingWithHandler:^(NSError * _Nullable error) {
                if (!error) {
                    NSLog(@"启动录屏成功...");
                }
            }];
        } else if (@available(iOS 9.0, *)) {
            [_recorder startRecordingWithMicrophoneEnabled:NO handler:^(NSError * _Nullable error) {
                if (!error) {
                    NSLog(@"启动录屏成功...");
                }
            }];
        }
    }
}
4、结束录屏
//停止录屏
- (void)stopRecord {
 
    if (@available(iOS 11.0, *)) {
        [_recorder stopCaptureWithHandler:^(NSError * _Nullable error) {
            
        }];
    } else {
        [_recorder stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
            NSURL * videoURL = [previewViewController valueForKey:@"movieURL"];
            if (!videoURL) {
                NSLog(@"录屏失败...");
            } else {
                //是否需要展示预览界面给用户,自行决定
                [self mx_saveVideoToPhoto:videoURL];
            }
        }];
    }
}
5、保存视频至相册
//保存视频至相册
- (void)mx_saveVideoToPhoto:(NSURL *)videoURL {
    
    NSString * videoPath = [videoURL path];
    BOOL compatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(videoPath);
    if (compatible) {
        UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, @selector(savedPhotoImage:didFinishSavingWithError:contextInfo:), nil);
    }
}

6、保存视频完成之后的回调
//保存视频完成之后的回调
- (void)savedPhotoImage:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    
    if (error) {
        NSLog(@"保存视频失败 == %@", error.description);
    } else {
        //取出这个视频并按创建日期排序
        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;
                    [self mx_saveVideoToDocument:urlAsset.URL];
                }];
            } else {
                NSLog(@"未成功保存视频...");
            }
        } else {
            NSLog(@"未成功保存视频...");
        }
    }
}
7、保存视频至沙盒
//保存视频至沙盒
- (void)mx_saveVideoToDocument:(NSURL *)videoURL {
    
    NSString * outPath  = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[@"mx_test_replay" stringByAppendingString:@".mp4"]];
    [MXReplayManager mx_compressQuailtyWithInputURL:videoURL outputURL:[NSURL fileURLWithPath:outPath] blockHandler:^(AVAssetExportSession * _Nonnull session) {
        if (session.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"视频已处理好可以对其进行操作");
            //处理完的视频是否需要删除?自行决定
        } else {
            NSLog(@"视频压缩出错...");
        }
    }];
}
8、压缩视频
+ (void)mx_compressQuailtyWithInputURL:(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);
        }
    }];
}

GitHub地址


原创文章,转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349