一,权限申请
默认应用等级和权限接口的等级是相对应的 应用等级成为APL 俗称就是一个权限等级的划分
应用等级(APL)划分为:
1.normal 这个标识普通应用
2.system basic 这个是系统服务
3.system_core 这个是核心系统服务 (这是操作系统能力 也就是你只要在鸿蒙中运行他就是支持的,但是目前看下来基本只会用到前面里两个等级)
权限等级(ACL):
1.normal
2.system basic
3.system_core (同理)
他们的等级是一一对应的 如果normal跨级访问system basic权限的接口
当应用需要访问用户的隐私信息或使用系统能力时,例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等,应该向用户请求授权,这部分权限是user_grant权限。
权限申请流程
1.第一步
首先就需要看 你申请的这个权限是否支持跨等级访问 这个官方有一个acl名单 标明了你这个等级的app是否支持那些权限 一般我们的应用默认都是normal
2.第二步
如果是支持的 要看使用这个权限的授权方式是userAgent 还是 system_grant 如果是userAgent 需要弹出一个授权弹框去找用户交互 让用户来手动授权 如果是system_grant 这个这个权限的你就可以直接声明权限直接去使用 不需要弹框告诉用户
3.第三步
要在modle.JSON5 文件中去声明权限(类似请求的那个权限就是写在那个文件里,具体怎么写 要看文档)
4.第四步
要自动生成签名 不然会导致你不能使用一些权限(所以切记 这个必须要生成,不然默认为你没有这个权限去访问这个写内容 ,他就相当于一个token ,有token才能访问这个内容)
5.最后
接下来就是正常套路了 先判断他是否拥有这个权限 如果拥有 然后再去调用申请这个系统的权限接口,这个调用的时候如果时userAgent 需要弹窗
具体实现流程
封装的一个权限管理工具类
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: `保存录音成功` })
}
录音结束 存数据库 假数据
-
初始化数据库
-
结束录音 - 假数据 获取相关数据,存储到数据库中
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%')
}
}
录音结束 存数据库 真数据
-
分析 子组件
AudioRecordComp.ets
要传递给父组件的数据AudioView.ets
-
子组件传递数据
-
声明属性
-
记录开始录制的时间
-
构造数据数据传递给父组件
-
-
父组件接收数据
删除录音
-
实现列表左滑删除
-
删除数据库数据
-
删除沙箱文件
-
实现滑动删除的组件结构
-
结构
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%")
}
-
执行删除
日期时间处理 dayjs
Date
复杂
2024年9月30日 23:59:59 + 3秒 =
第三方日期处理库
-
安装
-
css代码解读复制代码
ohpm i dayjs
-
-
引入
-
javascript代码解读复制代码
import dayjs from 'dayjs'
-
-
插入数据的时候 使用
编辑录音的名称
新建 编辑对话框
对话框里面 放文本输入框-显示待编辑的录音的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 代码解读复制代码* * *
评论记录:
回复评论: