前段時間公司項目有個要求,讓找一個第三方的錄屏框架,實現(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中錄制視頻和音頻约炎,可以通過社交媒體分享你錄制的作品。
-
但它目前還有如下一些不足:
- 不支持AVPlayer播放的視頻錄制
- 不支持模擬器
- 無法自定義RPPreviewViewController預(yù)覽視圖
- 無法修改錄制視頻保存路徑
- 只支持 iOS9.0+ 版本
-
幾張效果圖
-
錄屏權(quán)限提醒
-
開始錄制
-
錄制完成,預(yù)覽 - 這個vc按鈕樣式什么的沒法定制很不好圾浅,除非找到私有api
-
錄制的視頻保存在系統(tǒng)相冊掠手,然后讀取
-
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)換、壓縮等炮姨,以便以后讀取捌刮,分類。
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
有空接著寫完吧 懶懶~