HarmonyOS APP《画伴梦工厂》开发第13篇:视频播放器集成——Video 组件与 VideoController
第2.5篇:视频播放器集成——Video 组件与 VideoController
难度:⭐ 入门 | 前置知识:1.2 ArkUI 声明式 UI 基础 | 涉及源文件:
products/default/src/main/ets/pages/RecognitionResultPage.ets、products/default/src/main/ets/pages/Index.ets

一、引言
"画伴梦工厂"的核心产出是 AI 生成的动画视频。当涂鸦或照片经过 AI 处理后,用户需要一个美观、可控的视频播放器来观看结果。HarmonyOS 提供了功能完备的 Video 组件,配合 VideoController,可以轻松实现视频播放。
在项目中,视频播放器出现在两个关键位置:
- RecognitionResultPage —— 生成完成后展示结果,附带简单播放控制
- Index.ets(作品详情页) —— 更完整的播放体验,含进度条和时间显示
本文将从这两个场景入手,系统讲解 Video 组件的使用。
二、Video 组件基础
2.1 组件属性(Props)
Video({
src: this.videoUri, // 视频源 URI
previewUri: this.getPreviewUri(), // 预览封面图
controller: this.videoController // 控制器
})
| 属性 | 类型 | 说明 |
|---|---|---|
src |
string / Resource |
视频文件路径,支持本地路径、网络 URL 或 rawfile 资源 |
previewUri |
string / Resource |
视频封面图,加载完成前或未播放时显示 |
controller |
VideoController |
控制器,用于编程控制播放 |
2.2 配置选项
Video({ ... })
.width('100%')
.height(240)
.objectFit(ImageFit.Cover) // 视频画面裁剪方式
.controls(false) // 隐藏系统默认控制条
.autoPlay(true) // 自动播放
.loop(false) // 不循环播放
- controls(false):隐藏默认控制栏,实现自定义 UI。项目中统一使用自定义播放按钮,保持视觉风格统一。
- autoPlay(true):页面打开即自动播放,符合"生成即观看"的体验预期。
- objectFit(ImageFit.Cover):视频画面按比例填充容器,超出部分裁剪,类似于 CSS 的
object-fit: cover。
三、事件监听体系
Video 组件提供丰富的事件回调,覆盖播放全生命周期:
| 事件 | 触发时机 | 项目中的用途 |
|---|---|---|
onStart |
视频开始播放 | 更新 isPlaying = true |
onPause |
视频暂停 | 更新 isPlaying = false |
onFinish |
视频播放结束 | 更新 isPlaying = false |
onError |
视频加载/播放出错 | 更新 isPlaying = false,显示提示 |
onPrepared |
视频加载完成(元数据就绪) | 获取视频总时长 event.duration |
onUpdate |
播放进度更新 | 更新时间进度 event.time |
3.1 基础事件(RecognitionResultPage)
Video({ ... })
.onStart(() => { this.isPlaying = true; })
.onPause(() => { this.isPlaying = false; })
.onFinish(() => { this.isPlaying = false; })
.onError(() => { this.isPlaying = false; })
这是最精简的事件绑定,仅跟踪播放/暂停状态,用于切换播放按钮的图标。
3.2 完整事件(Index.ets 作品详情页)
Video({ ... })
.onPrepared((event: PreparedInfo) => {
this.videoDuration = Math.max(1, event.duration);
if (this.isPlaying) { this.videoController.start(); }
})
.onUpdate((event: PlaybackInfo) => {
this.videoProgress = Math.min(this.videoDuration, event.time);
})
.onStart(() => { this.isPlaying = true; })
.onPause(() => { this.isPlaying = false; })
.onFinish(() => { this.isPlaying = false; })
.onError(() => { this.isPlaying = false; })
onPrepared 事件详解:
PreparedInfo.duration 是视频的总时长,单位为秒(s)。这里用 Math.max(1, event.duration) 防止除零或时长为 0 导致进度条异常。
onUpdate 事件详解:
PlaybackInfo.time 是当前播放位置(秒)。Math.min(this.videoDuration, event.time) 做上限裁剪,防止进度溢出。
四、VideoController 编程控制
VideoController 是控制视频播放的核心 API 对象:
private videoController: VideoController = new VideoController();
4.1 核心 API
| 方法 | 说明 | 使用场景 |
|---|---|---|
start() |
开始/恢复播放 | 用户点击播放按钮 |
pause() |
暂停播放 | 用户点击暂停按钮 |
stop() |
停止播放 | 切换视频时重置状态 |
setCurrentTime(time) |
跳转到指定时间位置 | 重播时回到 0 秒 |
4.2 播放/暂停切换
private toggleVideoPlayback() {
this.isPlaying = !this.isPlaying;
if (this.isPlaying) {
this.videoController.start();
} else {
this.videoController.pause();
}
}
通过 isPlaying 状态决定调用 start() 还是 pause(),同时更新 UI 图标。
4.3 重播功能
private replayVideo() {
this.videoProgress = 0;
this.isPlaying = true;
this.videoController.setCurrentTime(0);
this.videoController.start();
}
重播的逻辑分三步:
- 重置进度状态到 0
- 调用
setCurrentTime(0)将播放头定位到开头 - 调用
start()开始播放
注意:必须先调用 setCurrentTime(0) 再 start(),否则如果视频已在末尾,start() 可能不会重新播放。
五、自定义播放控制 UI
5.1 RecognitionResultPage 的简约方案
@Builder
private VideoResult() {
Column() {
Stack({ alignContent: Alignment.BottomStart }) {
Video({
src: this.videoUri,
previewUri: this.getPreviewUri(),
controller: this.videoController
})
.width('100%')
.height(240)
.objectFit(ImageFit.Cover)
.controls(false)
.autoPlay(true)
.loop(false)
.onStart(() => { this.isPlaying = true; })
.onPause(() => { this.isPlaying = false; })
.onFinish(() => { this.isPlaying = false; })
.onError(() => { this.isPlaying = false; })
Row() {
Text(this.isPlaying ? 'II' : '>')
.fontSize(18)
.fontColor('#FFFFFF')
.onClick(() => {
if (this.isPlaying) {
this.videoController.pause();
} else {
this.videoController.start();
}
})
Text('已保存到作品')
.fontSize(12)
.fontColor('#FFFFFF')
.margin({ left: 10 })
}
.padding({ left: 14, right: 14, top: 8, bottom: 8 })
.backgroundColor('#66000000')
.borderRadius(18)
.margin({ left: 14, bottom: 14 })
}
.width('90%')
.height(240)
.borderRadius(18)
.clip(true)
.margin({ top: 12 })
}
}
设计特点:
- 视频控件和播放控制按钮放在
Stack中,控制按钮覆盖在视频底部左侧 - 使用半透明背景
#66000000让控制按钮在任何视频画面上都清晰可见 - 用
Text组件模拟播放/暂停图标(II表示暂停,>表示播放) - 外层
Stack设置了.clip(true),确保视频圆角裁剪
5.2 Index.ets 的完整方案
Stack({ alignContent: Alignment.BottomStart }) {
this.AnimationVideoScene() // 内置 Video 组件
Row() {
Text(this.isPlaying ? 'II' : '>')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.onClick(() => { this.toggleVideoPlayback(); })
Text(this.formatVideoTime(this.videoProgress))
.fontSize(11)
.fontColor('#FFFFFF')
.margin({ left: 8, right: 8 })
Progress({ value: this.videoProgress, total: this.videoDuration, type: ProgressType.Linear })
.layoutWeight(1)
.height(4)
.color('#FFFFFF')
.backgroundColor('#66FFFFFF')
Text(this.formatVideoTime(this.videoDuration))
.fontSize(11)
.fontColor('#FFFFFF')
.margin({ left: 8 })
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 14 })
}
升级特性:
- 播放/暂停按钮:同 RecognitionResultPage
- Progress 进度条:显示当前播放进度,用户可直观感知播放位置
- 时间标签:左侧显示当前时间,右侧显示总时长
六、Progress 组件做播放进度条
Progress({ value: this.videoProgress, total: this.videoDuration, type: ProgressType.Linear })
.layoutWeight(1)
.height(4)
.color('#FFFFFF')
.backgroundColor('#66FFFFFF')
工作流程:
视频实际播放 → onUpdate 回调 → event.time → setState videoProgress → Progress 自动刷新
value:当前播放进度(来自onUpdate的event.time)total:视频总时长(来自onPrepared的event.duration)type: ProgressType.Linear:线性进度条样式- 白色半透明背景
#66FFFFFF和白色前景#FFFFFF,在任何视频画面上都有良好的对比度
时间格式化工具:
private formatVideoTime(time: number): string {
const seconds = Math.max(0, Math.floor(time));
const minute = Math.floor(seconds / 60);
const second = seconds % 60;
return '0' + minute.toString() + ':' + (second < 10 ? '0' : '') + second.toString();
}
将秒数转换为 00:00 格式,处理了小于 10 秒的前导零。
七、视频源管理
项目中的视频来源有两种:
7.1 本地演示视频(rawfile)
private getWorkVideo(index: number): Resource | string {
switch (index) {
case 0: case 3: return $rawfile('index/demo1.mp4');
case 1: case 4: return $rawfile('index/demo2.mp4');
default: return $rawfile('index/demo3.mp4');
}
}
通过 $rawfile() 引用 resources/rawfile 目录下的视频资源。
7.2 AI 生成的视频(动态 URI)
// RecognitionResultPage 中
Video({
src: this.videoUri, // 从上一个页面通过 Router params 传递
...
})
videoUri 由 AI 生成服务返回,可能是本地文件路径或网络 URL。
7.3 预览封面图
private getPreviewUri(): Resource | string {
if (this.coverUri !== '') {
return this.coverUri;
}
return $r('app.media.dino_animation');
}
优先使用用户上传的封面图,若无则使用默认资源图。
八、常见问题与最佳实践
8.1 视频不自动播放
检查以下几点:
autoPlay(true)是否设置- 在某些 HarmonyOS 版本上,需在
onPrepared回调中显式调用start()
8.2 切换视频时状态重置
private selectWork(index: number) {
this.selectedWorkIndex = index;
this.videoProgress = 0;
this.videoDuration = 30;
this.isPlaying = true;
this.videoController.stop(); // 先停止当前视频
}
切换视频前必须重置所有播放状态,并调用 stop()。
8.3 视频循环
虽然 loop(false),但在 Index.ets 的 onFinish 中并未设置自动重播。用户可通过"重播"按钮手动触发 replayVideo()。
8.4 错误处理最佳实践
.onError(() => {
this.isPlaying = false;
this.showNotice('视频加载失败,请检查网络');
})
在 Index.ets 中,onError 还调用了 showNotice 向用户展示友好的错误提示。
九、总结
本文完整介绍了 HarmonyOS Video 组件的使用,通过项目中两个实际场景展示了不同复杂度的集成方案:
| 特性 | RecognitionResultPage | Index.ets 作品详情页 |
|---|---|---|
| 播放/暂停控制 | ✅ | ✅ |
| 进度条 | ❌ | ✅ |
| 时间显示 | ❌ | ✅ |
| 重播按钮 | ❌ | ✅ |
| 自动播放 | ✅ | ✅ |
| 自定义控制 UI | ✅ | ✅ |
核心要点回顾:
Video+VideoController是 HarmonyOS 视频播放的标准组合- 通过
controls(false)隐藏默认控件,实现自定义 UI - 利用
onPrepared/onUpdate事件驱动 Progress 进度条 - 切换视频时必须重置所有状态
- 注意资源管理和错误处理
掌握了这些知识,你就可以在任何 HarmonyOS 应用中构建专业、可控的视频播放体验了。
动手挑战:尝试为进度条添加拖动功能(通过
Progress的onClick或onTouch事件计算目标时间点,然后调用videoController.setCurrentTime())。
更多推荐


所有评论(0)