注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下

如果大家觉得博主文章写的好的话,可以点下关注,博主会一直更新鸿蒙next相关知识

目录

1. 面试录音

1.1. 应用权限

1.1.1. 应用权限概述

1.1.2. permission 工具

1.1.3. 录音授权

1.2. 录音知识

1.2.1. 使用 AvRecorder 录音

1.2.2. 录音声音振动效果

1.2.3. 使用 AvPlayer 播放


1. 面试录音

1.1. 应用权限

1.1.1. 应用权限概述

系统提供了一种允许应用访问系统资源(如:通讯录等)和系统能力(如:访问摄像头、麦克风等)的通用权限访问方式,来保护系统数据(包括用户个人数据)或功能,避免它们被不当或恶意使用。

应用申请敏感权限时,必须填写权限使用理由字段,敏感权限通常是指与用户隐私密切相关的权限,包括地理位置、相机、麦克风、日历、健身运动、身体传感器、音乐、文件、图片视频等权限。参考向用户申请授权。

  • system_grant

在配置文件中,声明应用需要请求的权限后,系统会在安装应用时自动为其进行权限预授予,开发者不需要做其他操作即可使用权限。

  • user_grant
    • 在配置文件中,声明应用需要请求的权限,且要设置需要使用的场景+使用原因
    • 调用 requestPermissionsFromUser() 方法后,应用程序将等待用户授权的结果。如果用户授权,则可以继续访问目标操作。如果用户拒绝授权,则需要提示用户必须授权才能访问当前页面的功能,并引导用户到系统应用“设置”中打开相应的权限。可参考二次向用户申请权限 requestPermissionOnSetting()

对所有应用开放权限列表

录音授权演示:

module.json5

"requestPermissions": [
  {
    "name": 'ohos.permission.INTERNET'
  },
  {
    "name": "ohos.permission.MICROPHONE",
    "reason": "$string:permission_microphone",
    "usedScene": {
      "abilities": ["EntryAbility"]
    }
  }
],

原因格式:用于xxx模块xxx功能

{
  "string": [
    ...
    {
      "name": "permission_microphone",
      "value": "录音功能申请麦克风权限"
    }
  ]
}

页面测试:

import { abilityAccessCtrl, Permissions } from "@kit.AbilityKit"
import { HcNavBar } from "../../commons/components/HcNavBar"

@Component
  struct AudioPage {
    aboutToAppear(): void {
      this.requestPermission()
    }

    async requestPermission() {
      // 1. 用户授权
      const permissionList: Permissions[] = ['ohos.permission.MICROPHONE']
      const atManager = abilityAccessCtrl.createAtManager()
      const ctx = getContext(this)
      const result = await atManager.requestPermissionsFromUser(ctx, permissionList)
      const flag = result.authResults.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
      // 2. 二次授权
      if (!flag) {
        const result2 = await atManager.requestPermissionOnSetting(ctx, permissionList)
        const flag2 = result2.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
        console.log(flag2.toString())
      }
    }

    build() {
      //必须用NavDestination包裹
      NavDestination() {
        HcNavBar({ title: '录音功能' })
      }
      .hideTitleBar(true)
    }
  }

// 跳转页面入口函数
@Builder
  export function AudioBuilder() {
    AudioPage()
  }

配置路由

{
  "routerMap": [
    ...
    {
      "name": "AudioPage",
      "pageSourceFile": "src/main/ets/pages/Audio/AudioPage.ets",
      "buildFunction": "AudioBuilder"
    }
  ]
}

跳转页面

this.toolsBuilder({
  icon: $r('app.media.ic_mine_invite'),
  name: '面试录音',
  onClick: () => {
    auth.checkAuth({
      name: 'AudioPage'
    })
  }
})

1.1.2. permission 工具

目标:封装权限工具,提供请求用户权限,拉起用户权限设置的能力

import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { AppStorageV2 } from '@kit.ArkUI';
import { SavedContext } from '../../models';

class Permission {
  // 请求用户授权
  async requestPermissions(permissions: Permissions[]) {
    const atManager = abilityAccessCtrl.createAtManager()
    const ctx = AppStorageV2.connect(SavedContext)!.context
    if (ctx) {
      const result = await atManager.requestPermissionsFromUser(ctx, permissions)
      return result.authResults.every(result => result === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
    }
    return false
  }

  // 打开权限设置 beta3
  async openPermissionSetting(permissions: Permissions[]) {
    const atManager = abilityAccessCtrl.createAtManager()
    const ctx = AppStorageV2.connect(SavedContext)!.context
    if (ctx) {
      const authResults = await atManager.requestPermissionOnSetting(ctx, permissions)
      return authResults.every(result => result === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
    }
    return false
  }
}

export const permission = new Permission()

1.1.3. 录音授权

目标:使用权限请求工具,在录音页面实现请求权限,无权限不可进入

import { HcNavBar } from "../../commons/components/HcNavBar"
import { promptAction } from "@kit.ArkUI"
import { navPathStack } from "../Index"
import { permission } from "../../commons/utils/Permission"
import { Permissions } from "@kit.AbilityKit"

@ComponentV2
struct AudioPage {
  permissions: Permissions[] = ['ohos.permission.MICROPHONE']
  confirmConfig: promptAction.ShowDialogOptions = {
    title: "温馨提示",
    message: "未授权使用麦克风将无法使用该面试录音功能,是否前往设置进行授权?",
    buttons: [
      { text: '离开', color: $r('app.color.common_gray_01') },
      { text: '去授权', color: $r('app.color.black') }
    ]
  }

  aboutToAppear(): void {
    this.requestPermission()
  }

  async requestPermission() {
    try {
      // 第一请求授权
      const isOk = await permission.requestPermissions(this.permissions)
      if (isOk) {
        return
      }
      // 弹窗提示
      const confirm = await promptAction.showDialog(this.confirmConfig)
      if (confirm.index === 1) {
        const isOk2 = await permission.openPermissionSetting(this.permissions)
        if (isOk2) {
          return
        }
      }
      navPathStack.pop()
    } catch (e) {
      promptAction.showToast({ message: '未授权' })
      navPathStack.pop()
    }
  }

  build() {
    //必须用NavDestination包裹
    NavDestination() {
      HcNavBar({ title: '录音功能' })
    }
    .hideTitleBar(true)
  }
}

// 跳转页面入口函数
@Builder
export function AudioBuilder() {
  AudioPage()
}

1.2. 录音知识

1.2.1. 使用 AvRecorder 录音

目标:使用 AvRecorder 实现音频录制存储到应用沙箱

实现步骤:

  • 需要一个文件接收音频数据
  • 准备录音配置
  • 使用 AvRecorder 实现开始录音,结束录音

落地代码:

avRecorder?: media.AVRecorder // 音视频录制管理类
fd?: number  // 资源句柄(fd)
filePath?: string // 文件路径

async startRecord() {
  // 1. 准备一个文件接收录音
  const ctx = getContext(this)
  const filePath = ctx.filesDir + '/' + Date.now() + '.m4a'
  this.filePath = filePath
  const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
  this.fd = file.fd
  // 2. 准备路由配置对象
  const config: media.AVRecorderConfig = {
    audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
    profile: {
      audioBitrate: 100000, // 音频比特率
      audioChannels: 1, // 音频声道数
      audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac
      audioSampleRate: 48000, // 音频采样率
      fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a
    },
    url: `fd://${file.fd}`
  }
  // 3. 开始录制
  const avRecorder = await media.createAVRecorder()
  await avRecorder.prepare(config)
  await avRecorder.start()
  this.avRecorder = avRecorder
}

async stopRecord() {
  if (this.avRecorder) {
    await this.avRecorder.stop()
    await this.avRecorder.release()
    fileIo.closeSync(this.fd)
  }
}

//必须用NavDestination包裹
NavDestination() {
  HcNavBar({ title: '录音功能' })
  Button('开始录音')
    .onClick(() => {
      this.startRecord()
    })
  Button('结束录音')
    .onClick(() => {
      this.stopRecord()
    })
}
.hideTitleBar(true)

1.2.2. 录音声音振动效果

目标:根据声音的大小实现声音振动特效

实现步骤:

  • 通过 getAudioCapturerMaxAmplitude 观察音频区间
  • 封装振动组件,通过声音振幅数据实现振动效果

落地代码:

1)获取振幅数据,出入振动组件 AudioPage.ets

timer?: number
@Local maxAmplitude: number = 0

// 每100ms获取一下声音振幅
this.timer = setInterval(async () => {
  this.maxAmplitude = await avRecorder.getAudioCapturerMaxAmplitude()
  logger.debug('startRecord', this.maxAmplitude.toString())
}, 100)

async stopRecord() {
  if (this.avRecorder) {
    await this.avRecorder.stop()
    await this.avRecorder.release()
    fileIo.closeSync(this.fd)
    // stopRecord 清理定时器
    clearInterval(this.timer)
  }
}

AudioBoComp({ maxAmplitude: this.maxAmplitude })

2)实现振动组件 Audio/components/AudioBoComp.ets

@ComponentV2
export struct AudioBoComp {
  @Local per: number = 0
  @Param maxAmplitude: number = 0
  @Monitor('maxAmplitude')
  onChange() {
    animateTo({ duration: 100 }, () => {
      if (this.maxAmplitude < 500) {
        this.per = 0
      } else if (this.maxAmplitude > 30000) {
        this.per = 1
      } else {
        this.per = this.maxAmplitude / 30000
      }
    })
  }

  build() {
    Row({ space: 5 }) {
      ForEach(Array.from({ length: 30 }), () => {
        Column()
          .layoutWeight(1)
          .height(this.per * 100 * Math.random())
          .backgroundColor($r('app.color.common_blue'))
      })
    }
    .width('100%')
    .height(100)
  }
}

1.2.3. 使用 AvPlayer 播放

目标:能够使用 AvPlayer 播放应用沙箱中的音频文件,且显示进度条

落地代码:

avPlayer?: media.AVPlayer
@Local total: number = 0
@Local value: number = 0

async startPlay() {
  try {
    const file = fileIo.openSync(this.filePath, fileIo.OpenMode.READ_ONLY)
    const avPlayer = await media.createAVPlayer()
    avPlayer.on('stateChange', state => {
      if (state === 'initialized') {
        avPlayer.prepare()
      } else if ( state === 'prepared') {
        avPlayer.loop = true
        this.total = avPlayer.duration
        avPlayer.play()
      }
    })
    // 当前播放时间改变
    avPlayer.on('timeUpdate', (time) => {
      this.value = time
    })
    avPlayer.url = `fd://${file.fd}`
    this.avPlayer = avPlayer
  } catch (e) {
    logger.error('startPlay', JSON.stringify(e))
  }
}

stopPlay() {
  if (this.avPlayer) {
    this.avPlayer.stop()
    this.avPlayer.release()
  }
}

Button('开始播放')
  .onClick(() => {
    this.startPlay()
  })
Button('停止播放')
  .onClick(() => {
    this.stopPlay()
  })
Progress({ total: this.total, value: this.value })
  .width('100%')

HarmonyOS赋能资源丰富度建设(第四期)-吴东林

https://developer.huawei.com/consumer/cn/training/classDetail/9fdeeb1a35d64d2fabad3948ae7aab72?type=1?ha_source=hmosclass&ha_sourceId=89000248

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐