鸿蒙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 Player
export 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 条)