HarmonyOS NEXT鸿蒙开发:使用AVPlayer开发音频播放功能(播放声音)

说明

官方指南
官方API

本文讲解使用AVPlayer实现播放声音

注意

不能在 previewer 播放声音
只能在 模拟器 播放声音

播放流程

  • 创建AVPlayer
  • 设置播放资源
  • 设置播放参数(音量/倍速/焦点模式)
  • 播放控制(播放/暂停/跳转/停止)
  • 重置
  • 销毁资源

播放状态

通过播放状态,可以知道当前播放器是在 播放 或 播放结束 等,具体如下:

  • idle:闲置状态,AVPlayer刚被创建 createAVPlayer() 或者调用了 reset() 方法之后,进入 Idle 状态。首次创建 createAVPlayer() ,所有属性都为默认值。调用 reset() 方法,url 或 fdSrc 或 dataSrc 属性及loop 属性会被重置,其他用户设置的属性将被保留。

  • initialized:资源初始化,在Idle 状态设置 url 或 fdSrc 属性,AVPlayer会进入 initialized 状态,此时可以配置窗口、音频等静态属性。

  • prepared:已准备状态,在 initialized 状态调用 prepare() 方法,AVPlayer会进入 prepared 状态,此时播放引擎的资源已准备就绪。

  • playing:正在播放状态,在 prepared/paused/completed 状态调用 play() 方法,AVPlayer会进入 playing 状态。

  • paused:暂停状态,在 playing 状态调用 pause() 方法,AVPlayer会进入 paused 状态。

  • completed:播放至结尾状态,当媒体资源播放至结尾时,如果用户未设置循环播放(loop = true),AVPlayer会进入 completed 状态,此时调用 play() 会进入 playing 状态和重播,调用 stop() 会进入 stopped 状态。

  • stopped:停止状态,在prepared/playing/paused/completed 状态调用 stop() 方法,AVPlayer会进入 stopped 状态,此时播放引擎只会保留属性,但会释放内存资源,可以调用 prepare() 重新准备,也可以调用 reset() 重置,或者调用 release() 彻底销毁。

  • released:销毁状态,销毁与当前AVPlayer关联的播放引擎,无法再进行状态转换,调用 release() 方法后,会进入released 状态,结束流程。

  • error:错误状态,当播放引擎发生不可逆的错误(详见媒体错误码),则会转换至当前状态,可以调用 reset() 重置,也可以调用 release() 销毁重建。
    注意: 区分error状态和 on(‘error’) :

    1. 进入 error 状态时,会触发 on('error') 监听事件,可以通过 on('error') 事件获取详细错误信息;
    2. 处于error状态时,播放服务进入不可播控的状态,要求客户端设计容错机制,使用 reset() 重置或者 release() 销毁重建;
    3. 如果客户端收到 on('error'),但未进入error 状态:
      • 原因1: 客户端未按状态机调用API或传入参数错误,被AVPlayer拦截提醒,需要客户端调整代码逻辑;
      • 原因2:播放过程发现码流问题,导致容器、解码短暂异常,不影响连续播放和播控操作的,不需要客户端设计容错机制。

获取状态

  • 通过AVPlayer的 state 属性主动获取当前状态

  • 使用on('stateChange') 方法监听状态变化

创建对象

接口1

异步方式创建音视频播放实例,通过注册回调函数获取返回值。

createAVPlayer(callback: AsyncCallback<AVPlayer>): void
参数
  • callback:类型:AsyncCallback<AVPlayer>,必填,回调函数。异步返回AVPlayer实例,失败时返回 null。可用于音视频播放。

接口2

异步方式创建音视频播放实例,通过Promise获取返回值

createAVPlayer(): Promise<AVPlayer>
返回值

Promise<AVPlayer>:Promise 对象。异步返回AVPlayer实例,失败时返回 null。可用于音视频播放。

例子

public async  initAudioPlayer() {
  this.avPlayer = await media.createAVPlayer()
}

属性

指定音频-url

url

类型:string,媒体URL,只允许在idle状态下设置。

应用场景:网络播放

支持的视频格式(mp4、mpeg-ts、mkv)。

支持的音频格式(m4a、aac、mp3、ogg、wav、flac、amr)。

支持路径示例:

  1. fd类型播放:fd://xx。

  2. http网络播放: http://xx。

  3. https网络播放: https://xx。

  4. hls网络播放路径:http://xx或者https://xx。

说明:

  • 设置网络播放路径,需声明权限:ohos.permission.INTERNET,相关错误码: 201。

指定音频-fdSrc

AVFileDescriptor

媒体文件描述,只允许在idle状态下设置。

使用场景:应用中的媒体资源放在 media 文件夹中

支持的视频格式(mp4、mpeg-ts、mkv)。

支持的音频格式(m4a、aac、mp3、ogg、wav、flac、amr)。

指定音频-dataSrc

dataSrc

类型:AVDataSrcDescriptor

流式媒体资源描述,只允许在idle状态下设置。

使用场景:应用播放从远端下载到本地的文件,在应用未下载完整音视频资源时,提前播放已获取的资源文件。

支持的视频格式(mp4、mpeg-ts、mkv)。

支持的音频格式(m4a、aac、mp3、ogg、wav、flac、amr)。

使用示例:

假设用户正在从远端服务器获取音视频媒体文件,希望下载到本地的同时播放已经下载好的部分:

  1. 用户需要获取媒体文件的总大小size(单位为字节),获取不到时设置为-1。

  2. 用户需要实现回调函数func用于填写数据,如果size = -1,则func形式为:func(buffer: ArrayBuffer, length: number),此时播放器只会按照顺序获取数据;否则func形式为:func(buffer: ArrayBuffer, length: number, pos: number),播放器会按需跳转并获取数据。

  3. 用户设置AVDataSrcDescriptor {fileSize = size, callback = func}。

注意事项:

如果播放的是mp4/m4a格式用户需要保证moov字段(媒体信息字段)在mdat字段(媒体数据字段)之前,或者moov之前的字段小于10M,否则会导致解析失败无法播放。

事件

监听错误事件(关键)

监听AVPlayer的错误事件,如果报错,需要通过 reset() 或者 release() 退出播放操作,否则无法继续播放

on(type: 'error', callback: ErrorCallback): void

例子

this.avPlayer.on('error', (err) => {
  console.error(TAG,`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`,err);
  // 调用reset重置资源,触发idle状态
  this.avPlayer.reset();
})

监听播放状态机AVPlayerState

在不同的状态,需要调用相应的方法,否则不执行

on(type: 'stateChange', callback: OnAVPlayerStateChangeHandle): void
参数
  • type:类型:string,必填,状态机切换事件回调类型,支持的事件:stateChange,用户操作和系统都会触发此事件。
  • callback:类型:OnAVPlayerStateChangeHandle,必填,状态机切换事件回调方法。

例子

// 状态机变化回调函数
this.avPlayer.on('stateChange', async (state, reason) => {
  switch (state) {
    case 'initialized':
      // console.info(TAG,'资源初始化完成');
      // 资源初始化完成,开始准备文件
      this.avPlayer.prepare();
      break;
    case 'prepared':
      // console.info(TAG,'资源准备完成');
      // console.log("播放时间1:", (Date.now() - this.time )/1000)
      // 设置音量
      this.avPlayer.setVolume(this.volume)
      // 资源准备完成,开始准备文件
      this.avPlayer.play();
      // console.log("播放时间1--:", (Date.now() - this.time )/1000)
      break;
    case 'completed':
      // console.info(TAG,'播放完成');
      // console.log("播放时间2:", (Date.now() - this.time )/1000)
      this.avPlayer.stop();
      break;
    case 'stopped': // stop接口成功调用后触发该状态机上报
      // console.info(TAG,'停止播放');
      // console.log("播放时间3:", (Date.now() - this.time )/1000)
      // 调用reset接口初始化avplayer状态,必须执行,否则第二次播放时会报错
      this.avPlayer.reset();
      break;
  }
})

方法

准备(接口1)

准备播放音频/视频,需在 stateChange 事件成功触发 initialized状态后,才能调用。使用 callback 方式异步获取返回值。

prepare(callback: AsyncCallback<void>): void
参数
  • callback:类型:AsyncCallback<void>,必填,准备播放的回调方法

准备(接口2)

准备播放音频/视频,需在 stateChange 事件成功触发至 initialized 状态后,才能调用。通过 Promise 获取返回值。

prepare(): Promise<void>
返回值

类型: Promise<void>:准备播放的Promise返回值。

播放(接口1)

开始播放音视频资源,只能在 prepared/paused/completed 状态调用。使用 callback 方式异步获取返回值。

play(callback: AsyncCallback<void>): void
参数
  • callback:类型:AsyncCallback<void>,必填,开始播放的回调方法。

播放(接口1)

开始播放音视频资源,只能在 prepared/paused/completed 状态调用。通过Promise获取返回值。

play(): Promise<void>
返回值

类型:Promise<void>:开始播放的Promise返回值。

停止播放(接口1)

停止播放音视频资源,只能在 prepared/playing/paused/completed 状态调用。使用 callback 方式异步获取返回值。

stop(callback: AsyncCallback<void>): void
参数
  • callback:类型:AsyncCallback<void>,必填,停止播放的回调方法。

停止播放(接口2)

停止播放音视频资源,只能在 prepared/playing/paused/completed 状态调用。通过Promise获取返回值。

stop(): Promise<void>
返回值

类型:Promise<void>:停止播放的Promise返回值。

重置(接口1)(重要)

重置播放,只能在 initialized/prepared/playing/paused/completed/stopped/error 状态调用。使用 callback 方式异步获取返回值。

注意:报错后,必须调用该方法,否则不能再继续播放

reset(callback: AsyncCallback<void>): void
参数
  • callback:类型:AsyncCallback<void>,必填,重置播放的回调方法。

重置(接口2)(重要)

重置播放,只能在 initialized/prepared/playing/paused/completed/stopped/error 状态调用。通过 Promise 获取返回值。

注意:报错后,必须调用该方法,否则不能再继续播放

reset(): Promise<void>
返回值

类型:Promise<void>,重置播放的Promise返回值。

设置音量

设置媒体播放音量,只能在prepared/playing/paused/completed 状态调用,可以通过volumeChange 事件确认是否生效。

setVolume(volume: number): void
参数
  • volume:类型:number,必填,指定的相对音量大小,取值范围为 [0.00-1.00],1表示最大音量,即 100%。

案例

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


const TAG = 'AVPlayerUtil';

export default class AVPlayerUtil {
  avPlayer !: media.AVPlayer;
  // time = Date.now()
  context = getContext(this) as common.UIAbilityContext;
  /**
   * 音量
   */
  volume:number = 100

  constructor() {
    this.initAudioPlayer();
  }
  public async  initAudioPlayer() {
    this.avPlayer = await media.createAVPlayer()

    this.setAVPlayerCallback();
  }

  private setAVPlayerCallback() {
    // error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
    this.avPlayer.on('error', (err) => {
      console.error(TAG,`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`,err);
      // 调用reset重置资源,触发idle状态
      this.avPlayer.reset();
    })
    // 状态机变化回调函数
    this.avPlayer.on('stateChange', async (state, reason) => {
      switch (state) {
        case 'initialized':
          // console.info(TAG,'资源初始化完成');
          // 资源初始化完成,开始准备文件
          this.avPlayer.prepare();
          break;
        case 'prepared':
          // console.info(TAG,'资源准备完成');
          // console.log("播放时间1:", (Date.now() - this.time )/1000)
          // 设置音量
          this.avPlayer.setVolume(this.volume)
          // 资源准备完成,开始准备文件
          this.avPlayer.play();
          // console.log("播放时间1--:", (Date.now() - this.time )/1000)
          break;
        case 'completed':
          // console.info(TAG,'播放完成');
          // console.log("播放时间2:", (Date.now() - this.time )/1000)
          this.avPlayer.stop();
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          // console.info(TAG,'停止播放');
          // console.log("播放时间3:", (Date.now() - this.time )/1000)
          // 调用reset接口初始化avplayer状态,必须执行,否则第二次播放时会报错
          this.avPlayer.reset();
          break;
      }
    })
  }
  /**
   * 设置音量,最大值为1,即:100%
   * @param volume
   */
  public setVolume(volume:number):void{
    this.volume = volume
  }
  /**
   * 音频文件放在 rawfile 文件夹中
   * @param fileName
   */
  public async play(fileName:string) {
    // this.time = Date.now()
    // 停止
    await this.avPlayer.reset()
    try {
      if(this.avPlayer) {
        const fileDescriptor = await this.context.resourceManager.getRawFd(fileName)
        this.avPlayer.fdSrc = fileDescriptor;
      }
    }catch (err){
      console.error(TAG, `播放错误,错误码:${err.code},信息:${err.message}`)
    }
  }

}

调用:

player = new AudioPlayer()
// 设置音量
this.player.setVolume(1)

// 1.mp3在 rawfile 文件夹中
this.player.play("1.mp3")

原文出处:/show_1IX8k35PYTqc.html