第2.5篇:视频播放器集成——Video 组件与 VideoController

难度:⭐ 入门 | 前置知识:1.2 ArkUI 声明式 UI 基础 | 涉及源文件products/default/src/main/ets/pages/RecognitionResultPage.etsproducts/default/src/main/ets/pages/Index.ets


在这里插入图片描述

一、引言

"画伴梦工厂"的核心产出是 AI 生成的动画视频。当涂鸦或照片经过 AI 处理后,用户需要一个美观、可控的视频播放器来观看结果。HarmonyOS 提供了功能完备的 Video 组件,配合 VideoController,可以轻松实现视频播放。

在项目中,视频播放器出现在两个关键位置:

  1. RecognitionResultPage —— 生成完成后展示结果,附带简单播放控制
  2. 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();
}

重播的逻辑分三步:

  1. 重置进度状态到 0
  2. 调用 setCurrentTime(0) 将播放头定位到开头
  3. 调用 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:当前播放进度(来自 onUpdateevent.time
  • total:视频总时长(来自 onPreparedevent.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 应用中构建专业、可控的视频播放体验了。


动手挑战:尝试为进度条添加拖动功能(通过 ProgressonClickonTouch 事件计算目标时间点,然后调用 videoController.setCurrentTime())。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐