AVPlayer播放器开发指导
AVPlayer播放器开发指导
简介
AVPlayer主要工作是将Audio/Video媒体资源转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。
运作机制
该模块提供了播放状态变化示意图AVPlayerState、音频播放外部模块交互图和视频播放外部模块交互图。
图2 音频播放外部模块交互图
说明:应用通过调用JS接口层提供的AVPlayer js接口实现相应功能时,框架层会通过Player Framework的播放服务解析成音频数据流,音频数据流经过软件解码后输出至Audio Framework的音频服务,由音频子系统输出至硬件接口层的音频HDI,实现音频播放功能。完整的音乐播放器工作需要:应用(应用适配)、Player Framework、Audio Framework、Audio HDI(驱动适配)共同实现。
注意:音频播放需要音频子系统配合
- 应用把url传递给AVPlayer JS。
- 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。
图3 视频播放外部模块交互图
说明:应用通过调用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(驱动适配)共同实现。
注意:视频播放需要显示、音频、解码等多个子系统配合。
- 应用从Xcomponent组件获取surfaceID,获取方式。
- 应用把url、surfaceID传递给AVPlayer JS。
- 播放服务把视频ES数据流输出给Codec HDI,解码获得视频帧(NV12/NV21/RGBA)。
- 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。
- 播放服务把视频帧(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() 回收。
监听事件
全量接口示例
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
正常播放场景
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
单曲循环场景
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
视频切换场景
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
相关示例
针对AVPlayer播放器开发,有以下相关示例可供参考:
待补充