鸿蒙媒体开发系列14——视频播放开发

如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。

1、概述

在HarmonyOS系统中,提供两种视频播放开发的方案:

  • AVPlayer:功能较完善的音视频播放ArkTS/JS API,集成了流媒体和本地资源解析,媒体资源解封装,视频解码和渲染功能,适用于对媒体资源进行端到端播放的场景,可直接播放mp4、mkv等格式的视频文件。

  • Video组件:封装了视频播放的基础能力,需要设置数据源以及基础信息即可播放视频,但相对扩展能力较弱。Video组件在鸿蒙UI开发中已经有介绍,可以移步到👉🏻 鸿蒙UI系统组件07——视频播放器(Video) 查看

本文讨论使用AVPlayer开发视频播放功能,实现端到端播放原始媒体资源。

播放的全流程包含以下几步:

    1. 创建AVPlayer

    2. 设置播放资源和窗口

    3. 设置播放参数(音量/倍速/缩放模式)

    4.播放控制(播放/暂停/跳转/停止),

    5. 重置,销毁资源。

2、AVPlayer的状态

在应用开发过程中,我们可以通过AVPlayer的state属性主动获取当前状态或使用on('stateChange')方法监听状态变化。

【如果应用在视频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为】

AVPlayer主要的状态罗列如下:

名称

类型

说明

idle

string

闲置状态,AVPlayer刚被创建createAVPlayer()或者调用了reset()方法之后,进入Idle状态。

首次创建createAVPlayer(),所有属性都为默认值。

调用reset()方法,url9+ 或 fdSrc9+属性会被重置,其他用户设置的属性将被保留。

initialized

string

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

prepared

string

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

playing

string

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

paused

string

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

completed

string

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

stopped

string

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

released

string

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

error

string

错误状态,当播放引擎发生不可逆的错误,则会转换至当前状态,可以调用reset()重置,也可以调用release()销毁重建。

注意: 区分error状态和 on('error') :

1、进入error状态时,会触发on('error')监听事件,可以通过on('error')事件获取详细错误信息;

2、处于error状态时,播放服务进入不可播控的状态,要求客户端设计容错机制,使用reset()重置或者release()销毁重建;

3、如果客户端收到on('error'),但未进入error状态:

原因1:客户端未按状态机调用API或传入参数错误,被AVPlayer拦截提醒,需要客户端调整代码逻辑;

原因2:播放过程发现码流问题,导致容器、解码短暂异常,不影响连续播放和播控操作的,不需要客户端设计容错机制。

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

播放状态变化示意图如下:

图片

3、AVPlayer的开发步骤

👉🏻 1 创建AVPlayer实例

 

let avPlayer;media.createAVPlayer((error, video) => {  if (video != null) {    avPlayer = video;    console.info('createAVPlayer success');  } else {    console.error(`createAVPlayer fail, error message:${error.message}`);  }});

👉🏻 2 设置业务需要监听的事件

目前支持的事件罗列如下:

事件类型

说明

stateChange

必要事件,监听播放器的state属性改变。

error

必要事件,监听播放器的错误信息。

durationUpdate

用于进度条,监听进度条长度,刷新资源时长。

timeUpdate

用于进度条,监听进度条当前位置,刷新当前时间。

seekDone

响应API调用,监听seek()请求完成情况。

当使用seek()跳转到指定播放位置后,如果seek操作成功,将上报该事件。

speedDone

响应API调用,监听setSpeed()请求完成情况。

当使用setSpeed()设置播放倍速后,如果setSpeed操作成功,将上报该事件。

volumeChange

响应API调用,监听setVolume()请求完成情况。

当使用setVolume()调节播放音量后,如果setVolume操作成功,将上报该事件。

bitrateDone

响应API调用,用于HLS协议流,监听setBitrate()请求完成情况。

当使用setBitrate()指定播放比特率后,如果setBitrate操作成功,将上报该事件。

availableBitrates

用于HLS协议流,监听HLS资源的可选bitrates,用于setBitrate()。

bufferingUpdate

用于网络播放,监听网络播放缓冲信息。

startRenderFrame

用于视频播放,监听视频播放首帧渲染时间。

videoSizeChange

用于视频播放,监听视频播放的宽高信息,可用于调整窗口大小、比例。

audioInterrupt

监听音频焦点切换信息,搭配属性audioInterruptMode使用。

如果当前设备存在多个媒体正在播放,音频焦点被切换(即播放其他媒体如通话等)时将上报该事件,应用可以及时处理。

以监听stateChange和error事件为例(其他事件实现代码区别在于事件名):

 

// 监听stateChange事件avPlayer.on('stateChange', async (state, reason) => {  switch (state) {    case 'idle':      console.info('state idle called')      break;    case 'initialized':      console.info('initialized prepared called')      break;       // ..  }});//监听error事件avPlayer.on('error', (error) => {  console.error('error happened,and error message is :' + error.message)  console.error('error happened,and error code is :' + error.code)})

👉🏻 3 设置资源(设置url属性,AVPlayer进入初始化状态)

avPlayer.url = fdPath;

👉🏻 4 设置窗口

获取并设置属性SurfaceID,用于设置显示画面。(需要从XComponent组件获取surfaceID)

surfaceID获取的示例代码如下(关键代码在16行):

 

// xxx.ets@Entry@Componentstruct PreviewArea {  private surfaceId : string =''  xcomponentController: XComponentController = new XComponentController()  build() {    Row() {      XComponent({        id: 'xcomponent',        type: 'surface',        controller: this.xcomponentController      })        .onLoad(() => {          this.xcomponentController.setXComponentSurfaceSize({surfaceWidth:1920,surfaceHeight:1080});          this.surfaceId = this.xcomponentController.getXComponentSurfaceId()        })        .width('640px')        .height('480px')    }    .backgroundColor(Color.Black)    .position({x: 0, y: 48})  }}

👉🏻 5 准备播放

调用prepare(),AVPlayer进入prepared状态,此时可以获取duration,设置缩放模式、音量等。

avPlayer.prepare()

👉🏻 6 播放控制

调用播放play(),暂停pause(),跳转seek(),停止stop() 等方法实现操作。

👉🏻 7 切换资源

调用reset()重置资源,AVPlayer重新进入idle状态(允许重新设置url,更换资源url)。

👉🏻 8 退出播放

调用release()销毁实例,AVPlayer进入released状态,退出播放。

一个完整的Demo如下:

 

import media from '@ohos.multimedia.media';import fs from '@ohos.file.fs';import common from '@ohos.app.ability.common';export class AVPlayerDemo {  private avPlayer;  private count: number = 0;  private surfaceID: string; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法  // 注册avplayer回调函数  setAVPlayerCallback() {    // seek操作结果回调函数    this.avPlayer.on('seekDone', (seekDoneTime) => {      console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);    })    // error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程    this.avPlayer.on('error', (err) => {      console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);      this.avPlayer.reset(); // 调用reset重置资源,触发idle状态    })    // 状态机变化回调函数    this.avPlayer.on('stateChange', async (state, reason) => {      switch (state) {        case 'idle': // 成功调用reset接口后触发该状态机上报          console.info('AVPlayer state idle called.');          this.avPlayer.release(); // 调用release接口销毁实例对象          break;        case 'initialized': // avplayer 设置播放源后触发该状态上报          console.info('AVPlayerstate initialized called.');          this.avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置          this.avPlayer.prepare().then(() => {            console.info('AVPlayer prepare succeeded.');          }, (err) => {            console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`);          });          break;        case 'prepared': // prepare调用成功后上报该状态机          console.info('AVPlayer state prepared called.');          this.avPlayer.play(); // 调用播放接口开始播放          break;        case 'playing': // play成功调用后触发该状态机上报          console.info('AVPlayer state playing called.');          if (this.count !== 0) {            console.info('AVPlayer start to seek.');            this.avPlayer.seek(this.avPlayer.duration); //seek到视频末尾          } else {            this.avPlayer.pause(); // 调用暂停接口暂停播放          }          this.count++;          break;        case 'paused': // pause成功调用后触发该状态机上报          console.info('AVPlayer state paused called.');          this.avPlayer.play(); // 再次播放接口开始播放          break;        case 'completed': // 播放结束后触发该状态机上报          console.info('AVPlayer state completed called.');          this.avPlayer.stop(); //调用播放结束接口          break;        case 'stopped': // stop接口成功调用后触发该状态机上报          console.info('AVPlayer state stopped called.');          this.avPlayer.reset(); // 调用reset接口初始化avplayer状态          break;        case 'released':          console.info('AVPlayer state released called.');          break;        default:          console.info('AVPlayer state unknown called.');          break;      }    })  }  // 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过url属性进行播放示例  async avPlayerUrlDemo() {    // 创建avPlayer实例对象    this.avPlayer = await media.createAVPlayer();    // 创建状态机变化回调函数    this.setAVPlayerCallback();    let fdPath = 'fd://';    let context = getContext(this) as common.UIAbilityContext;    // 通过UIAbilityContext获取沙箱地址filesDir,以下为Stage模型获方式,如需在FA模型上获取请参考《访问应用沙箱》获取地址    let pathDir = context.filesDir;    let path = pathDir  + '/H264_AAC.mp4';     // 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报    let file = await fs.open(path);    fdPath = fdPath + '' + file.fd;    this.avPlayer.url = fdPath;  }  // 以下demo为使用资源管理接口获取打包在HAP内的媒体资源文件并通过fdSrc属性进行播放示例  async avPlayerFdSrcDemo() {    // 创建avPlayer实例对象    this.avPlayer = await media.createAVPlayer();    // 创建状态机变化回调函数    this.setAVPlayerCallback();    // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址    // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度    let context = getContext(this) as common.UIAbilityContext;    let fileDescriptor = await context.resourceManager.getRawFd('H264_AAC.mp4');    // 为fdSrc赋值触发initialized状态机上报    this.avPlayer.fdSrc = fileDescriptor;  }}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值