鸿蒙Next开发日记 Day02 - 使用AVPlayer制作一个简易播放器

AVPlayer:功能较完善的音视频播放ArkTS/JS API,集成了流媒体和本地资源解析,媒体资源解封装,视频解码和渲染功能,适用于对媒体资源进行端到端播放的场景,可直接播放mp4、mkv等格式的视频文件。
1、创建播放器的相关回调
// 注册avplayer回调函数
setAVPlayerCallback(avPlayer: media.AVPlayer) {
// startRenderFrame首帧渲染回调函数
avPlayer.on('startRenderFrame', () => {
console.info(`AVPlayer start render frame`);
})
// seek操作结果回调函数
avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
})
// error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
avPlayer.on('error', (err: BusinessError) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // 调用reset重置资源,触发idle状态
})
avPlayer.on('videoSizeChange', (width: number, height: number) => {
// 获取当前屏幕尺寸
const whData = ScreenUtil.getScreenSize() as ScreenWidthHeightData
if (whData.error) {
UiUtils.toast(whData.error)
} else {
// 计算视频展示区域
const screenWidth = whData.width
const screenHeight = whData.height
const videoWidth = width
const videoHeight = height
// 计算视频的宽高比
const videoAspectRatio = videoWidth / videoHeight;
// 根据屏幕宽高比和视频宽高比,确定展示组件的宽高
let displayWidth: number, displayHeight: number;
// 屏幕宽高比大于视频宽高比时,以屏幕高度为基准计算宽度
if (screenWidth / screenHeight > videoAspectRatio) {
displayHeight = screenHeight;
displayWidth = screenHeight * videoAspectRatio;
} else {
// 屏幕宽高比小于视频宽高比时,以屏幕宽度为基准计算高度
displayWidth = screenWidth;
displayHeight = screenWidth / videoAspectRatio;
}
console.log(screenWidth + '', screenHeight + '', videoWidth + '', videoHeight + '', displayWidth + '', displayHeight + '')
this.xComController.setXComponentSurfaceRect({ surfaceWidth: displayWidth, surfaceHeight: displayHeight });
}
});
// 状态机变化回调函数
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info('AVPlayer state idle called.');
avPlayer.release(); // 调用release接口销毁实例对象
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info('AVPlayer state initialized called.');
avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置
avPlayer.prepare();
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info('AVPlayer state prepared called.');
avPlayer.play(); // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info('AVPlayer state playing called.');
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info('AVPlayer state paused called.');
avPlayer.play(); // 再次播放接口开始播放
break;
case 'completed': // 播放结束后触发该状态机上报
console.info('AVPlayer state completed called.');
avPlayer.stop(); //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info('AVPlayer state stopped called.');
avPlayer.reset(); // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called:' + state);
break;
}
})
}2、渲染方法里面使用XComponent组件承载播放器画面。
XComponent组件作为一种绘制组件,通常用于满足开发者较为复杂的自定义绘制需求,例如相机预览流的显示和游戏画面的绘制。
setXComponentSurfaceRect设置视频绘制组件的大小不是必须的,在videoSizeChange回调中,我们可以获取到当前的视频尺寸,再根据当前手机尺寸,计算并设置视频绘制组件的宽高。
build() {
Column() {
XComponent({
id: 'videoXComponent',
type: 'surface',
controller: this.xComController
})
.onLoad(async () => {
this.xComController.setXComponentSurfaceRect({ surfaceWidth: 0, surfaceHeight: 0 }); // 设置的长宽不是最终的
this.surfaceID = this.xComController.getXComponentSurfaceId()
await this.preDownloadDemo()
})
}
.width("100%")
}3、加载视频并播放
async preDownloadDemo() {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
this.setAVPlayerCallback(this.avPlayer)
// 设置媒体来源和播放策略
this.avPlayer.url = this.mediaUrl
}上面是一个加载网络视频的方法,通过avPlayer.url设置过视频地址后,会回调initialized,继续后续操作。
4、添加页面销毁时的播放器释放
aboutToDisappear(): void {
// 销毁播放器
if (this.avPlayer) {
this.avPlayer.release()
}
}完整代码如下
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { router } from '@kit.ArkUI';
import { ScreenUtil } from '../../utils/ScreenUtil';
import { ScreenWidthHeightData } from '../../viewmodel/common/ScreenWidthHeightData';
import UiUtils from '../../utils/UiUtil';
@Entry
@Component
struct Player {
xComController: XComponentController = new XComponentController()
private surfaceID: string = ''; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法
@State isPlaying: boolean = false;
avPlayer: media.AVPlayer | undefined = undefined
routerParams: Record<string, Object> = router.getParams() as Record<string, Object>; // 获取传递过来的参数对象
@Prop mediaUrl: string = '你的视频地址';
// 注册avplayer回调函数
setAVPlayerCallback(avPlayer: media.AVPlayer) {
// startRenderFrame首帧渲染回调函数
avPlayer.on('startRenderFrame', () => {
console.info(`AVPlayer start render frame`);
})
// seek操作结果回调函数
avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
})
// error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
avPlayer.on('error', (err: BusinessError) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // 调用reset重置资源,触发idle状态
})
avPlayer.on('videoSizeChange', (width: number, height: number) => {
// 获取当前屏幕尺寸
const whData = ScreenUtil.getScreenSize() as ScreenWidthHeightData
if (whData.error) {
UiUtils.toast(whData.error)
} else {
// 计算视频展示区域
const screenWidth = whData.width
const screenHeight = whData.height
const videoWidth = width
const videoHeight = height
// 计算视频的宽高比
const videoAspectRatio = videoWidth / videoHeight;
// 根据屏幕宽高比和视频宽高比,确定展示组件的宽高
let displayWidth: number, displayHeight: number;
// 屏幕宽高比大于视频宽高比时,以屏幕高度为基准计算宽度
if (screenWidth / screenHeight > videoAspectRatio) {
displayHeight = screenHeight;
displayWidth = screenHeight * videoAspectRatio;
} else {
// 屏幕宽高比小于视频宽高比时,以屏幕宽度为基准计算高度
displayWidth = screenWidth;
displayHeight = screenWidth / videoAspectRatio;
}
console.log(screenWidth + '', screenHeight + '', videoWidth + '', videoHeight + '', displayWidth + '', displayHeight + '')
this.xComController.setXComponentSurfaceRect({ surfaceWidth: displayWidth, surfaceHeight: displayHeight });
}
});
// 状态机变化回调函数
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info('AVPlayer state idle called.');
avPlayer.release(); // 调用release接口销毁实例对象
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info('AVPlayer state initialized called.');
avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置
avPlayer.prepare();
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info('AVPlayer state prepared called.');
avPlayer.play(); // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info('AVPlayer state playing called.');
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info('AVPlayer state paused called.');
avPlayer.play(); // 再次播放接口开始播放
break;
case 'completed': // 播放结束后触发该状态机上报
console.info('AVPlayer state completed called.');
avPlayer.stop(); //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info('AVPlayer state stopped called.');
avPlayer.reset(); // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called:' + state);
break;
}
})
}
pause() {
if (this.avPlayer) {
this.avPlayer.pause()
this.isPlaying = false
}
}
play() {
if (this.avPlayer && !this.isPlaying ) {
this.avPlayer.play()
this.isPlaying = true;
}
}
aboutToDisappear(): void {
// 销毁播放器
if (this.avPlayer) {
this.avPlayer.release()
}
}
// 时长数字(ms)转字符串
number2time(number: number) {
if (!number) {
return '00:00'
}
const ms: number = number % 1000
const second = (number - ms) / 1000
const s: number = second % 60
if (second > 60) {
const m: number = (second - s) / 60 % 60
return m.toString()
.padStart(2, '0') + ':' + s.toString()
.padStart(2, '0')
}
return '00:' + s.toString()
.padStart(2, '0')
}
// 以下demo为通过setMediaSource设置自定义头域及媒体播放优选参数实现初始播放参数设置
async preDownloadDemo() {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
this.setAVPlayerCallback(this.avPlayer)
// 设置媒体来源和播放策略
this.avPlayer.url = this.mediaUrl
}
build() {
Column() {
XComponent({
id: 'videoXComponent',
type: 'surface',
controller: this.xComController
})
.onLoad(async () => {
this.xComController.setXComponentSurfaceRect({ surfaceWidth: 0, surfaceHeight: 0 }); // 设置的长宽不是最终的
this.surfaceID = this.xComController.getXComponentSurfaceId()
await this.preDownloadDemo()
})
}
.width("100%")
}
}
export default Playerexport class ScreenWidthHeightData {
width: number = 0
height: number = 0
error: string | undefined = undefined
}import { display } from '@kit.ArkUI';
import { ScreenWidthHeightData } from '../viewmodel/common/ScreenWidthHeightData';
export class ScreenUtil {
/**
* 获取屏幕尺寸
* @returns
*/
static getScreenSize(): ScreenWidthHeightData | string {
let displayClass: display.Display | null = null;
try {
displayClass = display.getDefaultDisplaySync()
const data: ScreenWidthHeightData = {
width: displayClass.width,
height: displayClass.height,
error: undefined
}
return data
} catch (exception) {
console.error('error:' + JSON.stringify(exception))
return {
width: 0,
height: 0,
error: '[异常]' + JSON.stringify(exception)
}
}
}
}<< 上一篇
下一篇 >>
网友留言(0 条)