目录

元服务基于AVPlayer实现的短音频播放方案

问题现象

元服务中无法使用SoundPool和AudioRenderer接口,导致无法使用常规方式播放短音效(如按键声、提示音)。

背景知识

解决方案

在元服务中可以采用AVPlayer实现低延迟、可重播的短音频功能。

该解决方案的核心策略如下:

  1. 状态驻留:播放结束时不调用reset()或release(),使播放器保持在completed或prepared状态,避免重复IO加载开销。
  2. 即时重播:在触发播放前强制调用seek(0),既能处理播放完成后的重播,也能支持播放过程中的打断,从而实现连点播放音效。
  3. 预加载:在aboutToAppear生命周期中调用init,消除首次点击的加载延迟。
  4. 内存管理:在aboutToDisappear中调用release()释放播放器实例。

完整代码如下:

src/main/ets/pages/Index.ets:

import common from '@ohos.app.ability.common';
import { SoundManager } from './SoundManager';

@Entry
@Component
struct Index {
  private soundManager: SoundManager = new SoundManager();
  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  // 定义要播放的文件名
  private targetFileName: string = 'sample.mp3';

  // 显示正在播放的音效文件
  @State displayFileName: string = '';

  aboutToAppear() {
    // 初始化音频
    this.soundManager.init(this.context, this.targetFileName);

    // 初始化显示文案
    this.displayFileName = `正在播放 ${this.targetFileName}`;
  }

  aboutToDisappear() {
    this.soundManager.release();
  }

  build() {
    Column() {
      Text(this.displayFileName)
        .fontSize(12)
        .fontColor(Color.Gray)
        .margin({ bottom: 10 })

      Button('播放音效')
        .width(200)
        .height(50)
        .onClick(() => {
          this.soundManager.play();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

其中sample.mp3文件放置在src/main/resources/rawfile文件夹中,请按需替换为其他音频文件。

src/main/ets/pages/SoundManager.ets:

import media from '@ohos.multimedia.media';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';

export class SoundManager {
  private avPlayer: media.AVPlayer | undefined = undefined;
  private isPrepared: boolean = false;

  async init(context: common.UIAbilityContext, rawFileName: string) {
    try {
      this.avPlayer = await media.createAVPlayer();

      this.avPlayer.on('stateChange', (state: media.AVPlayerState) => {
        switch (state) {
          case 'initialized':
            this.avPlayer?.prepare();
            break;
          case 'prepared':
            this.isPrepared = true;
            console.info('AVPlayer: Prepared.');
            break;
          case 'completed':
            console.info('AVPlayer: Play completed.');
            // 不要在这里 stop 或 reset,保持 prepared/completed 状态
            // 这样下次点击 play 才能直接播放
            break;
          case 'error':
            console.error('AVPlayer: Error state.');
            break;
        }
      });

      let fileDescriptor = await context.resourceManager.getRawFd(rawFileName);
      this.avPlayer.fdSrc = {
        fd: fileDescriptor.fd,
        offset: fileDescriptor.offset,
        length: fileDescriptor.length
      };

    } catch (error) {
      console.error(`SoundManager Init Failed: ${(error as BusinessError).message}`);
    }
  }

  play() {
    if (!this.avPlayer || !this.isPrepared) {
      return;
    }

    // 支持打断播放和重复播放
    // 如果正在播放(playing) 或者 已经放完(completed),都把进度拉回 0
    if (this.avPlayer.state === 'playing' || this.avPlayer.state === 'completed') {
      this.avPlayer.seek(0); // 瞬间回到开头
    }

    // 只要不是 error 或 released,调用 play 都是安全的
    if (this.avPlayer.state !== 'playing') {
      this.avPlayer.play();
    }
  }

  release() {
    if (this.avPlayer) {
      this.avPlayer.release();
      this.avPlayer = undefined;
      this.isPrepared = false;
    }
  }
}