首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

鸿蒙NEXT-录音播放

  • 25-04-25 06:41
  • 2513
  • 6670
juejin.cn

一,权限申请

默认应用等级和权限接口的等级是相对应的 应用等级成为APL 俗称就是一个权限等级的划分

应用等级(APL)划分为:

1.normal 这个标识普通应用

2.system basic 这个是系统服务

3.system_core 这个是核心系统服务 (这是操作系统能力 也就是你只要在鸿蒙中运行他就是支持的,但是目前看下来基本只会用到前面里两个等级)

权限等级(ACL):

1.normal

2.system basic

3.system_core (同理)

他们的等级是一一对应的 如果normal跨级访问system basic权限的接口

file_1740838346026_670.png 当应用需要访问用户的隐私信息或使用系统能力时,例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等,应该向用户请求授权,这部分权限是user_grant权限。

权限申请流程

1.第一步

首先就需要看 你申请的这个权限是否支持跨等级访问 这个官方有一个acl名单 标明了你这个等级的app是否支持那些权限 一般我们的应用默认都是normal

2.第二步

如果是支持的 要看使用这个权限的授权方式是userAgent 还是 system_grant 如果是userAgent 需要弹出一个授权弹框去找用户交互 让用户来手动授权 如果是system_grant 这个这个权限的你就可以直接声明权限直接去使用 不需要弹框告诉用户

3.第三步

要在modle.JSON5 文件中去声明权限(类似请求的那个权限就是写在那个文件里,具体怎么写 要看文档)

4.第四步

要自动生成签名 不然会导致你不能使用一些权限(所以切记 这个必须要生成,不然默认为你没有这个权限去访问这个写内容 ,他就相当于一个token ,有token才能访问这个内容)

5.最后

接下来就是正常套路了 先判断他是否拥有这个权限 如果拥有 然后再去调用申请这个系统的权限接口,这个调用的时候如果时userAgent 需要弹窗

image.png

具体实现流程

封装的一个权限管理工具类

js
代码解读
复制代码
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; import { ENTRY_ABILITY_CONTEXT } from '../constants'; class Permission { // 请求用户授权 async requestPermissions(permissions: Permissions[]) { // 程序访问控制提供程序的权限管理能力,包括鉴权、授权等。 实例 const atManager = abilityAccessCtrl.createAtManager() const ctx = AppStorage.get<Context>(ENTRY_ABILITY_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 = AppStorage.get<Context>(ENTRY_ABILITY_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()

弹窗询问要权限 和 二次确认

entry/src/main/ets/pages/Test01.ets

js
代码解读
复制代码
import { permission } from '../commons/utils/Permission' import { Permissions } from '@kit.AbilityKit' import { promptAction, router } from '@kit.ArkUI' @Entry @Component struct Test01 { build() { Column() { Button("申请麦克风权限") .onClick(async () => { // 麦克风的权限数组 const perlist: Permissions[] = ["ohos.permission.MICROPHONE"] // 第一次请求权限 const res1 = await permission.requestPermissions(perlist) if (!res1) { // 如果首次没有授权 弹出确认窗 询问是否确定不授权 const confrim = await promptAction.showDialog({ title: "警告", message: "该模块必须要有麦克风权限,否则无法正常使用", buttons: [ { text: "不允许", color: "#f00" }, { text: "授权", color: "#000" } ] }) // 判断此次用户的决定 // 如果授权 if (confrim.index === 1) { const res2 = await permission.openPermissionSetting(perlist) if (!res2) { // 不授权 router.back() } } else { router.back() } } }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } }

二,功能划分

开始录音

AudioCapturer状态变化示意图

使用on('stateChange')方法可以监听AudioCapturer的状态变化,每个状态对应值与说明见AudioState。

entry/src/main/ets/pages/Test01.ets

完整代码

js
代码解读
复制代码
import { permission } from '../commons/utils/Permission' import { Permissions } from '@kit.AbilityKit' import { promptAction, router } from '@kit.ArkUI' import { media } from '@kit.MediaKit'; import { fileIo } from '@kit.CoreFileKit'; @Entry @Component struct Test01 { avRecorder: media.AVRecorder | undefined = undefined; @State text: string = "" build() { Column() { Text(this.text) .fontSize(30) Button("申请麦克风权限") .onClick(async () => { // 麦克风的权限数组 const perlist: Permissions[] = ["ohos.permission.MICROPHONE"] // 第一次请求权限 const res1 = await permission.requestPermissions(perlist) if (!res1) { // 如果首次没有授权 弹出确认窗 询问是否确定不授权 const confrim = await promptAction.showDialog({ title: "警告", message: "该模块必须要有麦克风权限,否则无法正常使用", buttons: [ { text: "不允许", color: "#f00" }, { text: "授权", color: "#000" } ] }) // 判断此次用户的决定 // 如果授权 if (confrim.index === 1) { const res2 = await permission.openPermissionSetting(perlist) if (!res2) { // 不授权 router.back() } } else { router.back() } } }) Button("开始录音") .onClick(async () => { // 指定录音文件的沙箱存储路径 const ctx = getContext(this) // cacheDir 容量有限制 满了 会覆盖 // filesDir 不卸载一直存在 const path = ctx.filesDir + `/1111.m4a` // 获得文件权限 const file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) // 1 创建 avRecord this.avRecorder = await media.createAVRecorder(); // 2 指定录音相关的配置信息 await this.avRecorder.prepare({ audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风 profile: { audioBitrate: 100000, // 音频比特率 audioChannels: 2, // 音频声道数 audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac audioSampleRate: 48000, // 音频采样率 fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a }, url: 'fd://' + file.fd, }); // 3 开始录制 await this.avRecorder.start() this.text = "开始录音" }) Button("结束录音") .onClick(async () => { await this.avRecorder?.release() this.text = "结束录音" }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } }

播放录音

在进行应用开发的过程中,建议开发者通过on('stateChange')方法订阅AudioRenderer的状态变更。因为针对AudioRenderer的某些操作,仅在音频播放器在固定状态时才能执行。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。

  • prepared状态: 通过调用createAudioRenderer()方法进入到该状态。
  • running状态: 正在进行音频数据播放,可以在prepared状态通过调用start()方法进入此状态,也可以在paused状态和stopped状态通过调用start()方法进入此状态。
  • paused状态: 在running状态可以通过调用pause()方法暂停音频数据的播放并进入paused状态,暂停播放之后可以通过调用start()方法继续音频数据播放。
  • stopped状态: 在paused/running状态可以通过stop()方法停止音频数据的播放。
  • released状态: 在prepared、paused、stopped等状态,用户均可通过release()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。
js
代码解读
复制代码
import { permission } from '../commons/utils/Permission' import { Permissions } from '@kit.AbilityKit' import { promptAction, router } from '@kit.ArkUI' import { media } from '@kit.MediaKit'; import { fileIo } from '@kit.CoreFileKit'; @Entry @Component struct Test01 { avRecorder: media.AVRecorder | undefined = undefined; avPlayer: media.AVPlayer | undefined = undefined @State text: string = "" Button("播放录音") .onClick(async () => { // 单词朗读 在线 // 现在 本地沙箱目录 // 创建avPlayer实例对象 this.avPlayer = await media.createAVPlayer(); this.avPlayer.on("stateChange", (state: string) => { // type AVPlayerState = 'idle' | 'initialized' | 'prepared' | 'playing' | 'paused' | 'completed' | 'stopped' | 'released' | 'error'; switch (state) { case "initialized": this.avPlayer?.prepare() break; case "prepared": this.avPlayer?.play() break; case "completed": this.avPlayer?.play() break; default: break; } }) // 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例 let path = getContext(this).filesDir + '/1111.m4a'; let file = fileIo.openSync(path); this.avPlayer.url = 'fd://' + file.fd this.text = "开始播放" }) Button("结束播放") .onClick(async () => { await this.avPlayer?.release() this.text = "结束播放" }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } }

声音大小 振幅 显示声纹

js
代码解读
复制代码
import { permission } from '../commons/utils/Permission' import { Permissions } from '@kit.AbilityKit' import { promptAction, router } from '@kit.ArkUI' import { media } from '@kit.MediaKit'; import { fileIo } from '@kit.CoreFileKit'; @Entry @Component struct Test01 { avRecorder: media.AVRecorder | undefined = undefined; avPlayer: media.AVPlayer | undefined = undefined @State text: string = "" tid: number = -1 @State amplitude: number = 1 aboutToAppear() { } build() { Column({ space: 10 }) { Row({ space: 5 }) { ForEach(Array.from({ length: 30 }), () => { Text() .width(4) .height(40 * Math.random() * (this.amplitude / 20000)) .backgroundColor(Color.Blue) }) } .width(300) .justifyContent(FlexAlign.SpaceBetween) Text(this.text) .fontSize(30) Button("申请麦克风权限") .onClick(async () => { // 麦克风的权限数组 const perlist: Permissions[] = ["ohos.permission.MICROPHONE"] // 第一次请求权限 const res1 = await permission.requestPermissions(perlist) if (!res1) { // 如果首次没有授权 弹出确认窗 询问是否确定不授权 const confrim = await promptAction.showDialog({ title: "警告", message: "该模块必须要有麦克风权限,否则无法正常使用", buttons: [ { text: "不允许", color: "#f00" }, { text: "授权", color: "#000" } ] }) // 判断此次用户的决定 // 如果授权 if (confrim.index === 1) { const res2 = await permission.openPermissionSetting(perlist) if (!res2) { // 不授权 router.back() } } else { router.back() } } }) Button("开始录音") .onClick(async () => { try { // 指定录音文件的沙箱存储路径 const ctx = getContext(this) // cacheDir 容量有限制 满了 会覆盖 // filesDir 不卸载一直存在 const path = ctx.filesDir + `/1111.m4a` // 获得文件权限 const file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) // 1 创建 avRecord this.avRecorder = await media.createAVRecorder(); // 2 指定录音相关的配置信息 await this.avRecorder.prepare({ audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风 profile: { audioBitrate: 100000, // 音频比特率 audioChannels: 2, // 音频声道数 audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac audioSampleRate: 48000, // 音频采样率 fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a }, url: 'fd://' + file.fd, }); // 3 开始录制 await this.avRecorder.start() this.text = "开始录音" } catch (e) { console.log("e 开始录音", e.message, e.code) } }) Button("获取声音的振幅") .onClick(() => { this.tid = setInterval(async () => { // 最大 30000 // 最小 0 this.amplitude = await this.avRecorder!.getAudioCapturerMaxAmplitude() if (this.amplitude > 20000) { this.amplitude = 20000 } else if (this.amplitude < 500) { this.amplitude = 500 } promptAction.showToast({ message: `${this.amplitude}` }) }, 500) }) Button("停止获取声音振幅") .onClick(() => { clearInterval(this.tid) }) Button("结束录音") .onClick(async () => { await this.avRecorder?.release() this.text = "结束录音" }) Button("播放录音") .onClick(async () => { // 单词朗读 在线 // 现在 本地沙箱目录 // 创建avPlayer实例对象 this.avPlayer = await media.createAVPlayer(); this.avPlayer.on("stateChange", (state: string) => { // type AVPlayerState = 'idle' | 'initialized' | 'prepared' | 'playing' | 'paused' | 'completed' | 'stopped' | 'released' | 'error'; switch (state) { case "initialized": this.avPlayer?.prepare() break; case "prepared": this.avPlayer?.play() break; case "completed": this.avPlayer?.play() break; default: break; } }) // 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例 let path = getContext(this).filesDir + '/1111.m4a'; let file = fileIo.openSync(path); this.avPlayer.url = 'fd://' + file.fd this.text = "开始播放" }) Button("结束播放") .onClick(async () => { await this.avPlayer?.release() this.text = "结束播放" }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } }

创建数据库

TestDB.ets

js
代码解读
复制代码
import { relationalStore } from '@kit.ArkData' @Entry @Component struct TestDB { store: relationalStore.RdbStore | null = null @State text: string = "" build() { Column({ space: 10 }) { Text(this.text) .fontSize(30) Button("创建数据库") .onClick(async () => { this.store = await relationalStore.getRdbStore(getContext(this), { name: 'asdfsdfd.db', securityLevel: relationalStore.SecurityLevel.S1 }) this.text = "创建数据库成功" }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } }

创建数据表

js
代码解读
复制代码
import { relationalStore } from '@kit.ArkData' /* * 自己理解透 手写 * cv获取到 * */ @Entry @Component struct TestDB { store: relationalStore.RdbStore | null = null tableName = 'article' @State text: string = "" // 获取仓库实例 getStore = async () => { this.store = await relationalStore.getRdbStore(getContext(this), { name: 'interview_tong.db', securityLevel: relationalStore.SecurityLevel.S1 }) this.text = "创建数据库成功" } aboutToAppear() { this.getStore() } build() { Column({ space: 10 }) { Text(this.text) .fontSize(30) Button("创建数据库") .onClick(async () => { this.getStore() }) Button("创建数据表") .onClick(async () => { await this.store?.executeSql(` CREATE TABLE IF NOT EXISTS ${this.tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL, create_time INTEGER NOT NULL ) `) this.text = "创建数据表格" }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } }

插入一条数据

js
代码解读
复制代码
Button("插入一条数据") .onClick(async () => { try { // 成功 返回 行id,失败返回 -1 const result = await this.store?.insert(this.tableName, { title: "标题" + Date.now(), content: "内容" + Date.now(), create_time: Date.now() }) this.text = `插入数据 ${result}` } catch (e) { promptAction.showToast({ message: `插入数据出错 ${e.message}` }) } })

查询数据

js
代码解读
复制代码
Button("查询数据") .onClick(async () => { // 要查询哪个数据表格的数据 const predicates = new relationalStore.RdbPredicates(this.tableName) // predicates.equalTo("id", 4) // 要查询文章数据 const resultSet = await this.store?.query(predicates) const list: ArticleItem[] = [] while (resultSet?.goToNextRow()) { list.push({ id: resultSet.getLong(resultSet.getColumnIndex('id')), title: resultSet.getString(resultSet.getColumnIndex('title')), content: resultSet.getString(resultSet.getColumnIndex('content')), create_time: resultSet.getLong(resultSet.getColumnIndex('create_time')) }) } // 关闭结果集 内存释放 后期无法 通过结果集再去拿数据 resultSet?.close() // this.list = list AlertDialog.show({ message: JSON.stringify(list, null, 2) }) })

删除数据

js
代码解读
复制代码
Button("删除数据") .onClick(() => { const predicates = new relationalStore.RdbPredicates(this.tableName) predicates.equalTo('id', 17) // 返回受影响的行数。 const res = this.store?.deleteSync(predicates) this.text = "删除表格中的数据" promptAction.showToast({ message: `删除了${res}条数据` }) })

修改数据

js
代码解读
复制代码
Button("修改数据") .onClick(() => { const predicates = new relationalStore.RdbPredicates(this.tableName) // predicates.equalTo('id', 18) const res = this.store?.updateSync({ title: "标题修改的数据", // content: "内容修改的数据", }, predicates) promptAction.showToast({ message: `修改了${res}数据` }) })

删除数据库

js
代码解读
复制代码
Button("删除数据库") .onClick(async () => { await relationalStore.deleteRdbStore(getContext(this), { name: 'interview_tong.db', securityLevel: relationalStore.SecurityLevel.S1 }) promptAction.showToast({ message: `删除数据库成功` }) })

新建了录音数据库工具类

entry/src/main/ets/commons/utils/AudioDB.ets

js
代码解读
复制代码
import { relationalStore, ValuesBucket } from '@kit.ArkData' import { ENTRY_ABILITY_CONTEXT } from '../constants' /** * 指定录音数据类型 */ export interface InterviewAudioItem extends ValuesBucket { id: number | null // 用户的id 同一个APP 可以存不同的用户数据 user_id user_id: string // 录音文件的名字 name: string // 录音存在沙箱目录下地址 播放录音要用到 path: string // 时长 duration: number // 大小 size: number // 文件创建日期 create_time: number } class AudioDB { store?: relationalStore.RdbStore tableName = 'interview_audio' // 初始化数据库 async initStore() { const ctx = AppStorage.get<Context>(ENTRY_ABILITY_CONTEXT) if (ctx) { const store = await relationalStore.getRdbStore(ctx, { name: 'interview_audio.db', securityLevel: relationalStore.SecurityLevel.S1 }) const sql = ` CREATE TABLE IF NOT EXISTS ${this.tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, name TEXT NOT NULL, path TEXT NOT NULL, duration INTEGER NOT NULL, size INTEGER NOT NULL, create_time INTEGER NOT NULL ) ` await store.executeSql(sql) this.store = store } } // 添加 async insert(item: InterviewAudioItem) { // 新插入的数据的id const rowId = await this.store?.insert(this.tableName, item) if (rowId === undefined || rowId === -1) { return Promise.reject('insert fail') } else { return Promise.resolve() } } // 删除 async delete(id: number) { const predicates = new relationalStore.RdbPredicates(this.tableName) predicates.equalTo('id', id) const rowCount = await this.store?.delete(predicates) if (rowCount === undefined || rowCount <= 0) { return Promise.reject('delete fail') } else { return Promise.resolve() } } // 修改 async update(item: InterviewAudioItem) { const predicates = new relationalStore.RdbPredicates(this.tableName) predicates.equalTo('id', item.id) const rowCount = await this.store?.update(item, predicates) if (rowCount === undefined || rowCount <= 0) { return Promise.reject('update fail') } else { return Promise.resolve() } } // 根据用户 user_id 来查询所有录音数据 async query(userId: string) { const predicates = new relationalStore.RdbPredicates(this.tableName) predicates.equalTo('user_id', userId) const resultSet = await this.store?.query(predicates) if (!resultSet) { return Promise.reject('query fail') } const list: InterviewAudioItem[] = [] while (resultSet.goToNextRow()) { list.push({ id: resultSet.getLong(resultSet.getColumnIndex('id')), user_id: resultSet.getString(resultSet.getColumnIndex('user_id')), name: resultSet.getString(resultSet.getColumnIndex('name')), path: resultSet.getString(resultSet.getColumnIndex('path')), duration: resultSet.getLong(resultSet.getColumnIndex('duration')), size: resultSet.getLong(resultSet.getColumnIndex('size')), create_time: resultSet.getLong(resultSet.getColumnIndex('create_time')) }) } resultSet.close() return Promise.resolve(list) } } export const audioDB = new AudioDB() // interface A { // num: number // } // // // function aaa(a: A) { // // } // // interface B extends A { // num: number // name: string // } // // const b: B = { // num: 100, // name: "xxx" // } // // 要求用 父类型, 而你传递子类型 有允许!!! // aaa(b)

三,具体实现

录音 开始 结束 保存文件

entry/src/main/ets/views/Audio/AudioRecordComp.ets

js
代码解读
复制代码
// 是否正在录音 @State isRecording: boolean = false // 声纹振幅 @State amplitude: number = 10 // 声音振幅定时器id tId: number = -1 // 录音 实例 avRecorder?: media.AVRecorder // 录音文件的沙箱路径 = ctx.filesDir + `/1111.m4a` filePath: string = "" // 开始录音 startRecord = async () => { this.isRecording = true // 开始录音 start // 指定录音文件的沙箱存储路径 const ctx = getContext(this) // cacheDir 容量有限制 满了 会覆盖 // filesDir 不卸载一直存在 this.filePath = ctx.filesDir + `/${Date.now()}.m4a` // 获得文件权限 const file = fileIo.openSync(this.filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) // 1 创建 avRecord this.avRecorder = await media.createAVRecorder(); // 2 指定录音相关的配置信息 await this.avRecorder.prepare({ audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风 profile: { audioBitrate: 100000, // 音频比特率 audioChannels: 2, // 音频声道数 audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac audioSampleRate: 48000, // 音频采样率 fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a }, url: 'fd://' + file.fd, }); // 3 开始录制 await this.avRecorder.start() // 结束录音 end // 测试 设置 声纹振幅 大小变化 this.tId = setInterval(async () => { // 500 - 20000 回去cv this.amplitude = await this.avRecorder!.getAudioCapturerMaxAmplitude() if (this.amplitude > 20000) { this.amplitude = 20000 } else if (this.amplitude < 500) { this.amplitude = 500 } }, 500) } // 结束录音 stopRecord = async () => { this.isRecording = false clearInterval(this.tId) // 结束录音 await this.avRecorder?.release() promptAction.showToast({ message: `保存录音成功` }) }

录音结束 存数据库 假数据

  1. 初始化数据库

  2. 结束录音 - 假数据 获取相关数据,存储到数据库中

js
代码解读
复制代码
import { HcNavBar } from '../../commons/components/HcNavBar' import { audioDB } from '../../commons/utils/AudioDB' import { auth } from '../../commons/utils/Auth' import { InterviewAudioItem } from '../../models' import { AudioItemComp } from './AudioItemComp' import { AudioRecordComp } from './AudioRecordComp' import { promptAction } from '@kit.ArkUI' @Component export struct AudioView { @State list: InterviewAudioItem[] = [{} as InterviewAudioItem, {} as InterviewAudioItem] async aboutToAppear() { // 初始化数据库 await audioDB.initStore() // 查询数据!! this.getList() } // 查询数据 async getList() { this.list = await audioDB.query("xxxx") } // 新数据到数据库 insert = async () => { // 插入数据 const res = await audioDB.insert({ id: null, user_id: "xxxx", // 存录音时 时间 name: "xxxx", // 沙箱目录 子组件传递 path: "xxxx", // 时长 子组件传递 duration: 3333, // 文件的大小 新需求 新api 传递 沙箱目录给我 子组件传递 size: 1000, // 文件创建日期 Date.now() create_time: 10000 }) promptAction.showToast({ message: `${res}` }) this.getList() } build() { Column() { HcNavBar({ title: '面试录音', showRightIcon: false }) Column() { Button("测试 新增数据") .onClick(() => { this.insert() }) List() { ForEach(this.list, (item: InterviewAudioItem) => { ListItem() { AudioItemComp({ item }) } }) } .width('100%') .height('100%') } .width('100%') .layoutWeight(1) AudioRecordComp() } .width('100%') .height('100%') } }

录音结束 存数据库 真数据

  1. 分析 子组件 AudioRecordComp.ets 要传递给父组件的数据 AudioView.ets

  1. 子组件传递数据

    1.   声明属性

    1.   记录开始录制的时间

    1.   构造数据数据传递给父组件

  1. 父组件接收数据

删除录音

  1. 实现列表左滑删除

  2. 删除数据库数据

  3. 删除沙箱文件

  4. 实现滑动删除的组件结构

  1. 结构

js
代码解读
复制代码
// 编辑和删除按钮 @Builder itemBuilder(item: InterviewAudioItem) { Row() { Button("编辑") .layoutWeight(1) .backgroundColor(Color.Blue) Button("删除") .layoutWeight(1) .backgroundColor(Color.Red) .onClick(() => { this.onDeleteItem(item) }) } .width("40%") .height("100%") }
  1. 执行删除

日期时间处理 dayjs

Date

复杂

2024年9月30日 23:59:59 + 3秒 =

第三方日期处理库

  1. 安装

    1. css
      代码解读
      复制代码
      ohpm i dayjs
  2. 引入

    1. javascript
      代码解读
      复制代码
      import dayjs from 'dayjs'
  3. 插入数据的时候 使用

编辑录音的名称

新建 编辑对话框

对话框里面 放文本输入框-显示待编辑的录音的name

点击键盘 保存 通知父组件 执行 修改数据操作 - 数据库

js
代码解读
复制代码
@CustomDialog export struct InputDialog { controller: CustomDialogController @Prop text: string = "" changeVoiceName: (t: string) => void = () => { } build() { Column() { TextInput({ text: $$this.text }) .onSubmit(() => { // 通知父组件 录音名称修改了 this.changeVoiceName(this.text) }) } .width(200) .height(50) .backgroundColor(`rgba(0,0,0,0.6)`) .justifyContent(FlexAlign.Center) .borderRadius(25) } }

父组件声明相关的状态

点击 编辑 弹出对话框

markdown
代码解读
复制代码
* * *

父组件保存数据

播放录音

新建播放组件

js
代码解读
复制代码
import { InterviewAudioItem } from '../../models' import { media } from '@kit.MediaKit' import { fileIo } from '@kit.CoreFileKit' import { promptAction } from '@kit.ArkUI' @Component export struct AudioPlayer { // 整个录音数据传递过来 @Prop item: InterviewAudioItem // 播放器实例 avPlayer: media.AVPlayer | undefined = undefined // 是否正在播放 @State isPlaying: boolean = false // 总时长 @State total: number = 0 // 当前播放进度 @State value: number = 0 build() { Column() { // 图片 Image($r('app.media.ic_mine_audio')) .width(100) .aspectRatio(1) // 名称 Text(this.item.name) .fontSize(18) // 控制条 Row({ space: 10 }) { Image(this.isPlaying ? $r('sys.media.ohos_ic_public_pause') : $r('sys.media.ohos_ic_public_play')) .width(24) .aspectRatio(1) .onClick(() => { if (this.isPlaying) { // 想要暂停 // this.pause() } else { // 想要播放 // this.play() } }) // 进度条组件 Progress({ total: this.total, value: this.value }) .layoutWeight(1) } .width("90%") } .width("100%") .height("100%") .justifyContent(FlexAlign.Center) .backgroundColor(Color.White) } }

父组件通过全模态来显示播放页面

播放页面 开始自动播放

播放页面实现 暂停

播放页面实现 恢复播放

播放页面实现 进度条功能

markdown
代码解读
复制代码
* * *

退出自动销毁

注:本文转载自juejin.cn的小镇梦想家的文章"https://juejin.cn/post/7476491917682245672"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

143
阅读
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top