写点什么

使用 avPlayer 和 xComponent 进行视频播放

作者:
  • 2025-05-26
    广东
  • 本文字数:3060 字

    阅读完需:约 10 分钟


介绍:该组件主要是封装 avPlayer 播放视频的工具类,结合 xComponent 组件,实现对视频的播放。

1. 组件原理

  • 核心功能:基于 avPlayer 和 xComponent 实现视频播放,集成横竖屏切换、AI 字幕、导航控制等功能。

  • 窗口管理:通过 WindowUtil 控制屏幕方向、系统栏显隐,实现沉浸式横屏播放。

  • 状态驱动:利用 @State 管理播放状态(横竖屏、退出标志),@StorageProp 存储安全区高度适配不同设备。

  • 模块化设计:将视频播放器(LVideoComponent)、AI 字幕(AICaptionComponent)封装为独立组件,通过属性注入交互逻辑。


2. 实现逻辑

  • 导航传参:通过 NavPathStack 获取上级页面传递的 VideoModel 数据(视频 URL、标题等)。

    生命周期aboutToAppear 初始化视频数据,准备播放资源。

  • 横屏模式:隐藏系统栏、锁定屏幕方向为 LANDSCAPE,进入沉浸式播放。

    竖屏模式:恢复系统栏、方向为 PORTRAIT,显示顶部安全区。

  • 视频播放控制LVideoComponent:核心播放器组件,接收 url 播放视频,通过 isScreenRow 控制 UI 适配横竖屏。

    事件回调leftIconClick 处理返回/退出全屏,fullScreenIconClick 切换横竖屏。

  • AI 字幕集成

状态控制:isShowAICaptionComponent 控制字幕组件显隐。

控制器:AICaptionController 管理字幕加载逻辑,通过 onPrepared 和 onError 回调处理状态。

  • 自定义返回逻辑:横屏时拦截返回键仅退出全屏,竖屏时正常返回上一页。

    路由管理:通过 exitPage 状态触发页面退出。


3. 实际应用场景

  • 视频播放器:用于 App 内视频详情页播放,支持横竖屏切换。

  • 全屏沉浸体验:适配影视类 App 的长视频播放场景。

  • 多语言支持:结合 AI 字幕实现实时翻译,适合教育、国际内容平台。


4. 优势

  • 沉浸式体验

    横屏自动隐藏系统栏,最大化视频显示区域。

    平滑的横竖屏切换动画(依赖系统实现)。

  • 模块解耦

    播放器(LVideoComponent)与字幕组件(AICaptionComponent)独立维护,通过接口交互。

    导航逻辑与播放逻辑分离,便于扩展。

  • 兼容性

    通过 WindowUtil 封装系统 API,统一处理不同设备的窗口操作。

    安全区高度动态适配,避免刘海屏/挖孔屏遮挡。


5. 视觉效果

  • 自适应布局

    横屏时顶部安全区高度为 0,视频区域全屏。

    竖屏时显示标题、控制栏,保留操作入口。

  • UI 反馈

    全屏按钮图标随状态切换(需在 LVideoComponent 实现)。

    标题过长时显示省略号(TextOverflow.Ellipsis)。


6. 可定制性

  • 扩展播放控件:通过在 LVideoComponent 中添加进度条、音量控制等 UI 元素。

  • 字幕样式:修改 AICaptionComponent 的字体、颜色、位置参数。

  • 主题适配:动态切换背景色(当前固定为 #ffeaeaea)。


7. 性能与适配

  • 性能优化

    avPlayer 硬解码提升视频流畅度,降低 CPU 占用。

    横竖屏切换时避免视频重新加载,保持播放连续性。

  • 内存管理

    页面退出时释放 avPlayer 资源(需在 aboutToDisappear 实现)。

    AI 字幕加载使用懒加载或分片加载策略。

  • 适配建议

    测试不同分辨率视频的缩放模式(如 covercontain)。

    处理屏幕旋转时的 UI 重绘卡顿(使用异步布局更新)。

8. 代码实现

import { media } from '@kit.MediaKit';import { promptAction } from '@kit.ArkUI';
export class AvPlayerController { @Track isPlaying: boolean = false; @Track currentTime: number = 0; @Track durationTime: number = 0; @Track currentBufferTime: number = 0; @Track videoAspectRatio: number = 16 / 9; private avPlayer?: media.AVPlayer; private surfaceId: string = ''; private url: string
constructor(url: string) { this.url = url }
async init() { this.avPlayer = await media.createAVPlayer();
this.setListener()
this.avPlayer.url = this.url }
private setListener() { //设置状态改变监听 this.avPlayer?.on('stateChange', (state) => { console.log('状态改变了,变为了' + state) if (state === 'initialized') { this.avPlayer!.surfaceId = this.surfaceId this.avPlayer?.prepare() } else if (state === 'prepared') { this.play() } else if (state === 'completed') { this.reset() } else if (state === 'error') { //如果状态变为error,播放失败,提示用户,并将播放器设置为闲置 this.reset() promptAction.showToast({ message: '播放失败,请检查播放源', duration: 5000 }) } }) //视频文件时长变化监听(视频文件切换) this.avPlayer?.on('durationUpdate', (duration: number) => { console.log('视频时长变化了' + duration) this.durationTime = duration }) //视频播放进度变化监听 this.avPlayer?.on('timeUpdate', (value) => { this.currentTime = value }) this.avPlayer?.on('videoSizeChange', (width: number, height: number) => { console.log('视频尺寸变化了' + width / height, width / height === 16 / 9) this.videoAspectRatio = width / height }) this.avPlayer?.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => { if (infoType === media.BufferingInfoType.BUFFERING_START) { console.log('开始缓存视频') } if (infoType === media.BufferingInfoType.BUFFERING_END) { console.log('视频缓存完成') } // console.log('视频缓存', infoType, value) this.currentBufferTime = value }) }
public setIsPlaying(isPlayer: boolean) { this.isPlaying = isPlayer; }
public setDurationTime(durationTime: number) { this.durationTime = durationTime; }
public setSurfaceID(surfaceId: string) { this.surfaceId = surfaceId; }
play() { this.avPlayer?.play() this.isPlaying = true }
pause() { this.avPlayer?.pause() this.isPlaying = false }
reset() { this.avPlayer?.reset() }

setSeek(value: number) { this.avPlayer?.seek(value) }
public releaseVideo() { if (this.avPlayer) { this.avPlayer.pause() this.avPlayer.off('stateChange'); this.avPlayer.off('durationUpdate'); this.avPlayer.off('timeUpdate'); this.avPlayer.off('videoSizeChange'); this.avPlayer.off('bufferingUpdate') this.avPlayer.release(); console.log('释放AvPlayer资源成功') } }}
复制代码

9.页面级部分代码

    Stack() {      XComponent({        controller: this.controller,        type: XComponentType.SURFACE,      })        .onLoad(() => {          this.init()          console.log('XComponent加载完成')        })        .onDestroy(() => {          console.log('XComponent销毁了')        })        .constraintSize({ maxWidth: '100%', maxHeight: '100%' })        .aspectRatio(this.avPlayerController.videoAspectRatio)
if (this.isShowSet) { this.videoSetBuilder() }
} .alignContent(Alignment.Center) .backgroundColor(Color.Black) .width(this.stackWidth) .height(this.stackHeight)
复制代码


用户头像

关注

还未添加个人签名 2025-05-06 加入

还未添加个人简介

评论

发布
暂无评论
使用avPlayer和xComponent进行视频播放_音视频技术_林_InfoQ写作社区