iOS-你不知道的系統(tǒng)錄屏框架-ReplayKit

前段時間公司項目有個要求,讓找一個第三方的錄屏框架,實現(xiàn)畫板繪畫過程的錄制坐搔,github找了好久婉徘,但沒有讓人十分滿意又符合項目要求的漠嵌,所以就研究了下系統(tǒng)提供的ReplayKit,這里整理下盖呼,發(fā)出來儒鹿,用到的書友們可以借鑒學(xué)習(xí)。

  • 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預(yù)覽視圖
    4. 無法修改錄制視頻保存路徑
    5. 只支持 iOS9.0+ 版本
  • 幾張效果圖

    1. 錄屏權(quán)限提醒


      1.png
    2. 開始錄制


      2.png
    3. 錄制完成,預(yù)覽 - 這個vc按鈕樣式什么的沒法定制很不好圾浅,除非找到私有api


      3.png
    4. 錄制的視頻保存在系統(tǒng)相冊掠手,然后讀取


      4.png

1. ReplayKit相關(guān)的class和protocol

* classes:

RPPreviewViewController - 錄制完成預(yù)覽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 - 錄制完成預(yù)覽vc的代理回調(diào)協(xié)議 - 錄制完成預(yù)覽頁面

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

RPScreenRecorderDelegate - 錄屏功能回調(diào)協(xié)議 - 錄屏開始 / 結(jié)束 / 出錯接受通知

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畢竟是比較新的一個框架,所以它能實現(xiàn)的功能也比較簡單單一狸捕,方法和協(xié)議也比較少喷鸽。

2.實現(xiàn)思路

  • 主要難點是錄制完成后,保存到系統(tǒng)相冊府寒,怎么區(qū)分并且分類你錄制的視頻魁衙,比如錄屏1,2株搔,3
  • 如何播放錄制的視頻剖淀,注意系統(tǒng)相冊的video是不支持將url傳遞來達到播放的,所以必須找到你錄制的視頻纤房,把它拷貝到你的沙盒目錄下纵隔,順便進行格式轉(zhuǎn)換、壓縮等炮姨,以便以后讀取捌刮,分類。
簡單實現(xiàn)流程

3.具體實現(xiàn)過程 - 關(guān)鍵代碼

1. 錄制前的檢測: 設(shè)備是否是真機舒岸、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("錄屏功能只支持真機設(shè)備", showVC: showVC)
            return false
        }
        return true
    }
    
    // 系統(tǒng)版本是9.0
    func isSystemVersionSuport(showVC: UIViewController) -> Bool{
        if NSString(string: UIDevice.currentDevice().systemVersion).floatValue < 9.0 {
            showUnsuportAlert("錄屏功能要求系統(tǒng)版本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. 錄制功能類: 開始、結(jié)束
    // 開始
    func startCapture(){
        print("錄屏初始化...")
        let recorder = RPScreenRecorder.sharedRecorder()
        recorder.delegate = self
        // 關(guān)鍵方法
        recorder.startRecordingWithMicrophoneEnabled(false) { (error) in
            print("錄屏開始")
            self.delegate?.didStartRecord()
            if let error = error {
                self.delegate?.didStopWithError(error)
            }
        }
    }
    
    // 完成
    func stopCapture(){
        let recorder = RPScreenRecorder.sharedRecorder()
        // 關(guān)鍵方法
        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("顯示預(yù)覽頁面...")
            }
        }
    }
    
    // 自定義錄屏manager協(xié)議   
    protocol ScreenCaptureManagerDelegate: class{
        func didStartRecord() // 正在錄制
        func didFinishRecord(preview: UIViewController) // 完成錄制
        func didStopWithError(error: NSError) //發(fā)生錯誤停止錄制
        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{
    // 取消回調(diào)
    func previewControllerDidFinish(previewController: RPPreviewViewController) {
        print("previewControllerDidFinish")
        dispatch_async(dispatch_get_main_queue()) {
            self.delegate?.discardingRecord() // 取消保存
        }
    }
    
    // 保存-分享等的回調(diào)
    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. 你可以在將錄制的視頻轉(zhuǎn)存到沙盒目錄后蛾派,刪除系統(tǒng)相冊里的視頻俄认,以減小空間,但每次會提示用戶是否刪除洪乍,用戶體驗很不好眯杏,后來沒找到解決方法就直接不刪除了

    // 導(dǎo)出視頻
    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("導(dǎo)出視頻成功", args: nil)
                
                dispatch_async(dispatch_get_main_queue(), {
                    if success != nil{
                        success!()
                    }
                })
            })
        }
    }

    // 視頻操作 - model轉(zhuǎn)換
    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類
        // 保存預(yù)覽圖
        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("保存視頻預(yù)覽圖成功", args: [path])
            // 導(dǎo)出視頻
            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])
        }
    }

這里差不多了,可能沒有講太清楚巷波,希望用到的童鞋可以參考下萎津,共同學(xué)習(xí)探討。

  • 稍后整理下代碼抹镊,上傳到github

有空接著寫完吧 懶懶~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姜性,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子髓考,更是在濱河造成了極大的恐慌,老刑警劉巖弃酌,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氨菇,死亡現(xiàn)場離奇詭異儡炼,居然都是意外死亡,警方通過查閱死者的電腦和手機查蓉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門乌询,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人豌研,你說我怎么就攤上這事妹田。” “怎么了鹃共?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵鬼佣,是天一觀的道長。 經(jīng)常有香客問我霜浴,道長晶衷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任阴孟,我火速辦了婚禮晌纫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘永丝。我一直安慰自己锹漱,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布慕嚷。 她就那樣靜靜地躺著哥牍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闯冷。 梳的紋絲不亂的頭發(fā)上砂心,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音蛇耀,去河邊找鬼辩诞。 笑死,一個胖子當(dāng)著我的面吹牛纺涤,可吹牛的內(nèi)容都是我干的译暂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼撩炊,長吁一口氣:“原來是場噩夢啊……” “哼外永!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拧咳,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤伯顶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祭衩,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡灶体,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掐暮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝎抽。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖路克,靈堂內(nèi)的尸體忽然破棺而出樟结,到底是詐尸還是另有隱情,我是刑警寧澤精算,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布瓢宦,位于F島的核電站,受9級特大地震影響殖妇,放射性物質(zhì)發(fā)生泄漏刁笙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一谦趣、第九天 我趴在偏房一處隱蔽的房頂上張望疲吸。 院中可真熱鬧,春花似錦前鹅、人聲如沸摘悴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹂喻。三九已至,卻和暖如春捂寿,著一層夾襖步出監(jiān)牢的瞬間口四,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工秦陋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蔓彩,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓驳概,卻偏偏與公主長得像赤嚼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子顺又,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Swift版本點擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,319評論 7 249
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,732評論 25 707
  • 厚重的現(xiàn)實生活里俯萌, 在他人面前偽裝, 在你前面清高上枕, 無非是掩飾那份少的憐惜自尊绳瘟。 在青春所剩無幾的日子, 唯有表...
    過雨晴天閱讀 135評論 0 0
  • 晚上想好好睡一覺斤彼,可惜不夠遮光的窗簾擋不住窗外晃眼的夜景照明分瘦;中午想小憩一會兒,但明媚的陽光卻總是“擾人清夢”琉苇;尤...
    眼罩哥閱讀 482評論 0 1
  • 懷孩子的時候嘲玫,別人不到幾個月就會長滿妊娠紋。我直到生并扇,肚子除了長肉去团,都沒長任何東西,像女孩子時一樣白白凈凈的穷蛹。 因...
    許安佑閱讀 413評論 0 2