HarmonyOS 鸿蒙Next 搞定了SoundPool,有了音效!做了小封装。开心。
HarmonyOS 鸿蒙Next 搞定了SoundPool,有了音效!做了小封装。开心。
话说一心想给应用加点儿音效。
前段时间学这用 avPlayer,感觉多个声音同时播放时会有杂音。
结果华为自己在文档中就推荐了 soundPool:
使用SoundPool(音频池)提供的接口,可以实现低时延短音播放。
当应用开发时,经常需要使用一些急促简短的音效(如相机快门音效、系统通知音效等),此时建议调用SoundPool,实现一次加载,多次低时延播放。
SoundPool当前支持播放1MB以下的音频资源,大小超过1MB的长音频将截取1MB大小数据进行播放。
毕竟是老白,业余玩家,看了文档半天没弄明白。
主要是涉及两个痛点:
- 如何加载rawfile下的资源文件?
- 异步操作的逻辑?
查了一堆网上资料,琢磨了两天。
参照华为技术文档,自己尝试着封装了一下功能,测试成功!
先上图片:
再上代码:
import { audio } from ‘@kit.AudioKit’;
import { media } from ‘@kit.MediaKit’;
import { BusinessError } from ‘@kit.BasicServicesKit’;
@Preview
@Entry
@ComponentV2
struct TestSound {
@Local ppSound: soundHelper | undefined = undefined
aboutToAppear() {
this.ppSound = new soundHelper() //创建类实例
//在OnCreated中注册有关事件
this.ppSound.onCreated = () => {
//注册 【错误回调函数】监听
this.ppSound?.setErrorCallback()
//注册 【音乐文件加载完毕】监听
this.ppSound?.soundPool?.on(‘loadComplete’, (soundId_: number) => {
this.ppSound?.PlaySoundPool() //加载完毕即播放一次,如不需要可不注册此事件
})
//注册 【播放完毕】监听 ,
this.ppSound?.soundPool?.on(‘playFinished’, () => {
this.ppSound?.addSoundMsg(‘声音文件播放完毕’)
})
//加载声音文件,此文件放在 resources/rawfile 目录下
this.ppSound?.loadSoundRawFile(‘firework.mp3’)
}
//开始创建 soundPool
this.ppSound.create()
}
aboutToDisappear(): void {
//注销有关监听
this.ppSound?.soundPool?.off(“playFinished”)
this.ppSound?.soundPool?.off(“loadComplete”)
this.ppSound?.soundPool?.off(“error”)
}
build() {
Column() {
<span class="hljs-title class_">Text</span>(<span class="hljs-string">"ArkTS 测试【soundPool】播放声音"</span>)
.<span class="hljs-title function_">margin</span>({ <span class="hljs-attr">top</span>: <span class="hljs-number">20</span> })
.<span class="hljs-title function_">fontSize</span>(<span class="hljs-string">'15'</span>)
.<span class="hljs-title function_">fontWeight</span>(<span class="hljs-title class_">FontWeight</span>.<span class="hljs-property">Bolder</span>)
.<span class="hljs-title function_">fontColor</span>(<span class="hljs-string">'#aaaaaa'</span>)
<span class="hljs-title class_">Text</span>(<span class="hljs-string">"By 端州老白"</span>)
.<span class="hljs-title function_">margin</span>({ <span class="hljs-attr">top</span>: <span class="hljs-number">5</span> })
.<span class="hljs-title function_">fontSize</span>(<span class="hljs-string">'14'</span>)
.<span class="hljs-title function_">fontColor</span>(<span class="hljs-string">'#888888'</span>)
<span class="hljs-title class_">Button</span>(<span class="hljs-string">'点击重播'</span>)
.<span class="hljs-title function_">margin</span>({ <span class="hljs-attr">top</span>: <span class="hljs-number">10</span> })
.<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =></span> {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">ppSound</span>?.<span class="hljs-title class_">PlaySoundPool</span>()
})
<span class="hljs-title class_">Text</span>(<span class="hljs-string">"事件信息:\n\n"</span> + <span class="hljs-variable language_">this</span>.<span class="hljs-property">ppSound</span>?.<span class="hljs-property">soundMsg</span>)
.<span class="hljs-title function_">fontSize</span>(<span class="hljs-number">12</span>)
.<span class="hljs-title function_">margin</span>(({ <span class="hljs-attr">top</span>: <span class="hljs-number">10</span> }))
.<span class="hljs-title function_">fontColor</span>((<span class="hljs-string">'#aaaaaa'</span>))
.<span class="hljs-title function_">width</span>(<span class="hljs-string">'90%'</span>)
.<span class="hljs-title function_">backgroundColor</span>(<span class="hljs-string">'#333333'</span>)
.<span class="hljs-title function_">borderRadius</span>(<span class="hljs-number">5</span>)
.<span class="hljs-title function_">padding</span>(<span class="hljs-number">5</span>)
}
.<span class="hljs-title function_">backgroundColor</span>(<span class="hljs-string">'#444444'</span>)
.<span class="hljs-title function_">width</span>(<span class="hljs-string">'100%'</span>)
.<span class="hljs-title function_">height</span>(<span class="hljs-string">'100%'</span>)
}
}
@ObservedV2
class soundHelper {
soundPool: media.SoundPool | undefined
streamId: number = 0
soundId: number = 0
private fileSize: number = -1;
private fd: number = 0;
private rowFile: string = “”
//soundMsg仅在本例子中使用,为了将调试信息加载到页面。
@Trace soundMsg: string = “”
// audioRenderInfo中的参数usage取值为STREAM_USAGE_UNKNOWN,STREAM_USAGE_MUSIC,STREAM_USAGE_MOVIE,
// STREAM_USAGE_AUDIOBOOK时,SoundPool播放短音时为混音模式,不会打断其他音频播放。
audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags: 1
}
playParameters: media.PlayParameters = {
loop: 0, // 循环1次
rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, // 正常倍速
leftVolume: 1, // range = 0.0-1.0
rightVolume: 1, // range = 0.0-1.0
priority: 0, // 最低优先级
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// onCreated: 在 soundPool创建成功时调用
// 注册 loadComplete、playFinished等事件应当在此函数内。
// 可在UI的 aboutToAppear() 中重写此函数
onCreated: () => void = () => {
console.log(‘Created!’)
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
//addSoundMsg: 仅在本例子中使用,为了将调试信息加载到页面。
addSoundMsg(msg: string) {
this.soundMsg = ‘\n @’ + (new Date()).toLocaleString() + ‘\n’ + msg + this.soundMsg
}
async create() {
//创建soundPool实例
media.createSoundPool(20, this.audioRendererInfo).then(async (soundpool_: media.SoundPool) => {
if (soundpool_ != null) {
this.addSoundMsg(成功创建 soundPool
)
console.info(‘成功创建 soundPool’);
this.soundPool = soundpool_;
this.onCreated(); //调用 onCreated函数。关键步骤之一
} else {
console.error(‘创建 soundPool 失败!’);
this.addSoundMsg(创建 soundPool 失败!
)
}
}).catch((error: BusinessError) => {
console.error(soundpool catchCallback, error message:<span class="hljs-subst">${error.message}</span>
);
});
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
//loadSoundRawFile:加载声音文件。此函数指定的 soundFileName 应当在 resources/rowfile 下
async loadSoundRawFile(soundFileName: string) {
this.rowFile = soundFileName
this.addSoundMsg("即将加载声音文件 : " + this.rowFile)
// 加载音频资源
let fileDescriptor = await getContext(this).resourceManager.getRawFd(soundFileName);
//this.soundMsg = soundHelper:fd:${fileDescriptor.fd} \n
+ this.soundMsg
this.soundPool?.load(fileDescriptor.fd, fileDescriptor.offset, fileDescriptor.length).then((soundid: number) => {
this.soundId = soundid
this.addSoundMsg(成功加载声音文件 : <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.rowFile}</span> soundid:<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.soundId}</span>
)
})
}
//设置错误类型监听
setErrorCallback() {
this.soundPool?.on(‘error’, (error: BusinessError) => {
this.addSoundMsg(发生错误: <span class="hljs-subst">${error.message}</span>
)
console.info(发生错误: <span class="hljs-subst">${error.message}</span>
);
})
}
//播放声音文件
async PlaySoundPool() {
this.addSoundMsg(即将播放声音,soundid:<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.soundId}</span>
)
this.soundPool?.play(this.soundId, this.playParameters, (error, streamID: number) => {
if (error) {
this.addSoundMsg(播放声音错误:<span class="hljs-subst">${error.code}</span>-><span class="hljs-subst">${error.message}</span>
)
console.info(播放声音错误:<span class="hljs-subst">${error.code}</span>-><span class="hljs-subst">${error.message}</span>
)
} else {
this.streamId = streamID;
this.addSoundMsg(成功播放声音,streamid:<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.streamId}</span>
)
console.info(成功播放声音,streamid:<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.streamId}</span>
);
}
});
}
}
更多关于HarmonyOS 鸿蒙Next 搞定了SoundPool,有了音效!做了小封装。开心。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS 鸿蒙Next 搞定了SoundPool,有了音效!做了小封装。开心。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
playParameters中有个参数,可以控制循环次数
另外好像只支持播放5秒钟短音频
要想一直播放,可能需要用到 background task kit。也可以用set TimeOut函数,但比较麻烦些。
找HarmonyOS工作还需要会Flutter技术的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17
恭喜你在HarmonyOS 鸿蒙Next上成功搞定SoundPool并实现了音效的小封装,这是一个非常实用的功能提升。
在HarmonyOS中,SoundPool主要用于播放较短的音频文件,如游戏中的音效、按钮点击声等。它提供了灵活的音频管理功能,允许你同时播放多个音频流,并控制每个音频流的音量、播放速度等属性。
你提到的小封装,很可能是对SoundPool的使用进行了一层封装,使得在项目中调用音效更加简便和统一。这样的封装有助于减少重复代码,提高开发效率,同时也便于后续的维护和升级。
值得注意的是,虽然你已经实现了音效的播放,但还需要确保音频文件的格式、大小等符合HarmonyOS的要求,以避免出现播放异常或性能问题。此外,对于音频资源的管理也是非常重要的,要避免内存泄漏和音频资源的浪费。
如果在后续的开发过程中遇到关于SoundPool的更多问题,比如音频播放异常、资源加载失败等,你可以查阅HarmonyOS的官方文档或相关开发社区获取更多的帮助。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html 。希望你在HarmonyOS的开发之路上越走越远,取得更多的成果!