HarmonyOS NEXT 鸿蒙ArkTS 权限控制-录音播放功能实现,一通百通!

458 阅读6分钟

一、权限相关信息

鸿蒙权限列表.png

以上是官网的权限列表,权限也分等级,不同的等级会有不同的授权方式, 比如:

image.png

第一个是禁默授权,第三个我们调不了,今天我们要做的是录音权限属于第二个,一套代码搞定所有权限控制!

二、提前配置好需要调用的权限

和我们调用网络权限一样,放在module.json5的requestPermissions里

image.png

比如有需要做定位权限的,就可以这样配置(权限等级为normal的只用配置name):

// 位置信息 
{ // 模糊定位 
"name": "ohos.permission.APPROXIMATELY_LOCATION", 
"reason": '$string:permission_reason_location', 
"usedScene": {} }, 
{ // 精准定位 
"name": "ohos.permission.LOCATION", 
"reason": '$string:permission_reason_location', 
"usedScene": {} },

然后在string.json里面配置对应的value{ "name": "permission_reason_camera", "value": "相机授权原因" },

  • 细节:这里获得是WGS-84的坐标系,在国内显示的是国测局的坐标系(地图上)

地图需要多配置一个AGC平台上获取的id,就是搞四个证书的地方

image.png

签名证书.png

接下来继续搞我们的录音

三、写一个类用于权限的管理(三个功能)

1.功能一:检查是否授权

checkPermissions(permissions: Permissions[]) {
  // 1. 创建应用权限管理器
  const atManager = abilityAccessCtrl.createAtManager()
  // 2. 获取 bundle 包信息,Sync 写法
  const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
  // 3. 提取 tokenID
  const tokenID = bundleInfo.appInfo.accessTokenId
  // 4. 检测是否授权,Sync 写法,升级成遍历权限组(数组)!!!
  const grantStatus = permissions.map(item => atManager.checkAccessTokenSync(tokenID, item))
  // 返回权限数组的检测结果
  return grantStatus.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
  // 温馨提示:当前函数没有用到 Promise,都是用 Sync 函数,好处:可直接再组件渲染权限检测结果
}

2.功能二:动态申请授权(首次弹窗申请)

async requestPermissions(permissions: Permissions[]) {
  // 1. 创建应用权限管理器
  const atManager = abilityAccessCtrl.createAtManager()
  // 2. 向用户申请 user_grant 权限(温馨提示:首次申请时会弹窗,后续申请则不会再出现弹窗)
  const requestResult = await atManager.requestPermissionsFromUser(
    getContext(), // 应用上下文
    permissions   // 参数:权限列表(数组)
  )
  // 通过 every 检查权限是否都成功授权
  const isAuth = requestResult.authResults.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
  // Promise.resolve()   返回 Promise 成功,await 后续代码,正常执行
  // Promise.reject()    返回 Promise 错误,await 后续代码,不被执行,Promise.reject() 的结果可被 catch 捕获
  return isAuth === true ? Promise.resolve(true) : Promise.reject(false)
}

3.功能三:打开系统设置的权限管理页(处理授权结果)

openPermissionSettingsPage() {
  // 1. 获取应用上下文,并通过 as 断言收窄类型为 UIAbilityContext,否则 context 默认类型无法调用 startAbility 方法
  const context = getContext() as common.UIAbilityContext
  // 2. 获取 bundle 包信息
  const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
  // 3. 通过 startAbility 打开 系统设置 页
  context.startAbility({
    bundleName: 'com.huawei.hmos.settings', // 固定写法CV:设置页的包名
    abilityName: 'com.huawei.hmos.settings.MainAbility', // 固定写法CV:设置页的 ability 名
    uri: 'application_info_entry', // 固定写法CV:打开 设置->应用和元服务
    parameters: {
      // 打开指定应用(包)的详情页面
      // pushParams: 'com.itheima.hm_guardian'
      // 应用包名可通过 bundleManager 动态获取
      pushParams: bundleInfo.name
    }
  })
}
  • import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';最后new一下导出去就可以了,这套代码可以供所有的调用权限使用

四、开始做录音和播放的功能

1.核心:创建音频采集器+创建音频播放器

audio.createAudioCapturer(this.audioCapturerOptions) 这里的audioCapturerOptions包含音频流信息和音频渲染器信息,这里代码固定,除非要做音频改动的

// 音频流信息
audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
  channels: audio.AudioChannel.CHANNEL_2, // 通道
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
};
// 音频采集器信息
audioCapturerInfo: audio.AudioCapturerInfo = {
  source: audio.SourceType.SOURCE_TYPE_MIC, // 声音来源
  capturerFlags: 0, // 0 代表普通音频采集器,1 代表低时延音频采集器 (ArkTS接口暂不支持低时延音频采集器)
};
// createAudioCapturer 创建音频采集器时的必传参数
audioCapturerOptions: audio.AudioCapturerOptions = {
  streamInfo: this.audioStreamInfo, // 音频流信息
  capturerInfo: this.audioCapturerInfo, // 音频采集器信息
};

audio.createAudioRenderer(this.audioRendererOptions)这个播放器,如果要那种根据耳朵贴近来自动切换听筒模式,需要用到传感器权限,也是用上面的代码直接搞定!

// 音频渲染器信息
audioRendererInfo: audio.AudioRendererInfo = {
  // usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,  // 用听筒播放
  usage: audio.StreamUsage.STREAM_USAGE_MOVIE, // 用外放喇叭播放
  // usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 用外放喇叭播放
  // usage: audio.StreamUsage.STREAM_USAGE_GAME, // 用外放喇叭播放
  rendererFlags: 0
};
// 音频渲染器配置
audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: this.audioStreamInfo, // 音频流信息
  rendererInfo: this.audioRendererInfo  // 音频渲染器信息
};

2.做录音功能

  • 1.先申请权限,用上面的new出来的对象 .requestPermissions(['ohos.permission.MICROPHONE'])这里返回的是个布尔类型,需要用户开放权限,或者给他直接跳到设置页面让他勾!

  • 2.创建音频采集器

  • 3.getContext()获得应用上下文

  • 4.调用fileIo里面的open方法创建一个文件路径用来储存我们的录音(路径,可读可写)

  • 5.采集器数据读入on方法

  • 6.采集器开始录制start方法

// 1. 创建音频采集器
const audioCapturer = await audio.createAudioCapturer(this.audioCapturerOptions);
// 保存起来,方便其他地方使用
this.audioCapturer = audioCapturer
this.isCreate = true
// ------ 文件系统 fileIo ------
//   1) 创建并打开本地文件
const context = getContext()
//   2) 通过应用上下文,获取到应用的 files 路径
this.filePath = context.filesDir + '/' + Date.now() + '.wav'
//   3) 创建并打开文件,注意打开模式选择可读可写 READ_WRITE
const file = fileIo.openSync(this.filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
// ------ 文件系统 ------
// 2. 订阅音频数据读入
audioCapturer.on('readData', (buffer) => {
  //  4) 把 buffer 写入到打开的文件中(file.fd为打开文件后标识)
  fileIo.writeSync(file.fd, buffer)
  console.log('读取采集器的音频流大小(单位B)', buffer.byteLength);
})
// // 2.x 订阅采集器状态
audioCapturer.on('stateChange', (state) => {
  this.audioState = state
})
// 3. 开始录制音频
await audioCapturer.start()


// 如果以上步骤出错,就不会运行到这里
promptAction.showToast({ message: '音频采集器创建成功' })

3.做录音的暂停、继续、释放资源功能

if (this.audioCapturer?.state === audio.AudioState.STATE_RUNNING) {
  // 停止采集
  await this.audioCapturer?.stop()
  promptAction.showToast({ message: '停止成功' })
}

// 开始(继续)采集
await this.audioCapturer?.start()
promptAction.showToast({ message: '继续开始' })

// 释放资源(释放内存和硬件)
await this.audioCapturer?.release()
this.isCreate = false
promptAction.showToast({ message: '释放资源' })

4.做播放录音功能

  • 1.创建音频渲染器(播放器)
  • 2.根据路径打开文件(这里要传文件路径的参数,点谁就是谁呗)
  • 3.使用渲染器的on方法读取文件的buffer,写入到渲染器中
  • 4.开始渲染start方法
  • 5.根据文件的大小,设置自动停止渲染(不然会不停的播放最后的buffer)
// 1. 创建音频渲染器
const audioRenderer = await audio.createAudioRenderer(this.audioRendererOptions);
// 保存起来
this.audioRenderer = audioRenderer
// 根据路径打开文件
const file = fileIo.openSync(this.filePath)
// 获取文件信息(大小,创建时间等)
const fileStat = fileIo.statSync(file.fd)
console.log('音频渲染器 buffer 当前文件大小为(单位B)', fileStat.size)
// 准备一个累加值,用于自动停止渲染
let bufferSize = 0
// 2. 订阅(buffer写入数据到音频渲染器中,就能发出声音)
audioRenderer.on('writeData', (buffer) => {
  // 读取打开文件的 buffer,写入到渲染器中,就能发出声音
  fileIo.readSync(file.fd, buffer)
  // buffer 大小累加,用于自动停止
  bufferSize += buffer.byteLength
  // 累加的结果,是否已超过文件大小
  if (bufferSize >= fileStat.size) {
    // 自动停止渲染
    audioRenderer.stop()
  }
  console.log('音频渲染器 buffer 累加结果(单位B)', bufferSize)
})
// 3. 开始渲染
audioRenderer.start()

promptAction.showToast({ message: '音频渲染器正常' })

5.做渲染的停止和释放资源功能(一样的)

await this.audioRenderer?.stop()
promptAction.showToast({ message: '停止成功' })

await this.audioRenderer?.release()
promptAction.showToast({ message: '销毁实例,释放资源成功' }

image.png