说明
当应用开发时,经常需要使用一些急促简短的音效(如相机快门音效、系统通知音效等),此时建议调用 SoundPool,可以加载 多个音频资源到内存,进行管理与播放,实现 一次加载,多次 低时延 播放。
与 Android 的 SoundPool 类似
提示:在 模拟器 上测试,第一次播放仍然有延时,与 AVPlayer 延时差不多
限制
SoundPool当前支持播放 1MB以下 的音频资源,大小超过1MB的长音频将 截取1MB大小 数据进行播放。
前提
只能在模拟器、真机使用,不能在预览模式下使用
开发过程
- 创建SoundPool实例
- 加载音频资源(包括资源的解封装与解码:解码格式参考音频解码支持)
- 设置播放参数(循环模式/播放优先级等)
- 播放控制(播放/停止)
- 释放资源
监听状态
通过监听方法检查 当前 播放状态,并按照一定顺序调用接口,执行对应操作,否则系统可能会 抛出异常 或 生成其他未定义的行为
导入模块
import { audio } from '@kit.AudioKit';
import { media } from '@kit.MediaKit';
创建 SoundPool 实例
调用 @kit.MediaKit 的 createSoundPool 方法创建 SoundPool 实例
使用 callback 方式异步获取返回值
接口1
createSoundPool(maxStreams: number, audioRenderInfo: audio.AudioRendererInfo, callback: AsyncCallback<SoundPool>): void
参数:
maxStreams:类型:number,必填,soundPool实例的最大播放的流数,理解:同时可播放的音频数量,例子:设置为1,同一时刻只能播放一个音频,如果第一个音频没有播放完,就播放第二个音频,会打断第一个音频
audioRenderInfo:类型:audio.AudioRendererInfo,必填,音频播放参数信息。本文
audioRenderInfo中的参数usage取值为 STREAM_USAGE_MUSIC 时,SoundPool播放短音时为混音模式,不会打断其他音频播放。详见官网callback:类型:
AsyncCallback<SoundPool>,必填,回调函数。异步返回SoundPool实例,失败时返回null。
代码
import { media } from '@kit.MediaKit';
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
let soundPool: media.SoundPool;
// audioRenderInfo中的参数usage取值为STREAM_USAGE_UNKNOWN,STREAM_USAGE_MUSIC,STREAM_USAGE_MOVIE,
// STREAM_USAGE_AUDIOBOOK时,SoundPool播放短音时为混音模式,不会打断其他音频播放。
let audioRendererInfo: audio.AudioRendererInfo = {
usage : audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags : 0
}
media.createSoundPool(5, audioRendererInfo).then((soundpool_: media.SoundPool) => {
if (soundpool_ != null) {
soundPool = soundpool_;
console.info('create SoundPool success');
} else {
console.error('create SoundPool fail');
}
}).catch((error: BusinessError) => {
console.error(`soundpool catchCallback, error message:${error.message}`);
});
接口2
创建音频池实例,通过Promise获取返回值
createSoundPool(maxStreams: number, audioRenderInfo: audio.AudioRendererInfo): Promise<SoundPool>
例子
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
let soundPool: media.SoundPool;
let audioRendererInfo: audio.AudioRendererInfo = {
usage : audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags : 0
}
media.createSoundPool(5, audioRendererInfo).then((soundpool_: media.SoundPool) => {
if (soundpool_ != null) {
soundPool = soundpool_;
console.info('Succceeded in creating SoundPool');
} else {
console.error('Failed to create SoundPool');
}
}, (error: BusinessError) => {
console.error(`soundpool catchCallback, error message:${error.message}`);
});
监听事件
通过下面方法可以监听事件,如:错误事件、加载完成事件、播放完成事件
on(type: string, callback: Callback<number>): void
监听错误事件
监听SoundPool的错误事件,该事件仅用于错误提示。
如:加载音频文件错误时,会报错
代码
this.soundPool.on('error', (error: BusinessError) => {
console.error(TAG,`错误-1,${error.code},${error.message}` );
})
加载音频资源
从加载音频所在的路径,分两类:
- 不支持加载
rawfile目录资源 - 可以加载
rawfile目录资源
本文只介绍 可以加载 rawfile 目录资源的接口
支持加载 rawfile 目录资源
接口1
加载音频资源。使用Promise方式异步获取资源ID
load(fd: number, offset: number, length: number): Promise<number>
参数:
- fd:类型:number,必填项,资源句柄,通过
resourceManager.getRawFileDescriptor获取。 - offset:类型:number,必填项,资源偏移量,通过
resourceManager.getRawFileDescriptor获取。 - length:类型:number,必填项,资源长度,通过
resourceManager.getRawFileDescriptor获取 - callback:类型:
AsyncCallback<number>,必填项,获取回调的 soundID,有效值大于0。
返回值: Promise<number>,以Promise方式获取返回的soundID,有效值大于0。
例子
try {
const fileDescriptor = getContext().resourceManager.getRawFdSync(filePath)
this.soundPool.load(fileDescriptor.fd,fileDescriptor.offset,fileDescriptor.length).then((soundId: number) => {
console.info(TAG,'load完成,soundId:',soundId);
}).catch((err: BusinessError) => {
console.error(TAG,`soundPool ${err.code} ${err.message}` );
})
}catch (err){
console.error(TAG, `play - err ${err}`)
}
soundID
获取soundID:load() 方法返回
作用:用于 play() 播放音频
play播放
播放音频资源。使用callback方式异步获取音频流streamID
play(soundID: number, callback: AsyncCallback<number>): void
参数:
- soundID:类型:
number,必填项,资源ID,通过load方法获取。 - callback:类型:
AsyncCallback<number>,必填项,获取回调的音频流 streamID,有效值大于0。
streamID
获取soundID:play() 方法返回
作用:用于 设置循环播放次数,优先级、音量等
例子
// 设置循环播放次数
await this.soundPool.setLoop(this.streamId, 1); // 播放3次
// 设置对应流的优先级
await this.soundPool.setPriority(this.streamId, 1);
// 设置音量
await this.soundPool.setVolume(this.streamId, 0.5, 0.5);
案例
SoundPoolUtil.ets文件
将 SoundPool 封装到该类中
import { audio } from '@kit.AudioKit';
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
const TAG = "SoundPoolUtil"
export default class SoundPoolUtil {
soundPool !: media.SoundPool;
streamId: number = 0;
soundIds: number[] = [];
audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 播放短音时为混音模式,不会打断其他音频播放
rendererFlags: 0
}
playParameters: media.PlayParameters = {
loop: 0, // 不循环
rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, // 正常倍速
leftVolume: 0.5, // range = 0.0-1.0
rightVolume: 0.5, // range = 0.0-1.0
priority: 0, // 最低优先级
}
constructor() {
this.create()
}
private async create() {
//创建soundPool实例
this.soundPool = await media.createSoundPool(5, this.audioRendererInfo);
//注册监听
// this.loadCallback();
// this.finishPlayCallback();
this.setErrorCallback();
this.load("a_muyu1.mp3")
this.load("spirit-berrylink.mp3")
}
private async load(filePath:string) {
try {
const fileDescriptor = getContext().resourceManager.getRawFdSync(filePath)
this.soundPool.load(fileDescriptor.fd,fileDescriptor.offset,fileDescriptor.length).then((soundId: number) => {
// console.info(TAG,'load完成,soundId:',soundId);
this.soundIds.push(soundId)
}).catch((err: BusinessError) => {
console.error(TAG,`soundPool ${err.code} ${err.message}` );
})
}catch (err){
console.error(TAG, `play - err ${err}`)
}
}
// private loadCallback() {
// // 加载完成回调
// this.soundPool.on('loadComplete', (soundId_: number) => {
// console.info(TAG,'loadComplete--load完成, soundId: ' + soundId_);
// })
// }
//设置播放完成监听
// private finishPlayCallback() {
// // 播放完成回调
// this.soundPool.on('playFinished', () => {
// // console.info(TAG,"播放完成");
// // 可进行下次播放
// })
// }
//设置错误类型监听
private setErrorCallback() {
this.soundPool.on('error', (error: BusinessError) => {
console.error(TAG,`错误-1,${error.code},${error.message}` );
})
}
public async play(index:number) {
// 开始播放,这边play也可带播放播放的参数PlayParameters,请在音频资源加载完毕,即收到loadComplete回调之后再执行play操作
this.soundPool.play(this.soundIds[index], (error, streamID: number) => {
if (error) {
console.error(TAG,`播放失败: errCode is ${error.code}, errMessage is ${error.message}`)
} else {
this.streamId = streamID;
// console.info(TAG,'播放成功,soundid:' + this.streamId);
}
});
// 设置循环播放次数
// await this.soundPool.setLoop(this.streamId, 1); // 播放3次
// 设置对应流的优先级
// await this.soundPool.setPriority(this.streamId, 1);
// 设置音量
// await this.soundPool.setVolume(this.streamId, 0.5, 0.5);
}
public async release() {
// 终止指定流的播放
await this.soundPool.stop(this.streamId);
// 卸载音频资源
for(let item of this.soundIds) {
await this.soundPool.unload(item);
}
//关闭监听
this.setOffCallback();
// 释放SoundPool
await this.soundPool.release();
}
//关闭监听
private setOffCallback() {
this.soundPool.off('loadComplete');
this.soundPool.off('playFinished');
this.soundPool.off('error');
}
}
界面
import SoundPoolUtil from './SoundPoolUtil'
const soundPoolUtil = new SoundPoolUtil()
@Entry
@Component
struct SoundPoolPage {
build() {
Column() {
Text("播放1")
.id('SoundPoolPageHelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick( e=>{
soundPoolUtil.play(0)
})
Text("播放2")
.id('SoundPoolPageHelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick( e=>{
soundPoolUtil.play(1)
})
}
.height('100%')
.width('100%')
}
}