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

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

一、前言

官方文档关于屏幕录制的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对象也需要创建为全局对象,看起来就很恶心。所以我这里改成局部对象,也避免了401参数错误的问题。

【官方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.rhkb.cn/news/504447.html

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

相关文章

java mail 535 Login Fail. Please enter your authorization code to login

报错信息提示查看 https://service.mail.qq.com/detail/0/53 帮助页面意思就是说你要使用授权码登录, 但是授权码我已经正确的设置上去了 后面从 QQ邮箱出现错误 Please enter your authorization code to_邮件群发-双翼邮件群发软件官方网 看到 账户 需要是 QQ号 例如…

怎样修改el-table主题样式

起因:el-table有主题样式,部分需要单独设置 环境:ideanodejs插件谷歌浏览器 第一步:找到scss文件: 谷歌浏览器打开表格页面,ctrlshifti打开开发者工具,点击后鼠标移动到表格单元格上单击一下…

记录一次面试中被问到的问题 (HR面)

文章目录 一、你对公司的了解多少二、为什么对这个岗位感兴趣三、不能说的离职原因四、离职原因高情商回复五、你的核心优势是什么六、你认为你比其他面试候选人的优势是什么七、不要提及情感 一、你对公司的了解多少 准备要点: 在面试前,对公司进行充分…

从零开始:使用VSCode搭建Python数据科学开发环境

引言 在数据科学领域,一个高效、稳定的开发环境是成功的关键。本文将详细介绍如何使用Visual Studio Code搭建一个完整的Python数据科学开发环境。通过本指南,您将学会: 安装和配置VSCode,包括基本设置和快捷键配置设置Python开…

【C++习题】20. 两个数组的交集

题目:349. 两个数组的交集 - 力扣(LeetCode) 链接🔗:349. 两个数组的交集 - 力扣(LeetCode) 题目: 代码: class Solution { public:// 函数功能:求两个数组…

【深度学习】深度(Deep Learning)学习基础

深度学习(Deep Learning) 深度学习是一种基于人工神经网络的机器学习方法,通过多个层次(深度)的神经网络从数据中自动学习特征和模式。它是人工智能的一个核心领域,尤其在处理复杂数据(如图像、…

【MySQL 保姆级教学】用户管理和数据库权限(16)

数据库账户管理是指对数据库用户进行创建、修改和删除等操作,以控制用户对数据库的访问权限。通过账户管理,可以设置用户名、密码、主机地址等信息,确保数据库的安全性和可控性。例如,使用 CREATE USER 创建用户,ALTER…

STM32+WIFI获取网络时间+8位数码管显示+0.96OLED显

资料下载地址:STM32WIFI获取网络时间8位数码管显示0.96OLED 1、项目介绍 主控芯片STM32C8T6 接线:串口1:PA9 PA10 OELD :PB6 PB7 数码管使用:MAX7219 8位数码管 Max7219_pinCLK PAout(5) Max7219_pinC…

决定系数(R²分数)——评估回归模型性能的一个指标

目录 1.定义 2.计算举例 3. 结果分析 1.定义 R(R平方)分数,也称为决定系数,是用来评估回归模型性能的一个指标。它表示自变量解释因变量变异性的比例。R分数的取值范围通常在0到1之间,其值越接近1,说明…

代码随想录算法训练营day23

代码随想录算法训练营 —day23 文章目录 代码随想录算法训练营前言一、39. 组合总和二、40.组合总和II三、131.分割回文串总结 前言 今天是算法营的第23天,希望自己能够坚持下来! 今日任务: ● 39. 组合总和 ● 40.组合总和II ● 131.分割回…

【电子通识】PWM驱动让有刷直流电机恒流工作

电机的典型驱动方法包括电压驱动、电流驱动以及PWM驱动。本文将介绍采用PWM驱动方式的恒流工作。 首先介绍的是什么是PWM驱动的电机恒流工作,其次是PWM驱动电机恒流工作时电路的工作原理。 PWM驱动 当以恒定的电流驱动电机时,电机会怎样工作呢&#xff1…

基于html5实现音乐录音播放动画源码

源码介绍 基于html5实现音乐录音播放动画源码是一款类似Shazam的UI,点击按钮后,会变成为一个监听按钮。旁边会有音符飞入这个监听按钮,最后转换成一个音乐播放器。 效果预览 源码获取 基于html5实现音乐录音播放动画源码

精度论文:【Coordinate Attention for Efficient Mobile Network Design】

Coordinate Attention for Efficient Mobile Network Design 《用于高效移动网络设计的坐标注意力机制》1.引言2.相关工作2.1 移动网络架构2.2 注意力机制 3. 坐标注意力3.1. 回顾SE注意力 (Squeeze-and-Excitation Attention)3.2. 坐标注意力块3.2.1 坐标信息嵌入3.2.2 坐标注…

高等数学学习笔记 ☞ 一元函数微分的基础知识

1. 微分的定义 (1)定义:设函数在点的某领域内有定义,取附近的点,对应的函数值分别为和, 令,若可以表示成,则称函数在点是可微的。 【 若函数在点是可微的,则可以表达为】…

openai swarm agent框架源码详解及应用案例实战

文章目录 简介数据类型Agent类Response类Result类Swarm类run_demo_loop交互式会话 基础应用agent-handsofffunction-callingcontext_variablestriage_agent 高阶应用通用客服机器人(support bot)构建航班服务agent 参考资料 openai 在24年10月份开源了一个教育性质的多agents协…

【测试】——Cucumber入门

📖 前言:Cucumber框架是行为驱动(BDD)框架的一种,通过自然语言站在功能使用者视角,描述编写测试用例。简单来说就是通过feature文件编写脚本,脚本对应java写的方法,会有一个启动器配…

无网络时自动切换备用网络环境

目录 背景目标为什么需要做自动网络切换网络切换手段 网络环境实现思路和代码部署脚本开机自动执行附录连接两个网络时的路由问题 背景 目标 学校实验室有两个网络环境,我电脑使用网线连接稳定但低速的网络A,使用WiFi连接高速但不稳定的网络B。因此&am…

【微服务】1、引入;注册中心;OpenFeign

微服务技术学习引入 - 微服务自2016年起搜索指数持续增长,已成为企业开发大型项目的必备技术,中高级java工程师招聘多要求熟悉微服务相关技术。微服务架构介绍 概念:微服务是一种软件架构风格,以专注于单一职责的多个响应项目为基…

OpenAI 故障复盘 - 阿里云容器服务与可观测产品如何保障大规模 K8s 集群稳定性

本文作者: 容器服务团队:刘佳旭、冯诗淳 可观测团队:竺夏栋、麻嘉豪、隋吉智 一、前言 Kubernetes(K8s)架构已经是当今 IT 架构的主流与事实标准(CNCF Survey[1])。随着承接的业务规模越来越大,用户也在使…

docker+ffmpeg+nginx+rtmp 拉取摄像机视频

1、构造程序容器镜像 app.py import subprocess import json import time import multiprocessing import socketdef check_rtmp_server(host, port, timeout5):try:with socket.create_connection((host, port), timeout):print(f"RTMP server at {host}:{port} is avai…