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 = falseprivate var sessionStarted = falsevar 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) inguard 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 inguard let self = self else { return }if let error = error {self.statusUpdate?("开始捕捉失败: \(error)")} else {self.isRecording = trueself.statusUpdate?("录制已开始。")}}}func stopRecording() {guard isRecording else {statusUpdate?("当前没有进行中的录制。")return}screenRecorder.stopCapture { [weak self] (error) inguard 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 AVFoundationclass ScreenRecorder {private var assetWriter: AVAssetWriter?private var videoInput: AVAssetWriterInput?private let screenRecorder = RPScreenRecorder.shared()private var isRecording = falseprivate var sessionStarted = falsevar 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) inguard 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 inguard let self = self else { return }if let error = error {self.statusUpdate?("开始捕捉失败: \(error)")} else {self.isRecording = trueself.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) inguard 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 = falsesessionStarted = falseassetWriter = nilvideoInput = nil}}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在做题中学习(56):二维前缀和模板

【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com) 理解题意: 要求的是(x1,y1) - (x2,y2)这段区间的和。 解法:二维前缀和 1. 和一维前缀和一样,需要有一个同等规模的dp数组,用来保存一段连续区域的和。 在二维dp中&#xff0…

客户案例:CACTER云网关为企业O365系统提供安全新护盾

一、客户背景 某智能驾驶企业是一家国际性的高科技创新型企业,其智能驾驶领域处于全球领先地位,专注于为广大客户提供个性化的智能驾驶解决方案,共建美好智能新时代。 使用产品:CACTER邮件安全云网关 二、痛点难点问题 根据…

最强特征点检测算法 DeDoDe v1/v2

论文地址v1:https://arxiv.org/pdf/2308.08479 论文地址v1:https://arxiv.org/pdf/2404.08928 代码地址:GitHub - Parskatt/DeDoDe: [3DV 2024 Oral] DeDoDe 🎶 Detect, Dont Describe --- Describe, Dont Detect, for Local Feature Matching 实测确实牛X! DeDoDeV1 关…

网安热议 | 中小企面临什么网络安全困扰?中小企网络安全是不是智商税?

近日,Coro 公司表示,许多中小型企业的 IT 人员被开发安全工作中多工具操作的复杂性和安全需求,压得“喘不过气”,导致其可能错过很多关键安全事件告警信息,从而将公司的网络安全置于危险之地。 研究机构采访了美国多行…

Python基本统计分析

常见的统计分析方法 import numpy as np import scipy.stats as spss import pandas as pd 鸢尾花数据集 https://github.com/mwaskom/seaborn-data df pd.read_csv("iris.csv",index_col"species") v1 df.loc["versicolor",:].petal_lengt…

比大小(打擂台)(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明比较大小函数max; int max(int a, int b);int main() {//初始化变量值&#xff1b;int i, n, m, a[10];//填充数组&#xff1b;printf("请输入10个数…

数据结构选择题(期末)

1.给定NN的二维数组A&#xff0c;则在不改变数组的前提下&#xff0c;查找最大元素的时间复杂度是&#xff08;A&#xff09;&#xff1a; A.O(N2) B.O(NlogN) C.O(N) D.O(N2logN) 两重循环即O(N2)的时间复杂度 2.与数据元素本身的形式、内容、相对位置、个数无关的是数据的…

MIT 6.5840(6.824) Lab1:MapReduce 设计实现

1 介绍 本次实验是实现一个简易版本的MapReduce&#xff0c;你需要实现一个工作程序&#xff08;worker process&#xff09;和一个调度程序&#xff08;coordinator process&#xff09;。工作程序用来调用Map和Reduce函数&#xff0c;并处理文件的读取和写入。调度程序用来协…

晶振在电子设备中的作用是什么?

在无源晶振电路中&#xff0c;并联电阻起着至关重要的作用。无源晶振本身不能自行产生振荡&#xff0c;因此需要借助外部电路来实现。并联在晶振两端的电阻&#xff0c;通常称为负载电阻&#xff0c;对电路的稳定性和振荡性能有着重要影响。 晶振电路的核心是皮尔斯振荡器&…

mysql根据字段值关联查不同表

mysql根据字段值关联查不同表&#xff1a; 实现&#xff1a; 使用left join 结合case when 判断直接取值&#xff1a; select mp.member_id ,mp.store_id, case mp.store_type when 1 then bs.store_namewhen 2 then sc.store_namewhen 3 then be.store_name end as store_na…

string类篇超超超详解,40余个成员函数详细解释(图文)!看完包会!!

本篇目标 constructoroperatorElements accessIteratorsCapacityModifiersString operationsmember contants其他函数 一、constructor(对象的创建) void StrTest1() {string s1;//直接构造cout << s1 << endl;//string里内置了流插入、流提取的函数重载&#xf…

Naive RAG 、Advanced RAG 和 Modular RAG 简介

简介&#xff1a; RAG&#xff08;Retrieval-Augmented Generation&#xff09;系统是一种结合了检索&#xff08;Retrieval&#xff09;和生成&#xff08;Generation&#xff09;的机制&#xff0c;用于提高大型语言模型&#xff08;LLMs&#xff09;在特定任务上的表现。随…

使用 Python 批量重命名文件

在日常工作或学习中,我们经常需要对大量文件进行重命名。手动操作一个一个改名既费时又费力,这时候可以使用 Python 脚本来自动完成这项任务。 本文将介绍一个使用 Python 的简单脚本,可以帮助您批量重命名指定目录下的所有文件。 脚本分析 import osdef batch_rename_fi…

深入解析RedisJSON:在Redis中直接处理JSON数据

码到三十五 &#xff1a; 个人主页 JSON已经成为现代应用程序之间数据传输的通用格式。然而&#xff0c;传统的关系型数据库在处理JSON数据时可能会遇到性能瓶颈。为了解决这一问题&#xff0c;Redis推出了RedisJSON模块&#xff0c;它允许开发者在Redis数据库中直接存储、查询…

产品推荐 | 基于 AMD Virtex UltraScale FPGA VCU1287 的特性描述套件

01 产品概述 VCU1287 功能描述套件可为您提供描述和评估 Virtex™ UltraScale™ XCVU095-FFVB2104E FPGA 上可用 32 GTH (16Gbps) 和 32 GTY (30Gbps) 收发器所需的一切功能。每个 GTH 与 GTY Quad 及其相关参考时钟均从 FPGA 路由至 SMA 及 Samtec BullsEye 连接器。 Bulls…

好题总结汇总

好题总结汇总 总结一些做完很有收获的题。 一、经典问题 DP的结合 1、题意&#xff1a; 给定 n n n 种颜色的球的数量 a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1​,a2​,...,an​&#xff0c;选出一些不同种类的球(也就是在n种球中选球的任意情况)&#xff0c;将球…

TCP的滑动窗口机制和流量控制

目录 滑动窗口 流量控制 拥塞控制 滑动窗口 TCP除了保证可靠性之外&#xff0c;也希望能够尽可能高效的完成数据传输。滑动窗口就是一种提高效率的机制。以下是不引入滑动窗口的数据传输过程&#xff1a; 可以看到&#xff0c;主机A这边每次收到一个ACK才发送下一个数据。这…

为什么cca门限和tx 功率有关系

Cca是用来决定信道是否繁忙&#xff0c;好像只和收有关。 但是为什么和tx有关。 设想一下这个网路布局。 如果某个STA在决定是否发送的时候&#xff0c;是否不能只看收到的干扰多大&#xff0c;还应该“冒险”一下&#xff0c;如果自己的功率足够&#xff0c;那么就可以扛住干…

Prometheus 服务发现 添加标签

在Prometheus中添加标签可以采用Relabel Config的方式&#xff0c;通过在配置文件中编写relabel_config模块来定义要给哪些目标添加标签&#xff0c;该模块可以实现筛选、替换、修剪、添加等不同的转换操作。 下面是一个添加标签的例子&#xff0c;该例子将添加标签“env: stag…

【经验03】spark处理离线数据速度缓慢遇到的坑

两张表关联 A表有15亿数据,B表有6亿数据 语句大概的意思如下: select a.* from A as a left join B as b on (a.id = b.id and a.id2 = b.id2); 运行了4个小时还没出结果。 增加了spark的参数,增加了RAM和并行设置。都不太好使。 最后发现是关联字段类型不一致导致。…