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

在 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
    }
    

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/600016.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

弹性云服务器是什么,为何如此受欢迎

云计算作为当下炙手可热的技术领域,已然成为现代企业不可或缺的核心能力。云服务器作为云计算的基石之一,在这个数字化时代发挥着至关重要的作用。而弹性云服务器,作为云服务器的一种演进形式,更是备受瞩目。 弹性云服务器&#…

使用 GPT-4-turbo+Streamlit+wiki+calculator构建Math Agents应用【Step by Step】

💖 Brief:大家好,我是Zeeland。Tags: 大模型创业、LangChain Top Contributor、算法工程师、Promptulate founder、Python开发者。📝 CSDN主页:Zeeland🔥📣 个人说明书:Zeeland&…

自动化运维管理工具 Ansible-----【inventory 主机清单和playbook剧本】

目录 一、inventory 主机清单 1.1inventory 中的变量 1.1.1主机变量 1.1.2组变量 1.1.3组嵌套 二、Ansible 的脚本 ------ playbook(剧本) 2.1 playbook介绍 2.2playbook格式 2.3playbooks 的组成 2.4playbook编写 2.5运行playbook 2.5.1ans…

学习笔记:【QC】Android Q qmi扩展nvReadItem/nvWriteItem

一、qmi初始化 流程图 初始化流程: 1、主入口: vendor/qcom/proprietary/qcril-hal/qcrild/qcrild/rild.c int main(int argc, char **argv) { const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **); rilInit RIL_Init; funcs rilInit…

【Linux】Linux线程

一、Linux线程的概念 1.什么是线程 1.一个进程的一个执行线路叫做线程,线程的一个进程内部的控制序列。 2.一个进程至少有一个执行线程 3.线程在进程内部,本质是在进程地址空间内运行 4.操作系统将进程虚拟地址空间的资源分配给每个执行流&#xff0…

基于51单片机的闭环反馈直流电机PWM控制电机转速测量( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机的闭环反馈直流电机PWM控制转速测量( proteus仿真程序设计报告原理图讲解视频) 仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0086 1. 主要功能: 基于51单片机的闭环…

js宏任务微任务输出解析

第一种情况 setTimeout(function () {console.log(setTimeout 1) //11 宏任务new Promise(function (resolve) {console.log(promise 1) //12 同步函数resolve()}).then(function () {console.log(promise then) //13 微任务})})async function async1() {console.log(async1 s…

语音识别--使用YAMNet识别环境音

⚠申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计3077字,阅读大概需要3分钟 🌈更多学习内容, 欢迎👏关注👀【文末】我的个人微信公众号&#xf…

2024.5.7

//头文件#ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QPushButton> #include <QLineEdit> #include <QLabel> #include <QTextToSpeech> #include <QString> #include <QtDebug> #include <QIcon> #i…

js浏览器请求,post请求中的参数形式和form-data提交数据时数据格式问题(2024-05-06)

浏览器几种常见的post请求方式 Content-Type 属性规定在发送到服务器之前应该如何对表单数据进行编码。 默认表单数据会编码为 "application/x-www-form-urlencoded" post请求的参数一般放在Body里。 Content-Type&#xff08;内容类型&#xff09;&#xff0c;一般…

截图工具Snipaste:不仅仅是截图,更是效率的提升

在数字时代&#xff0c;截图工具已成为我们日常工作和生活中不可或缺的一部分。无论是用于工作汇报、学习笔记&#xff0c;还是日常沟通&#xff0c;一款好用的截图工具都能大大提升我们的效率。今天&#xff0c;我要向大家推荐一款功能强大且易于使用的截图软件——Snipaste。…

CRC校验原理及步骤

文章目录 CRC定义&#xff1a;CRC校验原理&#xff1a;CRC校验步骤&#xff1a; CRC定义&#xff1a; CRC即循环冗余校验码&#xff0c;是数据通信领域中最常用的一种查错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查&#xff08;CRC&#…

JUC-synchronized练习-交替打印ABC

今天来练习一下synchronized 简单来利用synchronized实现一个字符串的交替打印 主要的实现设置一个全局的变量state&#xff0c;线程执行通过不断累加state&#xff0c;根据state对三取余的结果来判断该线程是否继续执行还是进入等待。并通过synchronized锁住一个共享变量loc…

设计模式之模板模式TemplatePattern(五)

一、模板模式介绍 模板方法模式&#xff08;Template Method Pattern&#xff09;&#xff0c;又叫模板模式&#xff08;Template Pattern&#xff09;&#xff0c; 在一个抽象类公开定义了执行它的方法的模板。它的子类可以更需要重写方法实现&#xff0c;但可以成为典型类中…

学习R语言第五天

文章目录 语法学习创建数据的方式绘制图形的方式图形添加颜色如何操作数据的方式数据进行验算的判断加付值的方式修改变量名称的方式判断是否存在缺失值在计算的方式忽略缺失值通过函数的方式忽略缺失值日期处理的方式字符串转化成日期的方式格式化数据框中数据返回当前的日期的…

保研面试408复习 1——操作系统、计网、计组

文章目录 1、操作系统一、操作系统的特点和功能二、中断和系统调用的区别 2、计算机组成原理一、冯诺依曼的三个要点二、MIPS&#xff08;每秒百万条指令&#xff09;三、CPU执行时间和CPI 3、计算机网络一、各个层常用协议二、网络协议实验——数据链路层a.网络速率表示b.数据…

《十八》QThread多线程组件

本章将重点介绍如何运用QThread组件实现多线程功能。 多线程技术在程序开发中尤为常用&#xff0c;Qt框架中提供了QThread库来实现多线程功能。当你需要使用QThread时&#xff0c;需包含QThread模块&#xff0c;以下是QThread类的一些主要成员函数和槽函数。 成员函数/槽函数 …

Linux内核之获取文件系统超级块:sget用法实例(六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

EMAIL-PHP功能齐全的发送邮件类可以发送HTML和附件

EMAIL-PHP功能齐全的发送邮件类可以发送HTML和附件 <?php class Email { //---设置全局变量 var $mailTo ""; // 收件人 var $mailCC ""; // 抄送 var $mailBCC ""; // 秘密抄送 var $mailFrom ""; // 发件人 var $mailSubje…

如何查看慢查询

4.2 如何查看慢查询 知道了以上内容之后&#xff0c;那么咱们如何去查看慢查询日志列表呢&#xff1a; slowlog len&#xff1a;查询慢查询日志长度slowlog get [n]&#xff1a;读取n条慢查询日志slowlog reset&#xff1a;清空慢查询列表 5、服务器端优化-命令及安全配置 安…
最新文章