HarmonyOS NEXT鸿蒙开发:使用SoundPool播放音频

说明

官网指南

当应用开发时,经常需要使用一些急促简短的音效(如相机快门音效、系统通知音效等),此时建议调用 SoundPool,可以加载 多个音频资源到内存,进行管理与播放,实现 一次加载,多次 低时延 播放。

与 Android 的 SoundPool 类似

提示:在 模拟器 上测试,第一次播放仍然有延时,与 AVPlayer 延时差不多

限制

SoundPool当前支持播放 1MB以下 的音频资源,大小超过1MB的长音频将 截取1MB大小 数据进行播放。

前提

只能在模拟器、真机使用,不能在预览模式下使用

开发过程

  1. 创建SoundPool实例
  2. 加载音频资源(包括资源的解封装与解码:解码格式参考音频解码支持)
  3. 设置播放参数(循环模式/播放优先级等)
  4. 播放控制(播放/停止)
  5. 释放资源

监听状态

通过监听方法检查 当前 播放状态,并按照一定顺序调用接口,执行对应操作,否则系统可能会 抛出异常 或 生成其他未定义的行为

导入模块

import { audio } from '@kit.AudioKit';
import { media } from '@kit.MediaKit';

创建 SoundPool 实例

官方API

调用 @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}` );
})

加载音频资源

官方API

从加载音频所在的路径,分两类:

  • 不支持加载 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%')
  }
}

原文出处:/show_1IX8kDuhlhBs.html