原创内容,转载引用请注明来源
最近公司项目需要使用录屏并保存到相册,简单整理了一下
需求是录屏并在录屏结束的时候把录制的视频自动保存系统相册
我们的App支持的最低系统为iOS 10 ,采用了iOS 10 + iOS11Later 两种方式进行录制。对此封装了一个ReplayManager。
废话不多说(其实是懒得整理了),下面直接上代码:
//
// ZTReplayManager.swift
// xi_men_xi_xue
// ┌─┐ ┌─┐ ┌─┐
// │ │ │ │ │ │
// │ └────┘ │ │ │
// │ ┌────┐ │ │ │
// │ │ │ │ │ └─────┐
// └─┘ └─┘ └───────┘
// Created by RHL on 2021/9/3.
// Copyright © 2021 Mac. All rights reserved.
//
import Foundation
import ReplayKit
class ZTReplayManager {
static let share = ZTReplayManager()
let recorder = RPScreenRecorder.shared() // 屏幕录制
private var videoPath:String? = {
let outputURL = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
let random = ZTTool.getCurrentTimeInterval()
let path = outputURL?.appending("/\(random)" + ".mp4")
let path2 = "file://\(path ?? "")"
return path2
}()
private var assetWriterInput:AVAssetWriterInput = {
let compressionProperties = [AVVideoAverageBitRateKey:NSNumber(value: Double(2000.0 * 1000.0))]
let videoSettings:[String:Any] = [AVVideoCompressionPropertiesKey : compressionProperties,
AVVideoCodecKey : AVVideoCodecH264,
AVVideoWidthKey : NSNumber(value: Double(ZT_ScreenWidth)),
AVVideoHeightKey : NSNumber(value: Double(ZT_ScreenHeight))]
let assetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
return assetWriterInput
}()
private lazy var assetWriter:AVAssetWriter? = {
guard let videoPath = videoPath else { return nil }
guard let outputURL = URL(string: videoPath) else { return nil }
guard var assetWriter = try? AVAssetWriter(url: outputURL, fileType: AVFileType.mp4) else{ return nil }
if assetWriter.canAdd(assetWriterInput){ assetWriter.add(assetWriterInput) }
return assetWriter
}()
}
//MARK: - 屏幕录制相关
extension ZTReplayManager {
//开始录制
func SR_Start(callBack: (@escaping () -> ())) {
guard recorder.isAvailable && !recorder.isRecording else { return }
if #available(iOS 11.0, *) { ios11Later_SR_Start { callBack() } } else { ios10_SR_Start { callBack() } }
}
func SR_End(callBack: (() -> ())) {
guard recorder.isRecording else {return}
if #available(iOS 11.0, *) { ios11Later_SR_End { callBack() } } else { ios10_SR_End { callBack() } }
}
}
//MARK: - available(iOS 10.0, *)
extension ZTReplayManager{
private func ios10_SR_Start(callBack: (@escaping () -> ())) {
recorder.startRecording { [weak self] err in
guard let strongSelf = self else { return }
if err == nil { callBack() }else if err.unsafelyUnwrapped.localizedDescription == "用户拒绝了应用程序录制" {
ZTShowToast(err.unsafelyUnwrapped.localizedDescription)
strongSelf.SR_End {print("用户拒绝了应用程序录制")}
}
}
}
private func ios10_SR_End(callBack: (() -> ())) {
recorder.stopRecording { previewVC, err in
DispatchQueue.main.async {
if ((previewVC?.responds(to: #selector(getter: previewVC?.movieURL))) != nil) {
let videoUrl = previewVC?.movieURL
guard let videoUrl = videoUrl else { ZTShowToast("获取视频失败"); return }
let compatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(videoUrl.relativePath)
if compatible { ZTTool.saveVideoToAlbum("\(videoUrl)") }
}
}
}
callBack()
}
//获取临时路径
private func tempURL() -> URL? {
let directory = NSTemporaryDirectory() as NSString
if directory != "" {
let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
return URL(fileURLWithPath: path)
}
return nil
}
}
//MARK: - available(iOS 11.0, *)
extension ZTReplayManager{
private func ios11Later_SR_Start(callBack: (@escaping () -> ())) {
if #available(iOS 11.0, *) {
recorder.startCapture { [weak self] sampleBuffer, bufferType, error in
guard let self = self else { return }
guard self.assetWriter?.status != .failed else { ZTLog("assetWriter.error = \(String(describing: self.assetWriter?.error))"); return }
if self.assetWriter?.status == .unknown && bufferType == .video {
self.assetWriter?.startWriting()
let pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
let videoPts = CMTimeGetSeconds(pts) * 1000
if videoPts < 0 { ZTLog("无用帧"); return }
self.assetWriter?.startSession(atSourceTime: pts)
}
if bufferType == .video {
if self.assetWriterInput.isReadyForMoreMediaData {
self.assetWriterInput.append(sampleBuffer)
}else{ ZTLog("Not ready for video") }
}
} completionHandler: { error in
if (error != nil) { ZTLog("开始录制失败\(String(describing: error))") }else{ ZTLog("开始录制");callBack() }
}
}
}
private func ios11Later_SR_End(callBack: (() -> ())) {
if #available(iOS 11.0, *) {
recorder.stopCapture { [weak self] error in
guard let self = self else { return }
if error != nil { ZTLog("停止录制失败:error = \(String(describing: error))"); return }
guard let videoPath = self.videoPath else { return}
self.assetWriter?.finishWriting(completionHandler: {
let realPath = videoPath.substring(fromIndex: 7)
ZTTool.saveVideoToAlbum(realPath)
do{
self.assetWriter = nil
self.videoPath = nil
}
})
}
}
}
}
网友评论