iOS Swift 中使用 ReplayKit 进行屏幕录制并获取文件路径

发布于:2024-05-08 ⋅ 阅读:(35) ⋅ 点赞:(0)

在 iOS 开发中,屏幕录制是一项强大的功能,尤其在应用演示、教育教程或游戏录屏等场景中非常有用。Apple 提供了一个名为 ReplayKit 的框架,允许开发者直接在应用中添加屏幕录制功能。本文将详细介绍如何使用 Swift 和 ReplayKit 结合 AVFoundation 来实现屏幕录制功能,并获取录制文件的路径。

1. 引入必要的框架

首先,需要在项目中引入 ReplayKit 和 AVFoundation 框架。这些框架提供了录制屏幕和处理视频文件所需的 API:

import Foundation
import ReplayKit
import AVFoundation

2. 创建屏幕录制器类

创建一个名为 ScreenRecorder 的类来封装屏幕录制的逻辑。这个类将负责设置视频文件写入器、开始和停止录制以及处理录制过程中的数据。

class ScreenRecorder {
    private var assetWriter: AVAssetWriter?
    private var videoInput: AVAssetWriterInput?
    private let screenRecorder = RPScreenRecorder.shared()
    private var isRecording = false
    private var sessionStarted = false
    
    var statusUpdate: ((String) -> Void)?
    var savePathUpdate: ((URL) -> Void)?
}

3. 设置视频文件写入器

创建一个方法 setupWriter 来配置视频文件的写入。此方法将初始化 AVAssetWriter 并为视频设置必要的参数,如编码格式、分辨率等。

private func setupWriter(fileName: String) -> Bool {
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let fileURL = documentsURL.appendingPathComponent(fileName)
    do {
        assetWriter = try AVAssetWriter(outputURL: fileURL, fileType: .mp4)
        let outputSettings: [String: Any] = [
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoWidthKey: UIScreen.main.bounds.width,
            AVVideoHeightKey: UIScreen.main.bounds.height
        ]
        videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: outputSettings)
        if assetWriter!.canAdd(videoInput!) {
            assetWriter!.add(videoInput!)
        }
    } catch {
        statusUpdate?("设置视频写入器失败: \(error)")
        return false
    }
    return true
}

4. 开始和停止录制

实现 startRecordingstopRecording 方法来控制录制的开始和结束。这些方法将处理录制的启动、数据捕获、文件写入和资源释放。

func startRecording(withFileName fileName: String) {
    

guard !isRecording else {

            statusUpdate?("录制已经在进行中。")

            return

        }

        guard RPScreenRecorder.shared().isAvailable else {

            statusUpdate?("屏幕录制不可用。")

            return

        }

        if !setupWriter(fileName: fileName) {

            return

        }

        

        assetWriter?.startWriting()

        screenRecorder.startCapture { [weak self] (sampleBuffer, bufferType, error) in

            guard let self = self else { return }

            if let error = error {

                

                self.statusUpdate?("捕捉过程中发生错误: \(error)")

                

                return

            }

            if bufferType == .video {

                DispatchQueue.main.async {

                    self.handleSampleBuffer(sampleBuffer)

                }

            }

        } completionHandler: { [weak self] error in

            guard let self = self else { return }

            if let error = error {

                

                self.statusUpdate?("开始捕捉失败: \(error)")

                

            } else {

                

                self.isRecording = true

                self.statusUpdate?("录制已开始。")

                

            }

        }

}

func stopRecording() {

        guard isRecording else {

            statusUpdate?("当前没有进行中的录制。")

            return

        }

        

        screenRecorder.stopCapture { [weak self] (error) in

            guard let self = self else { return }

            if let error = error {

                

                self.statusUpdate?("停止捕捉失败: \(error)")

                

                return

            }

            

            self.videoInput?.markAsFinished()

            self.assetWriter?.finishWriting {

                

                if self.assetWriter?.status == .completed {

                    self.statusUpdate?("录制已停止。")

                    if let url = self.assetWriter?.outputURL {

                        self.savePathUpdate?(url)

                    } else {

                        self.statusUpdate?("无法获取录制文件的URL。")

                    }

                } else {

                    print("录屏文件保存失败: \(self.assetWriter?.error?.localizedDescription ?? "未知错误")")

                }

                

                

                

                self.cleanup()

            }

        }

    }


5. 处理视频样本

在录制过程中,需要实时处理从 ReplayKit 捕获的视频样本。使用 handleSampleBuffer 方法来追加样本数据到视频文件中。

private func handleSampleBuffer(_ sampleBuffer: CMSampleBuffer) {

        guard CMSampleBufferGetPresentationTimeStamp(sampleBuffer).isValid,

              let videoInput = videoInput, videoInput.isReadyForMoreMediaData else {

            return

        }

        

        if !sessionStarted {

            assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))

            sessionStarted = true

        }

        

        videoInput.append(sampleBuffer)

    }

通过上述步骤,您可以在 iOS 应用中实现屏幕录制功能,并在录制完成后获取视频文件的路径。

以下是完整的代码

import Foundation
import ReplayKit
import AVFoundation

class ScreenRecorder {
    private var assetWriter: AVAssetWriter?
    private var videoInput: AVAssetWriterInput?
    private let screenRecorder = RPScreenRecorder.shared()
    private var isRecording = false
    private var sessionStarted = false
    
    var statusUpdate: ((String) -> Void)?
    var savePathUpdate: ((URL) -> Void)?
    
    // 设置视频文件写入器
    private func setupWriter(fileName: String) -> Bool {
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let fileURL = documentsURL.appendingPathComponent(fileName)
        do {
            assetWriter = try AVAssetWriter(outputURL: fileURL, fileType: .mp4)
            let outputSettings: [String: Any] = [
                AVVideoCodecKey: AVVideoCodecType.h264,
                AVVideoWidthKey: UIScreen.main.bounds.width,
                AVVideoHeightKey: UIScreen.main.bounds.height
            ]
            videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: outputSettings)
            if assetWriter!.canAdd(videoInput!) {
                assetWriter!.add(videoInput!)
            }
        } catch {
            statusUpdate?("设置视频写入器失败: \(error)")
            return false
        }
        return true
    }
    
    // 开始录制
    func startRecording(withFileName fileName: String) {
        guard !isRecording else {
            statusUpdate?("录制已经在进行中。")
            return
        }
        guard RPScreenRecorder.shared().isAvailable else {
            statusUpdate?("屏幕录制不可用。")
            return
        }
        if !setupWriter(fileName: fileName) {
            return
        }
        
        assetWriter?.startWriting()
        screenRecorder.startCapture { [weak self] (sampleBuffer, bufferType, error) in
            guard let self = self else { return }
            if let error = error {
                
                self.statusUpdate?("捕捉过程中发生错误: \(error)")
                
                return
            }
            if bufferType == .video {
                DispatchQueue.main.async {
                    self.handleSampleBuffer(sampleBuffer)
                }
            }
        } completionHandler: { [weak self] error in
            guard let self = self else { return }
            if let error = error {
                
                self.statusUpdate?("开始捕捉失败: \(error)")
                
            } else {
                
                self.isRecording = true
                self.statusUpdate?("录制已开始。")
                
            }
        }
    }
    
    // 处理视频样本
    private func handleSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
        guard CMSampleBufferGetPresentationTimeStamp(sampleBuffer).isValid,
              let videoInput = videoInput, videoInput.isReadyForMoreMediaData else {
            return
        }
        
        if !sessionStarted {
            assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
            sessionStarted = true
        }
        
        videoInput.append(sampleBuffer)
    }
    
    // 停止录制
    func stopRecording() {
        guard isRecording else {
            statusUpdate?("当前没有进行中的录制。")
            return
        }
        
        screenRecorder.stopCapture { [weak self] (error) in
            guard let self = self else { return }
            if let error = error {
                
                self.statusUpdate?("停止捕捉失败: \(error)")
                
                return
            }
            
            self.videoInput?.markAsFinished()
            self.assetWriter?.finishWriting {
                
                if self.assetWriter?.status == .completed {
                    self.statusUpdate?("录制已停止。")
                    if let url = self.assetWriter?.outputURL {
                        self.savePathUpdate?(url)
                    } else {
                        self.statusUpdate?("无法获取录制文件的URL。")
                    }
                } else {
                    print("录屏文件保存失败: \(self.assetWriter?.error?.localizedDescription ?? "未知错误")")
                }
                
                
                
                self.cleanup()
            }
        }
    }
    
    // 清理资源
    private func cleanup() {
        isRecording = false
        sessionStarted = false
        assetWriter = nil
        videoInput = nil
    }
    

}


网站公告

今日签到

点亮在社区的每一天
去签到