ArkTS 提供了 @ohos.multimedia.media 模块来处理音视频相关媒体业务,它提供了音视频播放和录制的功能,通过 AVPlayer 类,可以实现音视频的播放。下面我们使用AVPlayer实现一个简单的视频播放器。
AVPlayer的工作流程
在正式进入播放器开发前,我们先来认识一下AVPlayer的工作流程,下图是根据官方的状态变化示意图重新绘制的流程图:
- 我们要实现一个播放器,首先需要使用createAVPlayer()创建一个播放器实例,此时播放器会进入idle状态,也就是闲置状态。如果我们调用了reset()来重置播放器,此时播放器也会进入闲置状态
- 在闲置状态,给播放器设置播放源,即设置播放器的 url 或 的 fdSrc,播放器就会进入initialized状态,也就是初始化状态。在初始化状态,需要给播放器配置播放窗口,才能显示视频画面。
- 在初始化状态调用prepare()方法,播放器会进入prepared状态,也就是准备状态,此时播放器的资源已准备就绪。如果在播放器的停止状态调用了prepare()方法,播放器会重新进入prepared状态。
- 在准备状态调用play()方法,播放器就会进入playing状态,即正在播放状态。如果在播放器的停止、播放完成状态调用play()方法,播放器会重新进入播放状态。
- 在播放状态中调用了pause()方法,播放器就会进入paused状态,即暂停状态。如果此时调用了play()方法,播放器会进入playing状态,继续播放媒体资源。
- 当媒体资源播放至结尾时,如果用户没有设置循环播放(loop = 1),播放器就会进入completed状态,即完成状态。如果这个时候调用play()会进入playing状态重播媒体资源。
- 在prepared、playing、paused、completed状态是调用stop()方法,播放器就会进入stopped状态,即停止状态,此时的播放器会释放内存资源。在这个时候,可以调用prepare()方法让播放器进入准备状态,重新播放媒体资源。也可以调用reset()方法重置播放器,让其回到闲置状态,或者调用release()方法彻底销毁播放器。
了解完播放器的工作流程后,我们就正式开始播放器的开发。
视频播放器实现
实现播放器的核心,就是监听播放器状态,在对应的状态中执行下一步的动作。
1、创建播放器
首先我们建一个播放器工具类,在工具类中使用media.createAVPlayer() 方法创建一个AVPlayer播放器实例。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// 创建播放器的方法
static async init() {
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
}
}
2、监听状态
因为播放器的实现核心,就是监听播放器状态,然后在对应的状态中执行下一步动作。同时,我们也需要根据播放器的状态来设置页面的展示,因此需要监听播放器的状态。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// 创建播放器的方法
static async init() {
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
// ----------------------- 事件监听 --------------------------------------------------------------
// 用于进度条,监听进度条长度,刷新资源时长
VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
console.info('AVPlayer state durationUpdate called. current time: ', duration);
})
// 用于进度条,监听进度条当前位置,刷新当前时间
VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
console.info('AVPlayer state timeUpdate called. current time: ', time);
})
// 监听seek生效的事件
VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
VideoAVPlayerClass.avPlayer.play()
VideoAVPlayerClass.isPlay = true
})
// 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
VideoAVPlayerClass.avPlayer.on('error', (err) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
// 调用reset重置资源,触发idle状态
VideoAVPlayerClass.avPlayer.reset()
})
// 监听播放状态机AVPlayerState切换的事件
VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
switch (state) {
// 成功调用reset接口后触发该状态机上报
case 'idle':
console.info('AVPlayer state idle called.');
break
// avplayer 设置播放源后触发该状态上报
case 'initialized':
console.info('AVPlayerstate initialized called.');
break
// prepare调用成功后上报该状态机
case 'prepared':
console.info('AVPlayer state prepared called.');
break
// play成功调用后触发该状态机上报
case 'playing':
console.info('AVPlayer state playing called.');
break
// pause成功调用后触发该状态机上报
case 'paused':
console.info('AVPlayer state paused called.');
break
// 播放结束后触发该状态机上报
case 'completed':
console.info('AVPlayer state completed called.');
break
// stop接口成功调用后触发该状态机上报
case 'stopped':
console.info('AVPlayer state stopped called.');
// 调用reset接口初始化avplayer状态
VideoAVPlayerClass.avPlayer.reset()
break
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
}
在当前的实现中,我们主要监听了播放器的durationUpdate、timeUpdate、seekDone、stateChange事件。
3、设置播放源
我们提供一个changePlay方法,在这个方法中完成播放源的设置,在设置播放源前,应该先将播放器的状态重置为闲置状态。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// 创建播放器的方法
static async init() {
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
// ----------------------- 事件监听 --------------------------------------------------------------
// 用于进度条,监听进度条长度,刷新资源时长
VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
console.info('AVPlayer state durationUpdate called. current time: ', duration);
})
// 用于进度条,监听进度条当前位置,刷新当前时间
VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
console.info('AVPlayer state timeUpdate called. current time: ', time);
})
// 监听seek生效的事件
VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
VideoAVPlayerClass.avPlayer.play()
VideoAVPlayerClass.isPlay = true
})
// 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
VideoAVPlayerClass.avPlayer.on('error', (err) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
// 调用reset重置资源,触发idle状态
VideoAVPlayerClass.avPlayer.reset()
})
// 监听播放状态机AVPlayerState切换的事件
VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
switch (state) {
// 成功调用reset接口后触发该状态机上报
case 'idle':
console.info('AVPlayer state idle called.');
break
// avplayer 设置播放源后触发该状态上报
case 'initialized':
console.info('AVPlayerstate initialized called.');
break
// prepare调用成功后上报该状态机
case 'prepared':
console.info('AVPlayer state prepared called.');
break
// play成功调用后触发该状态机上报
case 'playing':
console.info('AVPlayer state playing called.');
break
// pause成功调用后触发该状态机上报
case 'paused':
console.info('AVPlayer state paused called.');
break
// 播放结束后触发该状态机上报
case 'completed':
console.info('AVPlayer state completed called.');
break
// stop接口成功调用后触发该状态机上报
case 'stopped':
console.info('AVPlayer state stopped called.');
// 调用reset接口初始化avplayer状态
VideoAVPlayerClass.avPlayer.reset()
break
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
static async changePlay() {
// 将播放状态置为闲置
await VideoAVPlayerClass.avPlayer.reset()
VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
}
}
4、设置播放窗口
用AVPlayer播放视频,需要设置播放窗口才能显示画面,从XComponent组件获取surfaceId后设置给播放器实例的surfaceId属性。
首先在播放器类中定义一个surfaceId变量来存储从XComponent组件获取的surfaceId,然后在init()方法中将surfaceId存储到播放器类上,在播放器的初始化状态设置播放器的播放窗口。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
static surfaceId: string = ''
// 创建播放器的方法
static async init(initParams: InitParams) {
// 存储属性SurfaceID,用于设置播放窗口,显示画面
VideoAVPlayerClass.surfaceId = initParams.surfaceId
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
// ----------------------- 事件监听 --------------------------------------------------------------
// 用于进度条,监听进度条长度,刷新资源时长
VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
console.info('AVPlayer state durationUpdate called. current time: ', duration);
})
// 用于进度条,监听进度条当前位置,刷新当前时间
VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
console.info('AVPlayer state timeUpdate called. current time: ', time);
})
// 监听seek生效的事件
VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
VideoAVPlayerClass.avPlayer.play()
VideoAVPlayerClass.isPlay = true
})
// 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
VideoAVPlayerClass.avPlayer.on('error', (err) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
// 调用reset重置资源,触发idle状态
VideoAVPlayerClass.avPlayer.reset()
})
// 监听播放状态机AVPlayerState切换的事件
VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
switch (state) {
// 成功调用reset接口后触发该状态机上报
case 'idle':
console.info('AVPlayer state idle called.');
break
// avplayer 设置播放源后触发该状态上报
case 'initialized':
console.info('AVPlayerstate initialized called.');
// 设置显示画面,当播放的资源为纯音频时无需设置
VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
break
// prepare调用成功后上报该状态机
case 'prepared':
console.info('AVPlayer state prepared called.');
break
// play成功调用后触发该状态机上报
case 'playing':
console.info('AVPlayer state playing called.');
break
// pause成功调用后触发该状态机上报
case 'paused':
console.info('AVPlayer state paused called.');
break
// 播放结束后触发该状态机上报
case 'completed':
console.info('AVPlayer state completed called.');
break
// stop接口成功调用后触发该状态机上报
case 'stopped':
console.info('AVPlayer state stopped called.');
// 调用reset接口初始化avplayer状态
VideoAVPlayerClass.avPlayer.reset()
break
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
static async changePlay() {
// 将播放状态置为闲置
await VideoAVPlayerClass.avPlayer.reset()
VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
}
}
5、获取播放信息
在页面中,需要展示当前播放视频的视频时长、播放时长以及播放器状态,因此我们需要获取视频的播放信息。我们先获取视频时长、当前播放时长、视频是否播放这三个播放信息。
- 获取视频时长
通过监听播放器的durationUpdate事件,可以获取视频资源的总时长。在播放器类中定义一个duration变量来存储视频时长,然后监听durationUpdate事件获取视频时长。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// 当前播放器播放视频的总时长
static duration: number = 0
// surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
static surfaceId: string = ''
// 创建播放器的方法
static async init(initParams: InitParams) {
// 存储属性SurfaceID,用于设置播放窗口,显示画面
VideoAVPlayerClass.surfaceId = initParams.surfaceId
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
// ----------------------- 事件监听 --------------------------------------------------------------
// 用于进度条,监听进度条长度,刷新资源时长
VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
console.info('AVPlayer state durationUpdate called. current time: ', duration);
// 获取视频总时长
VideoAVPlayerClass.duration = duration
})
// 用于进度条,监听进度条当前位置,刷新当前时间
VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
console.info('AVPlayer state timeUpdate called. current time: ', time);
})
// 监听seek生效的事件
VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
VideoAVPlayerClass.avPlayer.play()
VideoAVPlayerClass.isPlay = true
})
// 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
VideoAVPlayerClass.avPlayer.on('error', (err) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
// 调用reset重置资源,触发idle状态
VideoAVPlayerClass.avPlayer.reset()
})
// 监听播放状态机AVPlayerState切换的事件
VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
switch (state) {
// 成功调用reset接口后触发该状态机上报
case 'idle':
console.info('AVPlayer state idle called.');
break
// avplayer 设置播放源后触发该状态上报
case 'initialized':
console.info('AVPlayerstate initialized called.');
// 设置显示画面,当播放的资源为纯音频时无需设置
VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
break
// prepare调用成功后上报该状态机
case 'prepared':
console.info('AVPlayer state prepared called.');
break
// play成功调用后触发该状态机上报
case 'playing':
console.info('AVPlayer state playing called.');
break
// pause成功调用后触发该状态机上报
case 'paused':
console.info('AVPlayer state paused called.');
break
// 播放结束后触发该状态机上报
case 'completed':
console.info('AVPlayer state completed called.');
break
// stop接口成功调用后触发该状态机上报
case 'stopped':
console.info('AVPlayer state stopped called.');
// 调用reset接口初始化avplayer状态
VideoAVPlayerClass.avPlayer.reset()
break
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
static async changePlay() {
// 将播放状态置为闲置
await VideoAVPlayerClass.avPlayer.reset()
VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
}
}
- 获取当前播放时长
通过监听播放器的timeUpdate事件,可以获取当前的播放时长。在播放器类中定义一个
timeUpdate变量来存储当前播放时长,然后监听timeUpdate事件获取视频当前播放时长。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// 当前播放器播放视频的总时长
static duration: number = 0
// 当前播放器播放的时长
static time: number = 0
// surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
static surfaceId: string = ''
// 创建播放器的方法
static async init(initParams: InitParams) {
// 存储属性SurfaceID,用于设置播放窗口,显示画面
VideoAVPlayerClass.surfaceId = initParams.surfaceId
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
// ----------------------- 事件监听 --------------------------------------------------------------
// 用于进度条,监听进度条长度,刷新资源时长
VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
console.info('AVPlayer state durationUpdate called. current time: ', duration);
// 获取视频总时长
VideoAVPlayerClass.duration = duration
})
// 用于进度条,监听进度条当前位置,刷新当前时间
VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
console.info('AVPlayer state timeUpdate called. current time: ', time);
// 获取当前播放时长
VideoAVPlayerClass.time = time
})
// 监听seek生效的事件
VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
VideoAVPlayerClass.avPlayer.play()
VideoAVPlayerClass.isPlay = true
})
// 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
VideoAVPlayerClass.avPlayer.on('error', (err) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
// 调用reset重置资源,触发idle状态
VideoAVPlayerClass.avPlayer.reset()
})
// 监听播放状态机AVPlayerState切换的事件
VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
switch (state) {
// 成功调用reset接口后触发该状态机上报
case 'idle':
console.info('AVPlayer state idle called.');
break
// avplayer 设置播放源后触发该状态上报
case 'initialized':
console.info('AVPlayerstate initialized called.');
// 设置显示画面,当播放的资源为纯音频时无需设置
VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
break
// prepare调用成功后上报该状态机
case 'prepared':
console.info('AVPlayer state prepared called.');
break
// play成功调用后触发该状态机上报
case 'playing':
console.info('AVPlayer state playing called.');
break
// pause成功调用后触发该状态机上报
case 'paused':
console.info('AVPlayer state paused called.');
break
// 播放结束后触发该状态机上报
case 'completed':
console.info('AVPlayer state completed called.');
break
// stop接口成功调用后触发该状态机上报
case 'stopped':
console.info('AVPlayer state stopped called.');
// 调用reset接口初始化avplayer状态
VideoAVPlayerClass.avPlayer.reset()
break
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
static async changePlay() {
// 将播放状态置为闲置
await VideoAVPlayerClass.avPlayer.reset()
VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
}
}
获取是否播放状态
页面需要根据是否播放状态来展示播放/暂停按钮,因此我们需要记录是否播放状态。在播放器类中定义isPlay变量来记录是否播放状态,默认是暂停状态。
import media from '@ohos.multimedia.media'
export class VideoAVPlayerClass {
// 创建的播放器应该存在我们的工具类上,这样才能被导出使用
static player: media.AVPlayer | null = null
// 当前播放器播放视频的总时长
static duration: number = 0
// 当前播放器播放的时长
static time: number = 0
// 当前播放器是否播放
static isPlay: boolean = false
// surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
static surfaceId: string = ''
// 创建播放器的方法
static async init(initParams: InitParams) {
// 存储属性SurfaceID,用于设置播放窗口,显示画面
VideoAVPlayerClass.surfaceId = initParams.surfaceId
// 创建播放器实例
VideoAVPlayerClass.player = await media.createAVPlayer()
// ----------------------- 事件监听 --------------------------------------------------------------
// 用于进度条,监听进度条长度,刷新资源时长
VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
console.info('AVPlayer state durationUpdate called. current time: ', duration);
// 获取视频总时长
VideoAVPlayerClass.duration = duration
})
// 用于进度条,监听进度条当前位置,刷新当前时间
VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
console.info('AVPlayer state timeUpdate called. current time: ', time);
// 获取当前播放时长
VideoAVPlayerClass.time = time
})
// 监听seek生效的事件
VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
VideoAVPlayerClass.avPlayer.play()
VideoAVPlayerClass.isPlay = true
})
// 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
VideoAVPlayerClass.avPlayer.on('error', (err) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
// 调用reset重置资源,触发idle状态
VideoAVPlayerClass.avPlayer.reset()
})
// 监听播放状态机AVPlayerState切换的事件
VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
switch (state) {
// 成功调用reset接口后触发该状态机上报
case 'idle':
console.info('AVPlayer state idle called.');
break
// avplayer 设置播放源后触发该状态上报
case 'initialized':
console.info('AVPlayerstate initialized called.');
// 设置显示画面,当播放的资源为纯音频时无需设置
VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
break
// prepare调用成功后上报该状态机
case 'prepared':
console.info('AVPlayer state prepared called.');
break
// play成功调用后触发该状态机上报
case 'playing':
console.info('AVPlayer state playing called.');
break
// pause成功调用后触发该状态机上报
case 'paused':
console.info('AVPlayer state paused called.');
break
// 播放结束后触发该状态机上报
case 'completed':
console.info('AVPlayer state completed called.');
break
// stop接口成功调用后触发该状态机上报
case 'stopped':
console.info('AVPlayer state stopped called.');
// 调用reset接口初始化avplayer状态
VideoAVPlayerClass.avPlayer.reset()
break
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
static async changePlay() {
// 将播放状态置为闲置
await VideoAVPlayerClass.avPlayer.reset()
VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
}
}
至此,我们实现了播放器的一些基本设置,还不能实现视频的播放,视频的播放功能实现将在下一篇《鸿蒙开发之视频播放器实现中篇》讲解