【HarmonyOS】鸿蒙应用实现屏幕录制详解和源码

【HarmonyOS】鸿蒙应用实现屏幕录制详解和源码

一、前言

官方文档关于屏幕录制的API和示例介绍获取简单和突兀。使用起来会让上手程度变高。所以特意开篇文章,讲解屏幕录制的使用。官方文档参见:使用AVScreenCaptureRecorder录屏写文件(ArkTS)

二、方案思路

鸿蒙应用关于录制屏幕,官方提供了AVScreenCaptureRecorder进行屏幕录制的调用。分为以下几个步骤:
1.创建该对象

import media from '@ohos.multimedia.media';private avScreenCaptureRecorder: media.AVScreenCaptureRecorder | undefined = undefined;this.avScreenCaptureRecorder = await media.createAVScreenCaptureRecorder();

2.进行属性配置初始化
这里尤其要注意,config配置属性对象的作用范围,在官方示例中,一般不喜欢创建成局部对象,而是全局对象。但是fd又是异步获取,就容器造成fd拿到后,并没有赋值给config中,导致init函数初始化一直报错401参数错误。

如果像官方示例列为全局对象,那fd的file对象也需要创建为全局对象,看起来就很恶心。所以我这里改成局部对象。

【官方DEMO关于fd的出处并没有写全,春秋笔法过多。所以我经常吐槽说官方文档基本上属于你会了才能看懂了。。】

    let context = getContext(this) as common.UIAbilityContext; // 获取设备A的UIAbilityContext信息// 沙箱路径let pathDir: string = context.filesDir; // /data/storage/el2/base/haps/entry/files// 视频文件名字和路径let filesUri: string = pathDir + '/Screen_' + new Date().getTime() + '.mp4';// 缓存Uri,用于保存媒体库使用this.targetFileUri = filesUri;// 创建文件,赋予写权限let curFile = fs.openSync(filesUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);let avCaptureConfig: media.AVScreenCaptureRecordConfig = {// 文件需要先有调用者创建,赋予写权限,将文件fd传给此参数fd: curFile.fd,// 除了fd,其他参数都是可选,可以不设置。默认宽高就是手机时机宽高。// frameWidth: 768,// frameHeight: 1280,}await this.avScreenCaptureRecorder?.init(avCaptureConfig);

此时录屏文件是保存在我们创建的沙箱路径中的。所以并不需要官方文档中提到的读写权限。

3.然后调用开始录屏或者结束录屏。

 	await this.avScreenCaptureRecorder.startRecording()await this.avScreenCaptureRecorder.stopRecording()

4.选配-录音权限的配置和申请
如果没有配置和申请录音权限。默认录屏是没有麦克风的声音。反之,录屏时你说话,就能录入到视频中。
在这里插入图片描述

  /*** 申请麦克风权限*/private questMicPermissions(){const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();try {atManager.requestPermissionsFromUser(getContext(), ["ohos.permission.MICROPHONE"]).then((data) => {if (data.authResults[0] === 0) {} else {console.log(this.TAG, "user rejected")}}).catch((err: BusinessError) => {console.log(this.TAG, "BusinessError err: " + JSON.stringify(err))})} catch (err) {console.log(this.TAG, "catch err: " + JSON.stringify(err))}}

5.选配-将沙箱路径下的录屏保存到相册中
保存到媒体库中,有很多种方式。我此处举例使用的是saveButton的形式进行保存函数的调用。

直接调用以下保存函数是不会生效。在鸿蒙中,一定需要用户知情同意,才能将沙箱的资源保存到媒体库中。

  /*** 保存视频到媒体库*/private saveVideo() {let titleStr = 'Screen_'+ new Date().getTime()let context = getContext(this);let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);let photoType: photoAccessHelper.PhotoType = photoAccessHelper.PhotoType.VIDEO;let extension:string = 'mp4';let options: photoAccessHelper.CreateOptions = {title:titleStr}phAccessHelper.createAsset(photoType, extension, options).then(async (uriDes:string)=>{try {let file_uri =  fs.openSync(this.targetFileUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);let file =  fs.openSync(uriDes, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);fs.copyFileSync(file_uri.fd, file.fd);fs.closeSync(file.fd);fs.closeSync(file_uri.fd);promptAction.showToast({message: '已保存至相册!',duration: 3000});}catch (err) {console.error("error is "+ JSON.stringify(err))}}).catch((err:Error)=>{console.error("error is "+ JSON.stringify(err))});}

SaveButton,隐私窗口的豁免和录制状态的回调监听,参见源码示例。

注意:
实际开发中因为鸿蒙的后台特性,当录屏时应用切到后台大于三秒,应用进程就会被挂起。所以需要设置后台任务的长时任务。保证录屏的正常。(后面我会针对长时任务以录屏来举例,此处先不处理。)

三、源码示例:

      // 申请麦克风{"name": "ohos.permission.MICROPHONE","reason": "$string:reason","usedScene": {"abilities": ["EntryAbility"],"when": "always"}},

SCRecordTestPage.ets


import { media } from '@kit.MediaKit'
import { BusinessError } from '@kit.BasicServicesKit'
import fs from '@ohos.file.fs';
import { abilityAccessCtrl, common, PermissionRequestResult, Permissions } from '@kit.AbilityKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { promptAction } from '@kit.ArkUI';
import { fileUri } from '@kit.CoreFileKit';

struct SCRecordTestPage {private TAG: string = "SCRecordTestPage";private avScreenCaptureRecorder: media.AVScreenCaptureRecorder | undefined = undefined;private targetFileUri: string = "";private saveButtonOptions: SaveButtonOptions = {icon: SaveIconStyle.FULL_FILLED,text: SaveDescription.SAVE_FILE,buttonType: ButtonType.Capsule} // 设置安全控件按钮属性async aboutToAppear() {// 初始化屏幕录制渲染对象await this.createAVScreenCapture();}async createAVScreenCapture() {this.avScreenCaptureRecorder = await media.createAVScreenCaptureRecorder();this.avScreenCaptureRecorder.on('stateChange', async (infoType: media.AVScreenCaptureStateCode) => {switch (infoType) {case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_STARTED:console.info("录屏成功开始后会收到的回调");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_CANCELED:this.avScreenCaptureRecorder?.release();this.avScreenCaptureRecorder = undefined;console.info("不允许使用录屏功能");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_STOPPED_BY_USER:this.avScreenCaptureRecorder?.release();this.avScreenCaptureRecorder = undefined;console.info("通过录屏胶囊结束录屏,底层录制会停止");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_INTERRUPTED_BY_OTHER:console.info("录屏因其他中断而停止,底层录制会停止");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_STOPPED_BY_CALL:console.info("录屏过程因通话中断,底层录制会停止");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_MIC_UNAVAILABLE:console.info("录屏麦克风不可用");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_MIC_MUTED_BY_USER:console.info("录屏麦克风被用户静音");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_MIC_UNMUTED_BY_USER:console.info("录屏麦克风被用户取消静音");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_ENTER_PRIVATE_SCENE:// 目前可以从系统直接注册监听到进入隐私场景console.info("录屏进入隐私场景");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_EXIT_PRIVATE_SCENE:console.info("录屏退出隐私场景");break;case media.AVScreenCaptureStateCode.SCREENCAPTURE_STATE_STOPPED_BY_USER_SWITCHES:console.info("用户账号切换,底层录制会停止");break;default:break;}})this.avScreenCaptureRecorder.on('error', (err) => {console.info("处理异常情况");})let context = getContext(this) as common.UIAbilityContext; // 获取设备A的UIAbilityContext信息// 沙箱路径let pathDir: string = context.filesDir; // /data/storage/el2/base/haps/entry/files// 视频文件名字和路径let filesUri: string = pathDir + '/Screen_' + new Date().getTime() + '.mp4';// 缓存Uri,用于保存媒体库使用this.targetFileUri = filesUri;// 创建文件,赋予写权限let curFile = fs.openSync(filesUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);let avCaptureConfig: media.AVScreenCaptureRecordConfig = {// 文件需要先有调用者创建,赋予写权限,将文件fd传给此参数fd: curFile.fd,// 除了fd,其他参数都是可选,可以不设置。默认宽高就是手机时机宽高。// frameWidth: 768,// frameHeight: 1280,}await this.avScreenCaptureRecorder?.init(avCaptureConfig);}build() {Column({ space: 50 }) {Button('选配-开启麦克风').onClick(() => {this.questMicPermissions();}).height(60).width('100%')Button('开始录屏').onClick(() => {this.startRecord()}).height(60).width('100%')Button('结束录屏').onClick(() => {this.stopRecord()}).height(60).width('100%')SaveButton(this.saveButtonOptions) // 创建安全控件按钮.onClick(async (event, result: SaveButtonOnClickResult) => {if (result == SaveButtonOnClickResult.SUCCESS) {try {this.saveVideo();} catch (err) {console.error(`create asset failed with error: ${err.code}, ${err.message}`);}} else {console.error('SaveButtonOnClickResult create asset failed');}})}.justifyContent(FlexAlign.Center).width('100%').height('100%').padding({ left: 30, right: 30})}/*** 申请麦克风权限*/private questMicPermissions(){const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();try {atManager.requestPermissionsFromUser(getContext(), ["ohos.permission.MICROPHONE"]).then((data) => {if (data.authResults[0] === 0) {} else {console.log(this.TAG, "user rejected")}}).catch((err: BusinessError) => {console.log(this.TAG, "BusinessError err: " + JSON.stringify(err))})} catch (err) {console.log(this.TAG, "catch err: " + JSON.stringify(err))}}/*** 开启录制*/private startRecord() {// 创建豁免隐私窗口,这里填写的是子窗口id和主窗口id// let windowIDs = [57, 86];// await this.avScreenCaptureRecorder?.skipPrivacyMode(windowIDs);this.avScreenCaptureRecorder?.startRecording().then(() => {console.info('Succeeded in starting avScreenCaptureRecorder');}).catch((err: BusinessError) => {console.info('Failed to start avScreenCaptureRecorder, error: ' + err.message);})}/*** 暂停录制*/private stopRecord() {this.avScreenCaptureRecorder?.stopRecording().then(() => {console.info('Succeeded in stopping avScreenCaptureRecorder');}).catch((err: BusinessError) => {console.info('Failed to stop avScreenCaptureRecorder, error: ' + err.message);})}/*** 保存视频到媒体库*/private saveVideo() {let titleStr = 'Screen_'+ new Date().getTime()let context = getContext(this);let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);let photoType: photoAccessHelper.PhotoType = photoAccessHelper.PhotoType.VIDEO;let extension:string = 'mp4';let options: photoAccessHelper.CreateOptions = {title:titleStr}phAccessHelper.createAsset(photoType, extension, options).then(async (uriDes:string)=>{try {let file_uri =  fs.openSync(this.targetFileUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);let file =  fs.openSync(uriDes, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);fs.copyFileSync(file_uri.fd, file.fd);fs.closeSync(file.fd);fs.closeSync(file_uri.fd);promptAction.showToast({message: '已保存至相册!',duration: 3000});}catch (err) {console.error("error is "+ JSON.stringify(err))}}).catch((err:Error)=>{console.error("error is "+ JSON.stringify(err))});}
}

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

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

相关文章

解决在VS2019/2022中编译c++项目报错fatal error C1189: #error : “No Target Architecture“

解决在VS2019/2022中编译c项目报错fatal error C1189: #error : “No Target Architecture” 报错原因 在winnt.h中,不言而喻,一目了然: 代码节选: #if defined(_AMD64_) || defined(_X86_) #define PROBE_ALIGNMENT( _s ) TY…

统计素数并求和(PTA)C语言

本题要求统计给定整数M和N区间内素数的个数并对它们求和。 输入格式: 输入在一行中给出两个正整数M和N(1≤M≤N≤500)。 输出格式: 在一行中顺序输出M和N区间内素数的个数以及它们的和,数字间以空格分隔。 输入样例: 10 31输出样例: 7…

Python教程丨Python环境搭建 (含IDE安装)——保姆级教程!

工欲善其事,必先利其器。 学习Python的第一步不要再加收藏夹了!提高执行力,先给自己装好Python。 1. Python 下载 1.1. 下载安装包 既然要下载Python,我们直接进入python官网下载即可 Python 官网:Welcome to Pyt…

Java中时间类的转换是怎么进行的

String转TimeStamp DateFormat format new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Timestamp startTime new Timestamp(format.parse(startDate).getTime()); TimeStamp转String String nowDate new SimpleDateFormat("yyyyMMdd").format(nowTimeS…

如何限制大量请求并发

前言: 1、主流浏览器在 HTTP/1.1 下对同一域名的最大并发请求数通常是 6~8 个。超过限制的请求会进入队列,等待空闲的连接。 2、可以利用Promise模拟任务队列,控制并发请求数量,以避免对服务器造成过大的压力。(先进先…

LeetCode:2274. 不含特殊楼层的最大连续楼层数(排序 Java)

目录 2274. 不含特殊楼层的最大连续楼层数 题目描述: 实现代与解析: 排序 原理思路: 2274. 不含特殊楼层的最大连续楼层数 题目描述: Alice 管理着一家公司,并租用大楼的部分楼层作为办公空间。Alice 决定将一些…

实现AVL树

目录 AVL树概念 AVL树结构 AVL树插入 LL型 - 右单旋 RR型 - 左单旋 LR型 - 左右双旋 RL型 - 右左双旋 插入代码实现 AVL树测试 附AVL树实现完整代码 AVL树概念 前面的博客介绍了搜索二叉树,二叉搜索树-CSDN博客 在某些特定的情况下,⼆叉搜索树…

极客说|微软 Phi 系列小模型和多模态小模型

作者:胡平 - 微软云人工智能高级专家 「极客说」 是一档专注 AI 时代开发者分享的专栏,我们邀请来自微软以及技术社区专家,带来最前沿的技术干货与实践经验。在这里,您将看到深度教程、最佳实践和创新解决方案。关注「极客说」&am…

React+redux项目搭建流程

1.创建项目 create-react-app my-project --template typescript // 创建项目并使用typescript2.去除掉没用的文件夹,只保留部分有用的文件 3.项目配置: 配置项目的icon 配置项目的标题 配置项目的别名等(craco.config.ts&…

HTML+CSS+JS制作高仿小米官网网站(内附源码,含6个页面)

一、作品介绍 HTMLCSSJS制作一个高仿小米官网网站,包含首页、商品详情页、确认订单页、订单支付页、收货地址管理页、新增收获地址页等6个静态页面。其中每个页面都包含一个导航栏、一个主要区域和一个底部区域。 二、页面结构 1. 顶部导航栏 包含Logo、主导航菜…

obs directx11

创建逻辑 obs 在windows 下分为Opengl 和 directx 两种渲染模式,默认使用的是directx ,兼容性更好; 代码路径: E:\opensrc\obs_studio_src\obs-studio\UI\obs-app.cpp 选择渲染模式 const char* OBSApp::GetRenderModule() con…

QT实现 端口扫描暂停和继续功能 3

上篇QT给端口扫描工程增加线程2-CSDN博客 为按钮pushButton_Stop添加clicked事件,功能为暂停扫描,并在暂停后显示继续按钮,点击继续按钮之后继续扫描 1.更新UI 添加继续按钮 点击转到槽则会自动声明 2. 更新 MainWindow.h 需要新增的部分…

微软 2024 最新技术全景洞察

亲爱的小伙伴们😘,在求知的漫漫旅途中,若你对深度学习的奥秘、Java 与 Python 的奇妙世界,亦或是读研论文的撰写攻略有所探寻🧐,那不妨给我一个小小的关注吧🥰。我会精心筹备,在未来…

nginx-限流(请求/并发量)

一. 简述: 在做日常的web运维工作中,难免会遇到服务器流量异常,负载过大等情况。恶意攻击访问/爬虫等非正常性请求,会带来带宽的浪费,服务器压力增大,影响业务质量。 二. 限流方案: 对于这种情…

分布式ID生成-雪花算法实现无状态

雪花算法这里不再赘述,其缺点是有状态(多副本隔离时,依赖手动配置workId和datacenterId),代码如下: /*** 雪花算法ID生成器*/ public class SnowflakeIdWorker {/*** 开始时间截 (2017-01-01)*/private st…

(六)vForm 动态表单(数据量大,下拉选卡顿问题)

系列文章目录 (一)vForm 动态表单设计器之使用 (二)vForm 动态表单设计器之下拉、选择 (三)vForm 动态表单解决下拉框无数据显示id问题 (四)vForm 动态表单自定义组件、属性 (五)vForm 动态表单文件上传、下载 文章目录 目录 前言 一、组件改造 1.添加分页所需参…

Edge SCDN高效防护与智能加速

当今数字化时代,网络安全和内容分发效率已成为企业业务发展的关键因素。酷盾安全推出了Edge SCDN解决方案,为企业提供全方位的安全防护和高效的内容分发服务。 一、卓越的安全防护能力 1.DDoS攻击的精准防御:Edge SCDN具备强大的DDoS攻击检测…

【银河麒麟高级服务器操作系统】服务器异常重启故障分析及处理建议

了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://document.kylinos.cn 服务器环境以及配置 系统环境 物理机/虚拟机/云…

Go语言中的 os.Stat() 与 os.Lstat() 实际应用中,你该如何选择?

文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…

少儿编程:开启多元成长,引领未来之路

今天,想跟大家深入聊聊一个当下热度颇高,却又让不少家长心存疑惑的话题 —— 少儿为什么要学习编程。 咱们先把目光投向国家层面的教育政策风向。近几年,国家对科技创新人才的培养予以了前所未有的重视,一系列重磅政策相继出台&a…