1# AVPlayer播放器开发指导 2 3## 简介 4 5AVPlayer主要工作是将Audio/Video媒体资源转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。 6 7## 运作机制 8 9该模块提供了播放状态变化示意图[AVPlayerState](../reference/apis/js-apis-media.md#avplayerstate9)、音频播放外部模块交互图和视频播放外部模块交互图。 10 11**图1** 播放状态变化示意图<a name = avplayer_state></a> 12 13 14 15 16**图2** 音频播放外部模块交互图 17 18 19 20**说明**:应用通过调用JS接口层提供的AVPlayer js接口实现相应功能时,框架层会通过Player Framework的播放服务解析成音频数据流,音频数据流经过软件解码后输出至Audio Framework的音频服务,由音频子系统输出至硬件接口层的音频HDI,实现音频播放功能。完整的音乐播放器工作需要:应用(应用适配)、Player Framework、Audio Framework、Audio HDI(驱动适配)共同实现。 21 22*注意:音频播放需要音频子系统配合* 23 241. 应用把url传递给AVPlayer JS。 252. 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。 26 27 28**图3** 视频播放外部模块交互图 29 30 31 32**说明**:应用通过调用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(驱动适配)共同实现。 33 34*注意:视频播放需要显示、音频、解码等多个子系统配合。* 35 361. 应用从Xcomponent组件获取surfaceID,[获取方式](../reference/arkui-ts/ts-basic-components-xcomponent.md)。 372. 应用把url、surfaceID传递给AVPlayer JS。 383. 播放服务把视频ES数据流输出给Codec HDI,解码获得视频帧(NV12/NV21/RGBA)。 394. 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。 405. 播放服务把视频帧(NV12/NV21/RGBA)输出给RS服务,RS服务输出给Display HDI。 41 42## 兼容性说明 43 44视频播放支持的视频格式分必选规格和可选规格。必选规格为所有厂商均支持的视频格式。对于可选规格,厂商将基于实际情况决定是否实现。建议开发者做兼容处理,保证全平台兼容。 45推荐使用主流的播放格式和主流分辨率,不建议开发者自制非常或者异常码流,以免产生无法播放、卡住、花屏等兼容性问题。若发生此类问题不会影响系统,退出码流播放即可。 46 47| 视频格式 | 是否必选规格 | 48|:--------:|:-----:| 49| H264 | 是 | 50| MPEG2 | 否 | 51| MPEG4 | 否 | 52| H263 | 否 | 53| VP8 | 否 | 54 55主流的播放格式和主流分辨率如下: 56 57| 视频容器规格 | 规格描述 | 分辨率 | 58| :----------: | :-----------------------------------------------: | :--------------------------------: | 59| mp4 | 视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P | 60| mkv | 视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P | 61| ts | 视频格式:H264/MPEG2/MPEG4 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P | 62| webm | 视频格式:VP8 音频格式:VORBIS | 主流分辨率,如1080P/720P/480P/270P | 63 64| 音频容器规格 | 规格描述 | 65| :----------: | :----------: | 66| m4a | 音频格式:AAC | 67| aac | 音频格式:AAC | 68| mp3 | 音频格式:MP3 | 69| ogg | 音频格式:VORBIS | 70| wav | 音频格式:PCM | 71 72## 开发指导 73 74详细API含义可参考:[媒体服务API文档AVPlayer](../reference/apis/js-apis-media.md#avplayer9) 75 76### 播放流程说明 77 78播放的全流程场景包含:创建实例,设置资源,设置窗口(视频),准备播放(获取轨道信息/音量/倍速/焦点模式/缩放模式/设置bitrates),播控(播放/暂停/Seek/音量/停止),重置资源,销毁播放 79 801:创建实例[createAVPlayer()](../reference/apis/js-apis-media.md#mediacreateavplayer9),AVPlayer初始化[idle](#avplayer_state)状态 81 822:设置业务需要的监听事件,搭配全流程场景使用 83 843:设置资源 [url](../reference/apis/js-apis-media.md#avplayer_属性),AVPlayer进入[initialized](#avplayer_state)状态,此时可以设置视频窗口 [surfaceId](../reference/apis/js-apis-media.md#avplayer_属性),支持的规格可参考:[AVPlayer属性说明](../reference/apis/js-apis-media.md#avplayer_属性) 85 864:准备播放 [prepare()](../reference/apis/js-apis-media.md#avplayer_prepare),AVPlayer进入[prepared](#avplayer_state)状态 87 885:视频播控:播放 [play()](../reference/apis/js-apis-media.md#avplayer_play),暂停 [pause()](../reference/apis/js-apis-media.md#avplayer_pause),跳转 [seek()](../reference/apis/js-apis-media.md#avplayer_seek),停止 [stop()](../reference/apis/js-apis-media.md#avplayer_stop) 等操作 89 906:重置资源 [reset()](../reference/apis/js-apis-media.md#avplayer_reset),AVPlayer重新进入[idle](#avplayer_state)状态,允许更换资源 [url](../reference/apis/js-apis-media.md#avplayer_属性) 91 927:销毁播放 [release()](../reference/apis/js-apis-media.md#avplayer_release),AVPlayer进入[released](#avplayer_state)状态,退出播放 93 94> **说明:** 95> 96> prepared/playing/paused/compeled 状态时,播放引擎处于工作状态,这需要占用系统较多的运行内存,当客户端暂时不使用播放器时,要求调用 reset() 或 release() 回收。 97 98### 监听事件 99 100| 事件类型 | 说明 | 101| ------------------------------------------------- | ------------------------------------------------------------ | 102| stateChange<a name = stateChange></a> | 必要事件,监听播放器的状态机 | 103| error<a name = error></a> | 必要事件,监听播放器的错误信息 | 104| durationUpdate<a name = durationUpdate></a> | 用于进度条,监听进度条长度,刷新资源时长 | 105| timeUpdate<a name = timeUpdate></a> | 用于进度条,监听进度条当前位置,刷新当前时间 | 106| seekDone<a name = seekDone></a> | 响应api调用,监听seek()请求完成情况 | 107| speedDone<a name = speedDone></a> | 响应api调用,监听setSpeed()请求完成情况 | 108| volumeChange<a name = volumeChange></a> | 响应api调用,监听setVolume()请求完成情况 | 109| bitrateDone<a name = bitrateDone></a> | 响应api调用,用于HLS协议流,监听setBitrate()请求完成情况 | 110| availableBitrates<a name = availableBitrates></a> | 用于HLS协议流,监听HLS资源的可选bitrates,用于setBitrate() | 111| bufferingUpdate<a name = bufferingUpdate></a> | 用于网络播放,监听网络播放缓冲信息 | 112| startRenderFrame<a name = startRenderFrame></a> | 用于视频播放,监听视频播放首帧渲染时间 | 113| videoSizeChange<a name = videoSizeChange></a> | 用于视频播放,监听视频播放的宽高信息,可用于调整窗口大小、比例 | 114| audioInterrupt<a name = audioInterrupt></a> | 用于视频播放,监听音频焦点切换信息,搭配属性audioInterruptMode使用 | 115 116### 117 118### 全量接口示例 119 120```js 121import media from '@ohos.multimedia.media' 122import audio from '@ohos.multimedia.audio'; 123import fs from '@ohos.file.fs' 124 125const TAG = 'AVPlayerDemo:' 126export class AVPlayerDemo { 127 private count:number = 0 128 private avPlayer 129 private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法 130 131 // 注册avplayer回调函数 132 setAVPlayerCallback() { 133 // 状态机变化回调函数 134 this.avPlayer.on('stateChange', async (state, reason) => { 135 switch (state) { 136 case 'idle': // 成功调用reset接口后触发该状态机上报 137 console.info(TAG + 'state idle called') 138 this.avPlayer.release() // 释放avplayer对象 139 break; 140 case 'initialized': // avplayer 设置播放源后触发该状态上报 141 console.info(TAG + 'state initialized called ') 142 this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置 143 this.avPlayer.prepare().then(() => { 144 console.info(TAG+ 'prepare success'); 145 }, (err) => { 146 console.error(TAG + 'prepare filed,error message is :' + err.message) 147 }) 148 break; 149 case 'prepared': // prepare调用成功后上报该状态机 150 console.info(TAG + 'state prepared called') 151 this.avPlayer.play() // 调用播放接口开始播放 152 break; 153 case 'playing': // play成功调用后触发该状态机上报 154 console.info(TAG + 'state playing called') 155 if (this.count == 0) { 156 this.avPlayer.pause() // 调用暂停播放接口 157 } else { 158 this.avPlayer.seek(10000, media.SeekMode.SEEK_PREV_SYNC) // 前向seek置10秒处,触发seekDone回调函数 159 } 160 break; 161 case 'paused': // pause成功调用后触发该状态机上报 162 console.info(TAG + 'state paused called') 163 if (this.count == 0) { 164 this.count++ 165 this.avPlayer.play() // 继续调用播放接口开始播放 166 } 167 break; 168 case 'completed': // 播放结束后触发该状态机上报 169 console.info(TAG + 'state completed called') 170 this.avPlayer.stop() //调用播放结束接口 171 break; 172 case 'stopped': // stop接口成功调用后触发该状态机上报 173 console.info(TAG + 'state stopped called') 174 this.avPlayer.reset() // 调用reset接口初始化avplayer状态 175 break; 176 case 'released': 177 console.info(TAG + 'state released called') 178 break; 179 case 'error': 180 console.info(TAG + 'state error called') 181 break; 182 default: 183 console.info(TAG + 'unkown state :' + state) 184 break; 185 } 186 }) 187 // 时间上报监听函数 188 this.avPlayer.on('timeUpdate', (time:number) => { 189 console.info(TAG + 'timeUpdate success,and new time is :' + time) 190 }) 191 // 音量变化回调函数 192 this.avPlayer.on('volumeChange', (vol:number) => { 193 console.info(TAG + 'volumeChange success,and new volume is :' + vol) 194 this.avPlayer.setSpeed(media.AVPlayerSpeed.SPEED_FORWARD_2_00_X) // 设置两倍速播放,并触发speedDone回调 195 }) 196 // 视频播放结束触发回调 197 this.avPlayer.on('endOfStream', () => { 198 console.info(TAG + 'endOfStream success') 199 }) 200 // seek操作回调函数 201 this.avPlayer.on('seekDone', (seekDoneTime:number) => { 202 console.info(TAG + 'seekDone success,and seek time is:' + seekDoneTime) 203 this.avPlayer.setVolume(0.5) // 设置音量为0.5,并触发volumeChange回调函数 204 }) 205 // 设置倍速播放回调函数 206 this.avPlayer.on('speedDone', (speed:number) => { 207 console.info(TAG + 'speedDone success,and speed value is:' + speed) 208 }) 209 // bitrate设置成功回调函数 210 this.avPlayer.on('bitrateDone', (bitrate:number) => { 211 console.info(TAG + 'bitrateDone success,and bitrate value is:' + bitrate) 212 }) 213 // 缓冲上报回调函数 214 this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => { 215 console.info(TAG + 'bufferingUpdate success,and infoType value is:' + infoType + ', value is :' + value) 216 }) 217 // 首帧上报回调函数 218 this.avPlayer.on('startRenderFrame', () => { 219 console.info(TAG + 'startRenderFrame success') 220 }) 221 // 视频宽高上报回调函数 222 this.avPlayer.on('videoSizeChange', (width: number, height: number) => { 223 console.info(TAG + 'videoSizeChange success,and width is:' + width + ', height is :' + height) 224 }) 225 // 焦点上报回调函数 226 this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => { 227 console.info(TAG + 'audioInterrupt success,and InterruptEvent info is:' + info) 228 }) 229 // HLS上报所有支持的比特率 230 this.avPlayer.on('availableBitrates', (bitrates: Array<number>) => { 231 console.info(TAG + 'availableBitrates success,and availableBitrates length is:' + bitrates.length) 232 }) 233 } 234 235 async avPlayerDemo() { 236 // 创建avPlayer实例对象 237 this.avPlayer = await media.createAVPlayer() 238 let fdPath = 'fd://' 239 let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。 240 // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上 241 let path = pathDir + '/H264_AAC.mp4' 242 let file = await fs.open(path) 243 fdPath = fdPath + '' + file.fd 244 this.avPlayer.url = fdPath 245 } 246} 247``` 248 249### 正常播放场景 250 251```js 252import media from '@ohos.multimedia.media' 253import fs from '@ohos.file.fs' 254 255const TAG = 'AVPlayerDemo:' 256export class AVPlayerDemo { 257 private avPlayer 258 private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法 259 260 // 注册avplayer回调函数 261 setAVPlayerCallback() { 262 // 状态机变化回调函数 263 this.avPlayer.on('stateChange', async (state, reason) => { 264 switch (state) { 265 case 'idle': // 成功调用reset接口后触发该状态机上报 266 console.info(TAG + 'state idle called') 267 break; 268 case 'initialized': // avplayer 设置播放源后触发该状态上报 269 console.info(TAG + 'state initialized called ') 270 this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置 271 this.avPlayer.prepare().then(() => { 272 console.info(TAG+ 'prepare success'); 273 }, (err) => { 274 console.error(TAG + 'prepare filed,error message is :' + err.message) 275 }) 276 break; 277 case 'prepared': // prepare调用成功后上报该状态机 278 console.info(TAG + 'state prepared called') 279 this.avPlayer.play() // 调用播放接口开始播放 280 break; 281 case 'playing': // play成功调用后触发该状态机上报 282 console.info(TAG + 'state playing called') 283 break; 284 case 'paused': // pause成功调用后触发该状态机上报 285 console.info(TAG + 'state paused called') 286 break; 287 case 'completed': // 播放结束后触发该状态机上报 288 console.info(TAG + 'state completed called') 289 this.avPlayer.stop() //调用播放结束接口 290 break; 291 case 'stopped': // stop接口成功调用后触发该状态机上报 292 console.info(TAG + 'state stopped called') 293 this.avPlayer.release() // 调用reset接口初始化avplayer状态 294 break; 295 case 'released': 296 console.info(TAG + 'state released called') 297 break; 298 case 'error': 299 console.info(TAG + 'state error called') 300 break; 301 default: 302 console.info(TAG + 'unkown state :' + state) 303 break; 304 } 305 }) 306 } 307 308 async avPlayerDemo() { 309 // 创建avPlayer实例对象 310 this.avPlayer = await media.createAVPlayer() 311 let fileDescriptor = undefined 312 // 使用资源管理模块的getRawFileDescriptor获取集成在应用中的媒体资源,并使用AVPlayer的fdSrc属性完成媒体资源初始化 313 // 其中的参数fd/offset/length定义请查看媒体API文档,globalThis.abilityContext参数为系统环境变量,在系统启动时在主界面保存为全局变量 314 await globalThis.abilityContext.resourceManager.getRawFileDescriptor('H264_AAC.mp4').then((value) => { 315 fileDescriptor = {fd: value.fd, offset: value.offset, length: value.length} 316 }) 317 this.avPlayer.fdSrc = fileDescriptor 318 } 319} 320``` 321 322### 单曲循环场景 323 324```js 325import media from '@ohos.multimedia.media' 326import fs from '@ohos.file.fs' 327 328const TAG = 'AVPlayerDemo:' 329export class AVPlayerDemo { 330 private count:number = 0 331 private avPlayer 332 private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法 333 334 // 注册avplayer回调函数 335 setAVPlayerCallback() { 336 // 状态机变化回调函数 337 this.avPlayer.on('stateChange', async (state, reason) => { 338 switch (state) { 339 case 'idle': // 成功调用reset接口后触发该状态机上报 340 console.info(TAG + 'state idle called') 341 break; 342 case 'initialized': // avplayer 设置播放源后触发该状态上报 343 console.info(TAG + 'state initialized called ') 344 this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置 345 this.avPlayer.prepare().then(() => { 346 console.info(TAG+ 'prepare success'); 347 }, (err) => { 348 console.error(TAG + 'prepare filed,error message is :' + err.message) 349 }) 350 break; 351 case 'prepared': // prepare调用成功后上报该状态机 352 console.info(TAG + 'state prepared called') 353 this.avPlayer.loop = true // 设置单曲循环播放,单曲循环播放至结尾后会触发endOfStream回调 354 this.avPlayer.play() // 调用播放接口开始播放 355 break; 356 case 'playing': // play成功调用后触发该状态机上报 357 console.info(TAG + 'state playing called') 358 break; 359 case 'paused': // pause成功调用后触发该状态机上报 360 console.info(TAG + 'state paused called') 361 break; 362 case 'completed': // 播放结束后触发该状态机上报 363 console.info(TAG + 'state completed called') 364 // 当第二次触发endOfStream回调后取消循环播放,再次播放到结尾后触发completed状态机上报 365 this.avPlayer.stop() //调用播放结束接口 366 break; 367 case 'stopped': // stop接口成功调用后触发该状态机上报 368 console.info(TAG + 'state stopped called') 369 this.avPlayer.release() // 调用reset接口初始化avplayer状态 370 break; 371 case 'released': 372 console.info(TAG + 'state released called') 373 break; 374 case 'error': 375 console.info(TAG + 'state error called') 376 break; 377 default: 378 console.info(TAG + 'unkown state :' + state) 379 break; 380 } 381 }) 382 // 视频播放结束触发回调 383 this.avPlayer.on('endOfStream', () => { 384 console.info(TAG + 'endOfStream success') 385 if (this.count == 1) { 386 this.avPlayer.loop = false // 取消循环播放 387 } else { 388 this.count++ 389 } 390 }) 391 } 392 393 async avPlayerDemo() { 394 // 创建avPlayer实例对象 395 this.avPlayer = await media.createAVPlayer() 396 let fdPath = 'fd://' 397 let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。 398 // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上 399 let path = pathDir + '/H264_AAC.mp4' 400 let file = await fs.open(path) 401 fdPath = fdPath + '' + file.fd 402 this.avPlayer.url = fdPath 403 } 404} 405``` 406### 视频切换场景 407 408```js 409import media from '@ohos.multimedia.media' 410import fs from '@ohos.file.fs' 411 412const TAG = 'AVPlayerDemo:' 413export class AVPlayerDemo { 414 private count:number = 0 415 private avPlayer 416 private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法 417 418 async nextVideo() { 419 let fdPath = 'fd://' 420 let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。 421 // path路径的码流可通过"hdc file send D:\xxx\H264_MP3.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上 422 let path = pathDir + '/H264_MP3.mp4' 423 let file = await fs.open(path) 424 fdPath = fdPath + '' + file.fd 425 this.avPlayer.url = fdPath // 再次触发initialized状态机上报 426 } 427 428 // 注册avplayer回调函数 429 setAVPlayerCallback() { 430 // 状态机变化回调函数 431 this.avPlayer.on('stateChange', async (state, reason) => { 432 switch (state) { 433 case 'idle': // 成功调用reset接口后触发该状态机上报 434 console.info(TAG + 'state idle called') 435 await this.nextVideo() // 切换下一个视频播放 436 break; 437 case 'initialized': // avplayer 设置播放源后触发该状态上报 438 console.info(TAG + 'state initialized called ') 439 this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置 440 this.avPlayer.prepare().then(() => { 441 console.info(TAG+ 'prepare success'); 442 }, (err) => { 443 console.error(TAG + 'prepare filed,error message is :' + err.message) 444 }) 445 break; 446 case 'prepared': // prepare调用成功后上报该状态机 447 console.info(TAG + 'state prepared called') 448 this.avPlayer.play() // 调用播放接口开始播放 449 break; 450 case 'playing': // play成功调用后触发该状态机上报 451 console.info(TAG + 'state playing called') 452 break; 453 case 'paused': // pause成功调用后触发该状态机上报 454 console.info(TAG + 'state paused called') 455 break; 456 case 'completed': // 播放结束后触发该状态机上报 457 console.info(TAG + 'state completed called') 458 if (this.count == 0) { 459 this.count++ 460 this.avPlayer.reset() //调用重置接口准备切换下一个视频 461 } else { 462 this.avPlayer.release() //切换视频后播放至结尾释放avplayer对象 463 } 464 break; 465 case 'stopped': // stop接口成功调用后触发该状态机上报 466 console.info(TAG + 'state stopped called') 467 break; 468 case 'released': 469 console.info(TAG + 'state released called') 470 break; 471 case 'error': 472 console.info(TAG + 'state error called') 473 break; 474 default: 475 console.info(TAG + 'unkown state :' + state) 476 break; 477 } 478 }) 479 } 480 481 async avPlayerDemo() { 482 // 创建avPlayer实例对象 483 this.avPlayer = await media.createAVPlayer() 484 let fdPath = 'fd://' 485 let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。 486 // path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上 487 let path = pathDir + '/H264_AAC.mp4' 488 let file = await fs.open(path) 489 fdPath = fdPath + '' + file.fd 490 this.avPlayer.url = fdPath 491 } 492} 493```