harmony 鸿蒙AVPlayer播放器开发指导

2023-02-03 浏览 (1916)

AVPlayer播放器开发指导

简介

AVPlayer主要工作是将Audio/Video媒体资源转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。

运作机制

该模块提供了播放状态变化示意图AVPlayerState、音频播放外部模块交互图和视频播放外部模块交互图。

图1 播放状态变化示意图

zh-ch_image_avplayer_state_machine

图2 音频播放外部模块交互图

zh-ch_image_avplayer_audio

说明:应用通过调用JS接口层提供的AVPlayer js接口实现相应功能时,框架层会通过Player Framework的播放服务解析成音频数据流,音频数据流经过软件解码后输出至Audio Framework的音频服务,由音频子系统输出至硬件接口层的音频HDI,实现音频播放功能。完整的音乐播放器工作需要:应用(应用适配)、Player Framework、Audio Framework、Audio HDI(驱动适配)共同实现。

注意:音频播放需要音频子系统配合

  1. 应用把url传递给AVPlayer JS。
  2. 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。

图3 视频播放外部模块交互图

zh-ch_image_avplayer_video

说明:应用通过调用JS接口层提供的AVPlayer js接口实现相应功能时,框架层会通过Player Framework的播放服务解析成单独的音频数据流和视频数据流,音频数据流经过软件解码后输出至Audio Framework的音频服务,由音频子系统输出至硬件接口层的音频HDI,实现音频播放功能。视频数据流经过硬件(推荐)/软件解码后输出至Graphic Framework的渲染服务(Renderer Service),由RS子系统输出至硬件接口层的显示HDI。完整的视频播放器工作需要:应用(应用适配)、XCompomemt组件、Player Framework、Graphic Framework、Audio Framework、Display HDI(驱动适配)和Audio HDI(驱动适配)共同实现。

注意:视频播放需要显示、音频、解码等多个子系统配合。

  1. 应用从Xcomponent组件获取surfaceID,获取方式
  2. 应用把url、surfaceID传递给AVPlayer JS。
  3. 播放服务把视频ES数据流输出给Codec HDI,解码获得视频帧(NV12/NV21/RGBA)。
  4. 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。
  5. 播放服务把视频帧(NV12/NV21/RGBA)输出给RS服务,RS服务输出给Display HDI。

兼容性说明

推荐使用主流的播放格式和主流分辨率,不建议开发者自制非常或者异常码流,以免产生无法播放、卡住、花屏等兼容性问题。若发生此类问题不会影响系统,退出码流播放即可。

主流的播放格式和主流分辨率如下:

视频容器规格规格描述分辨率
mp4视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3主流分辨率,如1080P/720P/480P/270P
mkv视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3主流分辨率,如1080P/720P/480P/270P
ts视频格式:H264/MPEG2/MPEG4 音频格式:AAC/MP3主流分辨率,如1080P/720P/480P/270P
webm视频格式:VP8 音频格式:VORBIS主流分辨率,如1080P/720P/480P/270P
音频容器规格规格描述
m4a音频格式:AAC
aac音频格式:AAC
mp3音频格式:MP3
ogg音频格式:VORBIS
wav音频格式:PCM

开发指导

详细API含义可参考:媒体服务API文档AVPlayer

播放流程说明

播放的全流程场景包含:创建实例,设置资源,设置窗口(视频),准备播放(获取轨道信息/音量/倍速/焦点模式/缩放模式/设置bitrates),播控(播放/暂停/Seek/音量/停止),重置资源,销毁播放

1:创建实例createAVPlayer(),AVPlayer初始化idle状态

2:设置业务需要的监听事件,搭配全流程场景使用

3:设置资源 url,AVPlayer进入initialized状态,此时可以设置视频窗口 surfaceId,支持的规格可参考:AVPlayer属性说明

4:准备播放 prepare(),AVPlayer进入prepared状态

5:视频播控:播放 play(),暂停 pause(),跳转 seek(),停止 stop() 等操作

6:重置资源 reset(),AVPlayer重新进入idle状态,允许更换资源 url

7:销毁播放 release(),AVPlayer进入released状态,退出播放

说明:

prepared/playing/paused/compeled 状态时,播放引擎处于工作状态,这需要占用系统较多的运行内存,当客户端暂时不使用播放器时,要求调用 reset() 或 release() 回收。

监听事件

事件类型说明
stateChange必要事件,监听播放器的状态机
error必要事件,监听播放器的错误信息
durationUpdate用于进度条,监听进度条长度,刷新资源时长
timeUpdate用于进度条,监听进度条当前位置,刷新当前时间
seekDone响应api调用,监听seek()请求完成情况
speedDone响应api调用,监听setSpeed()请求完成情况
volumeChange响应api调用,监听setVolume()请求完成情况
bitrateDone响应api调用,用于HLS协议流,监听setBitrate()请求完成情况
availableBitrates用于HLS协议流,监听HLS资源的可选bitrates,用于setBitrate()
bufferingUpdate用于网络播放,监听网络播放缓冲信息
startRenderFrame用于视频播放,监听视频播放首帧渲染时间
videoSizeChange用于视频播放,监听视频播放的宽高信息,可用于调整窗口大小、比例
audioInterrupt用于视频播放,监听音频焦点切换信息,搭配属性audioInterruptMode使用

全量接口示例

import media from '@ohos.multimedia.media'
import audio from '@ohos.multimedia.audio';
import fs from '@ohos.file.fs'

const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
  private count:number = 0
  private avPlayer
  private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法

  // 注册avplayer回调函数
  setAVPlayerCallback() {
    // 状态机变化回调函数
    this.avPlayer.on('stateChange', async (state, reason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info(TAG + 'state idle called')
          this.avPlayer.release() // 释放avplayer对象
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info(TAG + 'state initialized called ')
          this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
          this.avPlayer.prepare().then(() => {
            console.info(TAG+ 'prepare success');
          }, (err) => {
            console.error(TAG + 'prepare filed,error message is :' + err.message)
          })
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info(TAG + 'state prepared called')
          this.avPlayer.play() // 调用播放接口开始播放
          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info(TAG + 'state playing called')
          if (this.count == 0) {
            this.avPlayer.pause() // 调用暂停播放接口
          } else {
            this.avPlayer.seek(10000, media.SeekMode.SEEK_PREV_SYNC) // 前向seek置10秒处,触发seekDone回调函数
          }
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info(TAG + 'state paused called')
          if (this.count == 0) {
            this.count++
            this.avPlayer.play() // 继续调用播放接口开始播放
          }
          break;
        case 'completed': // 播放结束后触发该状态机上报
          console.info(TAG + 'state completed called')
          this.avPlayer.stop() //调用播放结束接口
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info(TAG + 'state stopped called')
          this.avPlayer.reset() // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          console.info(TAG + 'state released called')
          break;
        case 'error':
          console.info(TAG + 'state error called')
          break;
        default:
          console.info(TAG + 'unkown state :' + state)
          break;
      }
    })
    // 时间上报监听函数
    this.avPlayer.on('timeUpdate', (time:number) => {
      console.info(TAG + 'timeUpdate success,and new time is :' + time)
    })
    // 音量变化回调函数
    this.avPlayer.on('volumeChange', (vol:number) => {
      console.info(TAG + 'volumeChange success,and new volume is :' + vol)
      this.avPlayer.setSpeed(media.AVPlayerSpeed.SPEED_FORWARD_2_00_X) // 设置两倍速播放,并触发speedDone回调
    })
    // 视频播放结束触发回调
    this.avPlayer.on('endOfStream', () => {
      console.info(TAG + 'endOfStream success')
    })
    // seek操作回调函数
    this.avPlayer.on('seekDone', (seekDoneTime:number) => {
      console.info(TAG + 'seekDone success,and seek time is:' + seekDoneTime)
      this.avPlayer.setVolume(0.5) // 设置音量为0.5,并触发volumeChange回调函数
    })
    // 设置倍速播放回调函数
    this.avPlayer.on('speedDone', (speed:number) => {
      console.info(TAG + 'speedDone success,and speed value is:' + speed)
    })
    // bitrate设置成功回调函数
    this.avPlayer.on('bitrateDone', (bitrate:number) => {
      console.info(TAG + 'bitrateDone success,and bitrate value is:' + bitrate)
    })
    // 缓冲上报回调函数
    this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
      console.info(TAG + 'bufferingUpdate success,and infoType value is:' + infoType + ', value is :' + value)
    })
    // 首帧上报回调函数
    this.avPlayer.on('startRenderFrame', () => {
      console.info(TAG + 'startRenderFrame success')
    })
    // 视频宽高上报回调函数
    this.avPlayer.on('videoSizeChange', (width: number, height: number) => {
      console.info(TAG + 'videoSizeChange success,and width is:' + width + ', height is :' + height)
    })
    // 焦点上报回调函数
    this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => {
      console.info(TAG + 'audioInterrupt success,and InterruptEvent info is:' + info)
    })
    // HLS上报所有支持的比特率
    this.avPlayer.on('availableBitrates', (bitrates: Array<number>) => {
      console.info(TAG + 'availableBitrates success,and availableBitrates length is:' + bitrates.length)
    })
  }

  async avPlayerDemo() {
    // 创建avPlayer实例对象
    this.avPlayer = await media.createAVPlayer()
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
    let path = pathDir  + '/H264_AAC.mp4'
    let file = await fs.open(path)
    fdPath = fdPath + '' + file.fd
    this.avPlayer.url = fdPath
  }
}

正常播放场景

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
  private avPlayer
  private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法

  // 注册avplayer回调函数
  setAVPlayerCallback() {
    // 状态机变化回调函数
    this.avPlayer.on('stateChange', async (state, reason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info(TAG + 'state idle called')
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info(TAG + 'state initialized called ')
          this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
          this.avPlayer.prepare().then(() => {
            console.info(TAG+ 'prepare success');
          }, (err) => {
            console.error(TAG + 'prepare filed,error message is :' + err.message)
          })
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info(TAG + 'state prepared called')
          this.avPlayer.play() // 调用播放接口开始播放
          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info(TAG + 'state playing called')
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info(TAG + 'state paused called')
          break;
        case 'completed': // 播放结束后触发该状态机上报
          console.info(TAG + 'state completed called')
          this.avPlayer.stop() //调用播放结束接口
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info(TAG + 'state stopped called')
          this.avPlayer.release() // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          console.info(TAG + 'state released called')
          break;
        case 'error':
          console.info(TAG + 'state error called')
          break;
        default:
          console.info(TAG + 'unkown state :' + state)
          break;
      }
    })
  }

  async avPlayerDemo() {
    // 创建avPlayer实例对象
    this.avPlayer = await media.createAVPlayer()
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
    let path = pathDir  + '/H264_AAC.mp4'
    let file = await fs.open(path)
    fdPath = fdPath + '' + file.fd
    this.avPlayer.url = fdPath
  }
}

单曲循环场景

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
  private count:number = 0
  private avPlayer
  private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法

  // 注册avplayer回调函数
  setAVPlayerCallback() {
    // 状态机变化回调函数
    this.avPlayer.on('stateChange', async (state, reason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info(TAG + 'state idle called')
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info(TAG + 'state initialized called ')
          this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
          this.avPlayer.prepare().then(() => {
            console.info(TAG+ 'prepare success');
          }, (err) => {
            console.error(TAG + 'prepare filed,error message is :' + err.message)
          })
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info(TAG + 'state prepared called')
          this.avPlayer.loop = true // 设置单曲循环播放,单曲循环播放至结尾后会触发endOfStream回调
          this.avPlayer.play() // 调用播放接口开始播放
          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info(TAG + 'state playing called')
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info(TAG + 'state paused called')
          break;
        case 'completed': // 播放结束后触发该状态机上报
          console.info(TAG + 'state completed called')
          // 当第二次触发endOfStream回调后取消循环播放,再次播放到结尾后触发completed状态机上报
          this.avPlayer.stop() //调用播放结束接口
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info(TAG + 'state stopped called')
          this.avPlayer.release() // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          console.info(TAG + 'state released called')
          break;
        case 'error':
          console.info(TAG + 'state error called')
          break;
        default:
          console.info(TAG + 'unkown state :' + state)
          break;
      }
    })
    // 视频播放结束触发回调
    this.avPlayer.on('endOfStream', () => {
      console.info(TAG + 'endOfStream success')
      if (this.count == 1) {
        this.avPlayer.loop = false // 取消循环播放
      } else {
        this.count++
      }
    })
  }

  async avPlayerDemo() {
    // 创建avPlayer实例对象
    this.avPlayer = await media.createAVPlayer()
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
    let path = pathDir  + '/H264_AAC.mp4'
    let file = await fs.open(path)
    fdPath = fdPath + '' + file.fd
    this.avPlayer.url = fdPath
  }
}

视频切换场景

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
  private count:number = 0
  private avPlayer
  private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法

  async nextVideo() {
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\H264_MP3.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
    let path = pathDir  + '/H264_MP3.mp4'
    let file = await fs.open(path)
    fdPath = fdPath + '' + file.fd
    this.avPlayer.url = fdPath // 再次触发initialized状态机上报
  }

  // 注册avplayer回调函数
  setAVPlayerCallback() {
    // 状态机变化回调函数
    this.avPlayer.on('stateChange', async (state, reason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info(TAG + 'state idle called')
          await this.nextVideo() // 切换下一个视频播放
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info(TAG + 'state initialized called ')
          this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
          this.avPlayer.prepare().then(() => {
            console.info(TAG+ 'prepare success');
          }, (err) => {
            console.error(TAG + 'prepare filed,error message is :' + err.message)
          })
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info(TAG + 'state prepared called')
          this.avPlayer.play() // 调用播放接口开始播放
          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info(TAG + 'state playing called')
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info(TAG + 'state paused called')
          break;
        case 'completed': // 播放结束后触发该状态机上报
          console.info(TAG + 'state completed called')
          if (this.count == 0) {
            this.count++
            this.avPlayer.reset() //调用重置接口准备切换下一个视频
          } else {
            this.avPlayer.release() //切换视频后播放至结尾释放avplayer对象
          }
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info(TAG + 'state stopped called')
          break;
        case 'released':
          console.info(TAG + 'state released called')
          break;
        case 'error':
          console.info(TAG + 'state error called')
          break;
        default:
          console.info(TAG + 'unkown state :' + state)
          break;
      }
    })
  }

  async avPlayerDemo() {
    // 创建avPlayer实例对象
    this.avPlayer = await media.createAVPlayer()
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
    let path = pathDir  + '/H264_AAC.mp4'
    let file = await fs.open(path)
    fdPath = fdPath + '' + file.fd
    this.avPlayer.url = fdPath
  }
}

相关示例

针对AVPlayer播放器开发,有以下相关示例可供参考:

待补充

你可能感兴趣的鸿蒙文章

harmony 鸿蒙媒体

harmony 鸿蒙音频采集开发指导

harmony 鸿蒙音频焦点模式开发指导

harmony 鸿蒙音频开发概述

harmony 鸿蒙音频播放开发指导

harmony 鸿蒙音频录制开发指导

harmony 鸿蒙音频渲染开发指导

harmony 鸿蒙路由、设备管理开发指导

harmony 鸿蒙音频流管理开发指导

harmony 鸿蒙音量管理开发指导

  • 所属分类: 后端技术
  • 本文标签: 软件 鸿蒙
  • 版权声明: 本文链接 https://seaxiang.com/blog/f36ffcaaf9e948afa2d0c8084100312e