【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对象也需要创建为全局对象,看起来就很恶心。所以我这里改成局部对象。

【官方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/bicheng/66528.shtml

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

相关文章

Ubuntu挂载云盘操作步骤

1. 查看磁盘分区情况 使用 fdisk -l 命令查看当前系统中所有磁盘的分区情况,找到需要挂载的云盘设备,例如/dev/vdc。 2. 创建新分区 使用 fdisk /dev/vdc 命令对云盘进行分区操作: 输入n创建新分区。 输入p选择创建主分区。 输入1指定分区…

stm32u5串口点灯

通过对单个字符输入的拼接暂存,实现对字符串的比较控制灯的亮灭 char buf[32];char temp[32];while (1){printf("start\n\r");memset(temp,0, sizeof(temp));memset(buf,0, sizeof(buf));while(temp[0] !\r){memset(temp,0, sizeof(temp));HAL_UART_Rece…

PHP 5 6 7 8 9 各重要版本开发特性和选择简要说明

PHP开发,所用版本的选型 PHP5.4是最后一个支持纯正32位操作系统的版本,在Winxp下仍可使用。 PHP5.6是php5.x的最后一个稳定版本,时至今天,仍有很多用户网站系统在使用,网上仍有很多学习资料是基于这个版本,…

Xen 虚拟化技术在云计算平台中的应用详解

Xen 虚拟化技术在云计算平台中的应用详解 随着云计算的飞速发展,虚拟化技术成为构建云平台的核心支柱,而 Xen 作为一种高性能、开源的虚拟化技术,被广泛应用于云计算平台中。Xen 凭借其灵活的架构和出色的性能,为众多云服务商提供…

Elixir语言的正则表达式

Elixir语言中的正则表达式 引言 正则表达式是用于匹配文本模式的一种强大工具。在很多编程语言中,正则表达式被广泛应用于字符串的查找、替换和验证。Elixir作为一门现代化的函数式编程语言,也提供了对正则表达式的支持,方便开发者进行复杂…

MATLAB语言的正则表达式

MATLAB 中的正则表达式使用指南 引言 在数据处理和文本分析中,正则表达式是一种强大而灵活的工具。MATLAB 作为一种广泛应用于科学计算和数据分析的编程语言,提供了对正则表达式的支持,使得用户可以方便地进行字符串匹配与处理。本文将深入…

《Java 中 Thread 类的基本用法总结》

在 Java 编程中,Thread类是实现多线程的核心类之一。下面将对Thread类在创建线程、线程中断、线程等待、线程休眠和获取线程实例等方面的基本用法进行总结。 一、线程创建 继承 Thread 类 定义一个类继承自Thread类。重写run方法,run方法中包含了该线程…

Flannel:Kubernetes 网络方案的“轻骑兵”

Flannel:Kubernetes 网络方案的“轻骑兵” 在 Kubernetes 中,网络是连接所有组件的核心。每个 Pod 都需要一个独立的 IP,方便 Pod 间的通信,而 Flannel 正是解决这个问题的经典容器网络插件(CNI)。它简单、…

Android - NDK:编译可执行程序在android设备上运行

在android开发中,调试时会把C代码直接编译成可执行程序,运行在android设备上以确认其功能是否正常。 1、基于NDK编译可执行文件 2、push到 /data/local/tmp目录下 3、设置权限,执行。 ndk工程中build.gradle设置 groovy plugins {id com.a…

用matlab调用realterm一次性发送16进制数

realterm采用PutString接口进行发送,需要注意的是发送的16进制数前面要加入0x标志。只有这样,realterm才能将输入的字符串识别为16进制数的形式。 另外,PutString函数支持两个参数输入,第一个参数为字符串,第二个参数为发送形式&…

Python3刷算法来呀,贪心系列题单

1.7号题单 1、​​​​​​k次取反后最大值 2、柠檬水找零 3、分发糖果 示例 1: 输入:ratings [1,0,2] 输出:5 解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。示例 2: 输入:ratings…

Couchbase 和数据湖技术的区别、联系和相关性分析

Couchbase 和数据湖技术(如 Delta Lake、Apache Hudi、Apache Iceberg)分别是两类不同的数据存储与管理系统,但它们也可以在特定场景中结合使用,以下是它们的区别、联系和相关性分析: 区别: 1. 核心用途&a…

el-table拖拽表格

1、拖拽插件安装 npm i -S vuedraggable // vuedraggable依赖Sortable.js,我们可以直接引入Sortable使用Sortable的特性。 // vuedraggable是Sortable的一种加强,实现组件化的思想,可以结合Vue,使用起来更方便。 2、引入拖拽函数…

深入理解 Android 中的 KeyguardManager

深入理解 Android 中的 KeyguardManager 引言 在 Android 系统中,KeyguardManager 是一个重要的系统服务,负责管理设备的锁屏界面(Keyguard)。锁屏界面是设备安全性的第一道防线,用于防止未经授权的用户访问设备。Ke…

Transformer 和 Attention机制入门

1. 什么是 Transformer 背景: 在自然语言处理领域,早期常使用循环神经网络(RNN)及其变体(如 LSTM、GRU)来处理序列数据,如机器翻译、文本生成等任务。然而,RNN 结构存在以下问题&…

Unity学习笔记(七)使用状态机重构角色攻击

前言 本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记 攻击状态重构 首先我们重构攻击状态的动画 之前的动画,我们是使用状态(isAttacking)攻击次数(comboCounter)完成动画的过渡,这样虽然能完成功能,但是如…

Windows10环境下安装RabbitMq折腾记

最近有个老项目需要迁移到windows10环境,用的是比较老的rabbitmq安装包,如下所示。经过一番折腾,死活服务起不来,最终果断放弃老版本启用新版本。现在把折腾过程记录下: 一、安装erlang 安装完成后的目录结构&#xff…

Three.js 基础概念:构建3D世界的核心要素

文章目录 前言一、场景(Scene)二、相机(Camera)三、渲染器(Renderer)四、物体(Object)五、材质(Material)六、几何体(Geometry)七、光…

了解RabbitMQ中的Exchange:深入解析与实践应用

在分布式系统设计中,消息队列(Message Queue)扮演着至关重要的角色,而RabbitMQ作为开源消息代理软件的佼佼者,以其高性能、高可用性和丰富的功能特性,成为了众多开发者的首选。在RabbitMQ的核心组件中&…

分布式主键ID生成方式-snowflake雪花算法

这里写自定义目录标题 一、业务场景二、技术选型1、UUID方案2、Leaf方案-美团(基于数据库自增id)3、Snowflake雪花算法方案 总结 一、业务场景 大量的业务数据需要保存到数据库中,原来的单库单表的方式扛不住大数据量、高并发,需…