开天辟地 HarmonyOS(鸿蒙) - 媒体: SoundPool(音效播放器)

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 媒体: SoundPool(音效播放器)

示例如下:

pages\media\SoundPoolDemo.ets

/*
 * SoundPool - 音效播放器,用于低时延播放短音频(要求音频解码后在 1MB 以内)
 *
 * 大致的流程为:
 * 1、创建 SoundPool 实例,并监听相关事件
 * 2、加载指定的短音频文件,拿到对应的 soundId
 * 3、根据 soundId 播放短音频(如果指定的 soundId 正在播放,则会将其停止,然后重新播放),并拿到对应的 streamId
 * 4、根据 streamId 停止短音频的播放,根据 soundId 卸载短音频文件
 */

import { TitleBar } from '../TitleBar'
import { BusinessError } from '@kit.BasicServicesKit'
import { media } from '@kit.MediaKit'
import { LengthMetrics } from '@kit.ArkUI'
import { fileIo as fs } from '@kit.CoreFileKit';
import { audio } from '@kit.AudioKit';

@Entry
@Component
struct SoundPoolDemo {

  build() {
    Column({ space: 10 }) {
      TitleBar()
      Tabs() {
        TabContent() { MySample1() }.tabBar('基础').align(Alignment.Top)
      }
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .layoutWeight(1)
    }
  }
}

@Component
struct MySample1 {

  @State message: string = ""
  soundIdList: number[] = []
  streamIdList: Set<number> = new Set<number>()

  /*
   * SoundPool - 音效播放器
   *   media.createSoundPool() - 创建 SoundPool 对象
   *     maxStreams - 音频池内的最大的音频流的数量(1 - 32 之间)
   *     audioRenderInfo - 音频渲染器的信息
   *       usage - 音频流的类型,写死 audio.StreamUsage.STREAM_USAGE_MUSIC 即可
   *       rendererFlags - 音频渲染器标志,写死 0 即可
   *     注:api 18 或以上,一个进程最多可以创建 128 个 SoundPool 实例;api 18 以下,一个进程只能创建 1 个 SoundPool 实例
   *   on('loadComplete')/off('loadComplete') - 短音频文件加载完成时的回调
   *   on('playFinished')/off('playFinished') - 短音频播放完成时的回调
   *   on('playFinishedWithStreamId')/off('playFinishedWithStreamId')/ - 短音频播放完成时的回调,可以从回调参数中拿到 streamId
   *     ‘playFinishedWithStreamId’ 仅在 api 18 或以上有效
   *     如果 ‘playFinished’ 和 ‘playFinishedWithStreamId’ 都监听了,则只会触发 ‘playFinishedWithStreamId’
   *   on('error') - 发生异常时的回调
   *   load() - 通过 fd:// 协议的方式或 FileDescriptor 的方式加载短音频文件,返回的是 soundId
   *   play() - 根据 soundId 播放指定的短音频
   *     soundID - 需要播放的 soundId
   *     params - 播放参数(一个 PlayParameters 对象)
   *       loop - 循环次数(比如设置为 2 时,则代表一共播放 3 次)
   *       rate - 播放倍速(AudioRendererRate 枚举)
   *         RENDER_RATE_NORMAL - 正常倍速
   *         RENDER_RATE_DOUBLE - 2 倍速
   *         RENDER_RATE_HALF - 0.5 倍速
   *       leftVolume - 左声道音量(0 - 1 之间)
   *       rightVolume - 右声道音量(0 - 1 之间)
   *       priority - 优先级(0 代表最低优先级)
   *     callback - 从此回调中可以拿到 streamId
   *   stop() - 停止指定的 streamId 的播放
   *   unload() - 卸载指定的 soundId 的文件
   *   release() - 清理资源
   */
  soundPool: media.SoundPool | undefined

  async aboutToAppear() {
    await this.initSoundPool()
    await this.loadSound()
  }

  // 创建 SoundPool 实例,并监听相关事件
  async initSoundPool() {
    // 如果需要的话,可以通过 canIUse 判断当前设备是否支持 SoundPool
    let supported = canIUse('SystemCapability.Multimedia.Media.SoundPool')
    this.message += `是否支持 SoundPool: ${supported}\n`

    this.soundPool = await media.createSoundPool(6, {
      usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
      rendererFlags: 0
    })

    this.soundPool.on('loadComplete', (soundId: number) => {
      this.message += `loadComplete soundId:${soundId}\n`
    });

    this.soundPool.on('playFinished', () => {
      this.message += `playFinished\n`
    });
    /*
    this.soundPool.on('playFinishedWithStreamId', (streamId: number) => {
      this.message += `playFinishedWithStreamId streamId:${streamId}\n`
    });
    */
    this.soundPool.on('error', (error: BusinessError) => {
      this.message += `error errCode:${error.code}, errMessage:${error.message}\n`
    });
  }

  // 加载短音频文件
  async loadSound() {
    /*
    // 下面的注销的示例用于演示,如何通过 fd:// 协议的方式加载指定的沙箱地址中的短音频文件
    for (let i = 0; i < 6; i++) {
      // 从 rawfile 中复制文件到指定的沙箱地址
      let filePath = getContext(this).getApplicationContext().filesDir + `/${i +  1}.mp3`
      let myBuffer: ArrayBufferLike = (await getContext(this).resourceManager.getRawFileContent(`audio/${i +  1}.mp3`)).buffer
      let file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
      await fs.write(file.fd, myBuffer)
      // 加载指定的沙箱地址中的短音频文件(通过 fd:// 协议的方式)
      let fdPath = 'fd://' + (file.fd).toString()
      let soundId = await this.soundPool?.load(fdPath)
      if (soundId) {
        this.soundIdList.push(soundId)
      }
      await fs.close(file)
    }
    */

    // 加载指定的 rawfile 中的短音频文件(通过 FileDescriptor 的方式)
    for (let i = 0; i < 6; i++) {
      let fileDescriptor = await getContext(this).resourceManager.getRawFd(`audio/${i +  1}.mp3`)
      let soundId = await this.soundPool?.load(fileDescriptor.fd,  fileDescriptor.offset,  fileDescriptor.length)
      if (soundId) {
        this.soundIdList.push(soundId)
      }
    }
  }

  // 播放指定的短音频
  playSound(soundId: number) {
    // 短音频的播放参数
    let playParameters: media.PlayParameters = {
      loop: 0, // 播放 1 此
      rate: audio.AudioRendererRate.RENDER_RATE_NORMAL,
      leftVolume: 0.5,
      rightVolume: 0.5,
      priority: 100,
    };

    // 根据 soundId 播放短音频(如果指定的 soundId 正在播放,则会将其停止,然后重新播放),并拿到对应的 streamId
    this.soundPool?.play(soundId, playParameters, (error, streamId: number) => {
      this.message += `play soundId:${soundId}, streamId:${streamId}\n`
      this.streamIdList.add(streamId)

      /*
      // 设置指定 streamId 的播放参数(播放参数与上面的 PlayParameters 是一样的)
      this.soundPool?.setLoop(streamId, 0)
      this.soundPool?.setRate(streamId, audio.AudioRendererRate.RENDER_RATE_NORMAL)
      this.soundPool?.setVolume(streamId, 0.5, 0.5)
      this.soundPool?.setPriority(streamId, 100)
      */
    });
  }

  // 清理资源
  async release() {
    this.soundPool?.off('loadComplete')
    this.soundPool?.off('playFinished')
    /*
    this.soundPool.on('playFinishedWithStreamId')
    */
    this.soundPool?.off('error')
    for (let streamId of this.streamIdList) {
      await this.soundPool?.stop(streamId)
    }
    for (let soundId of this.soundIdList) {
      await this.soundPool?.unload(soundId)
    }
    await this.soundPool?.release()
  }

  async aboutToDisappear() {
    await this.release()
  }

  build() {
    Column({ space: 10 }) {

      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceAround, space: { main: LengthMetrics.vp(10), cross: LengthMetrics.vp(10) } }) {
        Button("play 0").onClick(() => {
          this.playSound(this.soundIdList[0])
        })
        Button("play 1").onClick(() => {
          this.playSound(this.soundIdList[1])
        })
        Button("play 2").onClick(() => {
          this.playSound(this.soundIdList[2])
        })
        Button("play 3").onClick(() => {
          this.playSound(this.soundIdList[3])
        })
        Button("play 4").onClick(() => {
          this.playSound(this.soundIdList[4])
        })
        Button("play 5").onClick(() => {
          this.playSound(this.soundIdList[5])
        })
      }

      Text(this.message)
    }
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

posted @ 2025-05-27 14:46  webabcd  阅读(18)  评论(0)    收藏  举报