美文网首页iOS开发常用iOS之Swift框架理解经验demo
iOS-你不知道的系统录屏框架-ReplayKit

iOS-你不知道的系统录屏框架-ReplayKit

作者: 清無 | 来源:发表于2016-08-10 17:38 被阅读6307次

    前段时间公司项目有个要求,让找一个第三方的录屏框架,实现画板绘画过程的录制,github找了好久,但没有让人十分满意又符合项目要求的,所以就研究了下系统提供的ReplayKit,这里整理下,发出来,用到的书友们可以借鉴学习。

    • The ReplayKit framework provides the ability to record video and audio within an app. Users can then share the resulting recording with other users through social media, and you can build app extensions for live broadcasting your content to sharing services. ReplayKit is not compatible with AVPlayer content.
    • 简单说,RP - ReplayKit能够用来在你的app中录制视频和音频,可以通过社交媒体分享你录制的作品。

    • 但它目前还有如下一些不足:

      1. 不支持AVPlayer播放的视频录制
      2. 不支持模拟器
      3. 无法自定义RPPreviewViewController预览视图
      4. 无法修改录制视频保存路径
      5. 只支持 iOS9.0+ 版本
    • 几张效果图

      1. 录屏权限提醒


        1.png
      2. 开始录制


        2.png
      3. 录制完成,预览 - 这个vc按钮样式什么的没法定制很不好,除非找到私有api


        3.png
      4. 录制的视频保存在系统相册,然后读取


        4.png

    1. ReplayKit相关的class和protocol

    * classes:

    RPPreviewViewController - 录制完成预览vc

    The RPPreviewViewController class displays a user interface that allows users to preview and edit a screen recording created with ReplayKit. The preview view controller is passed into the completion handler for stopRecordingWithHandler: upon a successful recording.

    RPScreenRecorder - 真正录屏的类

    Use the RPScreenRecorder class to implement the ability to record audio and video of your app.

    * protocols:

    RPPreviewViewControllerDelegate - 录制完成预览vc的代理回调协议 - 录制完成预览页面

    Implement the RPPreviewViewControllerDelegate protocol to respond to changes to a screen recording user interface, represented by a RPPreviewViewController object.

    RPScreenRecorderDelegate - 录屏功能回调协议 - 录屏开始 / 结束 / 出错接受通知

    Implement the RPScreenRecorderDelegate protocol to receive notifications from an RPScreenRecorder object. The delegate is called when recording stops or there is a change in recording availability.

    • ReplayKit毕竟是比较新的一个框架,所以它能实现的功能也比较简单单一,方法和协议也比较少。

    2.实现思路

    • 主要难点是录制完成后,保存到系统相册,怎么区分并且分类你录制的视频,比如录屏1,2,3
    • 如何播放录制的视频,注意系统相册的video是不支持将url传递来达到播放的,所以必须找到你录制的视频,把它拷贝到你的沙盒目录下,顺便进行格式转换、压缩等,以便以后读取,分类。
    简单实现流程

    3.具体实现过程 - 关键代码

    1. 录制前的检测: 设备是否是真机、iOS版本是否>9.0、录屏硬件是否可用
        // 真机模拟器检测
        struct Platform {
            static let isSimulator: Bool = {
                var isSim = false
                #if arch(i386) || arch(x86_64)
                    isSim = true
                #endif
                return isSim
            }()
        }
    
        // Alert提示框
        private func showUnsuportAlert(message: String, showVC: UIViewController){
            let ok = UIAlertAction(title: "好的", style: .Default) { (action) in
            }
            let alert = UIAlertController(title: nil, message: message, preferredStyle: .Alert)
            alert.addAction(ok)
            showVC .presentViewController(alert, animated: true, completion: nil)
        }
    
        // 是真机还是模拟器
        func isPlatformSuport(showVC: UIViewController) -> Bool{
            if Platform.isSimulator {
                showUnsuportAlert("录屏功能只支持真机设备", showVC: showVC)
                return false
            }
            return true
        }
        
        // 系统版本是9.0
        func isSystemVersionSuport(showVC: UIViewController) -> Bool{
            if NSString(string: UIDevice.currentDevice().systemVersion).floatValue < 9.0 {
                showUnsuportAlert("录屏功能要求系统版本9.0以上", showVC: showVC)
                return false
            }
            return true
        }
        
        // 检查是否可用
        func isRecorderAvailable(showVC: UIViewController) -> Bool {
            if !RPScreenRecorder.sharedRecorder().available{
                showUnsuportAlert("录屏功能不可用", showVC: showVC)
                return false
            }
            return true
        }
    
    2. 录制功能类: 开始、结束
        // 开始
        func startCapture(){
            print("录屏初始化...")
            let recorder = RPScreenRecorder.sharedRecorder()
            recorder.delegate = self
            // 关键方法
            recorder.startRecordingWithMicrophoneEnabled(false) { (error) in
                print("录屏开始")
                self.delegate?.didStartRecord()
                if let error = error {
                    self.delegate?.didStopWithError(error)
                }
            }
        }
        
        // 完成
        func stopCapture(){
            let recorder = RPScreenRecorder.sharedRecorder()
            // 关键方法
            recorder.stopRecordingWithHandler { (previewController, error) in
                if let error = error {
                    self.delegate?.didStopWithError(error)
                } else if let preview = previewController{
                    print("录屏完成")
                    preview.previewControllerDelegate = self
                    self.delegate?.didFinishRecord(preview)
                    print("显示预览页面...")
                }
            }
        }
        
        // 自定义录屏manager协议   
        protocol ScreenCaptureManagerDelegate: class{
            func didStartRecord() // 正在录制
            func didFinishRecord(preview: UIViewController) // 完成录制
            func didStopWithError(error: NSError) //发生错误停止录制
            func savingRecord() // 保存
            func discardingRecord() // 取消保存
        }
    
    3. 代理方法: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate
    extension ScreenCaptureManager: RPScreenRecorderDelegate{
        // 录制出错而停止
        func screenRecorder(screenRecorder: RPScreenRecorder, didStopRecordingWithError error: NSError, previewViewController: RPPreviewViewController?) {
            delegate?.didStopWithError(error)
        }
    }
    
    extension ScreenCaptureManager: RPPreviewViewControllerDelegate{
        // 取消回调
        func previewControllerDidFinish(previewController: RPPreviewViewController) {
            print("previewControllerDidFinish")
            dispatch_async(dispatch_get_main_queue()) {
                self.delegate?.discardingRecord() // 取消保存
            }
        }
        
        // 保存-分享等的回调
        func previewController(previewController: RPPreviewViewController, didFinishWithActivityTypes activityTypes: Set<String>) {
            if activityTypes.contains("com.apple.UIKit.activity.SaveToCameraRoll") {
                dispatch_sync(dispatch_get_main_queue(), {
                    self.delegate?.savingRecord() // 正在保存
                })
            }
        }
    }
    
    4. 录屏完成后的视频处理逻辑类:CaptureVideoManager

    1. import AssetsLibrary
    2. 你可以在将录制的视频转存到沙盒目录后,删除系统相册里的视频,以减小空间,但每次会提示用户是否删除,用户体验很不好,后来没找到解决方法就直接不删除了

        // 导出视频
        private func outputVideo(url: NSURL, board: Blackboard, model: CaptureVideo, success: (() -> Void)?){
            let asset = AVAsset(URL: url)
            let fmanager = NSFileManager.defaultManager()
            let path = blackboardPath(board.id) + "/\(BlackboardFileName.video.rawValue)"
            if !fmanager.fileExistsAtPath(path) {
                guard model.URL != nil else{
                    return
                }
                
                let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
                session?.outputFileType = AVFileTypeQuickTimeMovie //MOV
                session?.outputURL = NSURL(fileURLWithPath: path)
                session?.exportAsynchronouslyWithCompletionHandler({
                    ()in
                    PrintUtil.Log("导出视频成功", args: nil)
                    
                    dispatch_async(dispatch_get_main_queue(), {
                        if success != nil{
                            success!()
                        }
                    })
                })
            }
        }
    
        // 视频操作 - model转换
        private func operatingVideoAsset(board: Blackboard, asset: ALAsset, success: (() -> Void)?){
            let name = asset.defaultRepresentation().filename()
            let url = asset.defaultRepresentation().url()
            let model = CaptureVideo(URL: url, videoName: name, board: nil) //录制视频model类
            // 保存预览图
            let img = UIImage(CGImage: asset.aspectRatioThumbnail().takeUnretainedValue())
            let path = blackboardPath(board.id) + "/\(BlackboardFileName.thumbnail.rawValue)"
            if NSFileManager.defaultManager().createFileAtPath(path, contents: UIImagePNGRepresentation(img), attributes: nil){
                
                PrintUtil.Log("保存视频预览图成功", args: [path])
                // 导出视频
                outputVideo(url, board: board, model: model, success: success)
            }
        }
    
        // 提取刚才录制的视频 - NSEnumerationOptions.Reverse 倒序遍历,时间
        func saveVideo(board: Blackboard, success: (() -> Void)?){
            assets.enumerateGroupsWithTypes(ALAssetsGroupSavedPhotos, usingBlock: {
                (group, stop) in
                if group != nil{
                    PrintUtil.Log("group", args: [group])
                    // 过滤
                    group.setAssetsFilter(ALAssetsFilter.allVideos())
                    group.enumerateAssetsWithOptions(NSEnumerationOptions.Reverse, usingBlock:          { (asset, index, stop2) in
                        if asset != nil{
                            let name = asset.defaultRepresentation().filename()
                            // 特征点,一般为app boundle id
                            if name.containsString("com.founder.OrangeClass"){ 
                                // 是否是要的视频
                                self.operatingVideoAsset(board, asset: asset, success: success)
                                PrintUtil.Log("asset", args: [asset,index])
                                // 停止遍历 - 倒序遍历,第一个就是刚才录制的
                                stop2.memory = true
                            }
                        }
                    })
                }
                
            }) { (error) in
                PrintUtil.Log("error", args: [error])
            }
        }
    

    这里差不多了,可能没有讲太清楚,希望用到的童鞋可以参考下,共同学习探讨。

    • 稍后整理下代码,上传到github

    有空接着写完吧 懒懒~

    相关文章

      网友评论

      • 小别胜新婚:请问一定要跳RPPreviewViewController才能保存录制视频吗,我看王者荣耀 阴阳师等游戏直接点击保存按钮就存在相册里了
        清無:@小别胜新婚 去github找一找第三方有没有录屏的框架。
        小别胜新婚:@清無 感谢回复,目前确实只能这样了,好无奈:yum:
        清無:@小别胜新婚 目前是只能这样的,其他公司做的不是用这个库,而是自己封装的一帧一帧的图合成了视频。苹果这个不人性化,可定制度不高。
      • 沃小沃:能问下,replaykit 录制出的视频流是什么格式,比如说我想将其中的一帧转化成图片怎么转化
      • Heikki_:你好 我的RPPreviewViewController 上面的按钮都是英文的 请问怎么解决呢
        Lucas_5:这个问题解决了吗?我现在系统是11.4的怎么设置按钮都是英文的。。
        Heikki_::sweat_smile:谢谢大佬
        清無:@Heikki_ 这个可以在设置里,将语言设置为中文,然后看看,不行的话,就是系统默认为英文,无法修改;
      • iOS学习着ne:各位朋友有没有遇到,第一次录屏,调用停止录屏方法没有回调的情况!
        CoderZNB:@iOS学习着ne 是的,重启手机就行了
        iOS学习着ne:@CoderZNB 不可能吧,再试试,或者重启手机试试
        CoderZNB:我开始录屏都没有回调🤣
      • dbe6d1c29bbe:很好奇 画板的redo undo是怎么实现的
        dbe6d1c29bbe:@菲拉兔 我的想法是定义一个协议 然后每个操作都遵循这个协议 实现协议方法 redo undo
        清無:@叉叉歪 总体是用归/解档的方式
        清無:@叉叉歪 见我的另一篇文章:http://www.jianshu.com/p/5b0eb262e290
      • 背着吉他去流浪:这个录屏只支持当前App的录制么? 如果是需要后台录制的怎么办?
        背着吉他去流浪:@菲拉兔 好吧, 谢谢老铁了~
        清無: @背着吉他去流浪 只支持app内录屏。。。其他方法iOS是没法实现的
      • 红胡子老头:hi,大神,ios11 之后,点击保存 ,在相册中不能找到自己录制的视频了嘞
        - (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet <NSString *> *)activityTypes {

        if ([activityTypes containsObject:@"com.apple.UIKit.activity.SaveToCameraRoll"]) {
        dispatch_async(dispatch_get_main_queue(), ^{
        [previewController dismissViewControllerAnimated:YES completion:^{
        [previewController.view removeFromSuperview];
        [previewController removeFromParentViewController];
        }];




        });
        }
        我是这样的
      • iOS学习着ne:hey Blockboard是什么类啊?有demo了没?哥们
        清無: @红胡子老头 不应该哇,我得看看去,还没再11上试过
        红胡子老头:ios 11 之后,这个点击保存之后,不能保存到相册中了嘞
        清無:@iOS学习着ne 是一个抽出来的画板,我前段时间在整理,发现以前公司哥们写得c++库只支持真机,所以后面有空发出来。。。见谅:flushed:
      • 秋天的橘子:大神, git地址
        秋天的橘子:大神,有个地方问一下,我开始录制以后,结束录制的时候,会弹出一个视屏回放的view,上面有保存等选项,但是我不想要这个view,我想我点结束录制以后,直接自动保存在相册? 该怎么做?
        秋天的橘子:大神,正在写录屏,希望能得到指点。Q631487050。
        清無:@秋天的橘子 :smile: 忙忙忙
      • 神一样的队友:请问git地址呢?
        清無::smiley: 还没整理好
      • FANTASIED:大神,,画板不错耶~~要不要分享一下:heartbeat:
        FANTASIED:@菲拉兔 好,加油大神,开源有你更精彩:yum:
        清無:@FANTASIED 还没整理出来,just await patiently😁
      • 豆宝的老公:你好,请问一下,不要RPPreviewViewController预览视图的话,怎么保存录制的视频?
        豆宝的老公:@菲拉兔 好吧,现在确实还比较坑
        清無: @allentsing 好像暂时还不行,因为没有相关的saveVideo等接口开放。。。
      • 丶小六丶:视频并不能导出来
        清無:@丶小六丶 如果是iOS10的话,你的设置下Info.plist增加对相册,相机的访问privacy权限,试试。
        丶小六丶:@菲拉兔 就是用的这个,提示没有权限
        清無:@丶小六丶 视频你得利用AVSession重新copy方式保存到沙盒下,然后就可以取出来。
      • Uncle_Gao:大神 请教下 这个框架是否支持后台录屏功能啊,目前我只是实现了像你说的当前App内录屏的功能,那如果想要让它在后台运行,录制屏幕,是否有办法呢 谢了
        Uncle_Gao:@菲拉兔 貌似想单纯的在手机端实现我的这种录屏功能,暂时解决不了了,只能通过AirPlay镜像投到电脑上才能实现了。就是不太方便。
        Uncle_Gao:@菲拉兔 嗯嗯 已经研究有一段时间了 不论是自己基于replayKit这个写的 还是市面上有的 最多都只能实现后台录屏,但是还是无法达到我的要求,我的目的是想在微信视频过程中录屏,不知道是不是微信做了什么限制还是什么,总之在视频过程中,录屏总是失败,甚是无奈。
        清無:@Uncle_Gao 后台录屏不行的。。。因为它是基于当前应用的keyWindow而不是mainScreen进行的渲染录制。。。后台录屏是系统级的,目前估计还没有好的方法吧,不想安卓可以悬浮一个按钮,全局操作,iOS退到后台,不可能让你进行这么“耗资源”的事情。。。建议你搜下相关应用,比如录屏。。。
      • Yishionnnn:请问我们游戏加入了replaykit,可是录制的时候游戏声音会变的特别小,结束录制之后会回复正常,请问大神是什么原因呢
        清無:@Yishionnnn 我们之前有这么搞过一次:将屏幕截图存储,最后合成视频,按帧率20...但效率不高
        Yishionnnn:@菲拉兔 坑好多这个replaykit,第三方库最大的问题就是东西不是你写的,想找到问题,好麻烦
        清無:@Yishionnnn 还真没遇到这个问题,回头看看,我也小白共同学习。
      • 哥只是个菜鸟:可以录制直播流视频吗?我在做一个监控摄像头直播相关的app
        清無:@哥只是个菜鸟 这个应该不可以,系统不支持,建议找第三方库。
        哥只是个菜鸟:@菲拉兔 那可以改变录制frame大小吗?不想录制全屏,只需要录制屏幕一部分区域
        清無:@哥只是个菜鸟 理论上可以,因为只要是当前app内部【屏幕】的操作,都会被录制下来
      • boovit:这个录屏只能录keyWindow的么
        清無: @boovit 被你问住了。。。目前假定是吧,我回头看看
      • 朝阳独行者:你知道为什么录制时app播放app内的声音就录制不下来声音为什么
        朝阳独行者:@菲拉兔 非常感谢您的回答 我都试过了,不行的,外部的声音在app播放声音时也是录不到的,不知道为什么, 另外一点 官方API说是可以录制app内音频的 ,以后多交流,谢谢
        清無:recorder.startRecordingWithMicrophoneEnabled(`这里改为true试试`) { (error) in
        print("录屏开始")
        self.delegate?.didStartRecord()
        if let error = error {
        self.delegate?.didStopWithError(error)
        }
        }
        清無:这个原因是:RPKit现在还不支持。
        也就说系统支持由通过外接/系统内置的麦克疯传入的声音录制。
        苹果推出RPKit主要是为了“游戏玩家”的分享,比如他们录制自己玩游戏的过程,然后社交分享。
        所以苹果这个新的库还不是很支持除了游戏录制视频外的其他应用场景。。。
      • Damon4Zhou:大神,这个画板不错!一起上到git上去吧:blush:
      • Damon4Zhou:git地址呢,大神!

      本文标题:iOS-你不知道的系统录屏框架-ReplayKit

      本文链接:https://www.haomeiwen.com/subject/hesjsttx.html