写点什么

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

作者:GeorgeGcs
  • 2025-03-24
    上海
  • 本文字数:6142 字

    阅读完需:约 20 分钟

【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';

@Entry@Componentstruct 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)) }); }}
复制代码


发布于: 刚刚阅读数: 5
用户头像

GeorgeGcs

关注

路漫漫其修远兮,吾将上下而求索。 2024-12-24 加入

历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 OpenHarmony,HarmonyOS,Flutter,H5,Android,IOS。 目前任职鸿蒙应用架构师。 HarmonyOS官方认证创作先锋

评论

发布
暂无评论
【HarmonyOS NEXT】鸿蒙应用实现屏幕录制详解和源码_鸿蒙_GeorgeGcs_InfoQ写作社区