A hand-drawn doodle illustration on pure white pap

前言

视频播放这功能,看着简单——不就是个 Video 组件嘛。但真要做得像样,自定义控制器、全屏切换、手势控进度,一个比一个折腾。HarmonyOS7 自带的 Video 组件默认控制器功能有限,样式也不可控。想要自己的播放器 UI,就得手动搞一套。这篇文章从最基础的用法讲起,一路到自定义控制器 + 手势控制,够你做出一个像样的视频播放器。

我的目标是:看完这篇,你能做出一个带自定义进度条、播放/暂停按钮、全屏切换、手势拖动进度的视频播放器。HarmonyOS7 的 Video 组件本身能力是够的,关键在于怎么把默认控制器关掉,自己接管所有交互逻辑。

Video 组件基础

最简单的用法,三行代码就能播放视频:

@State videoSrc: Resource = $rawfile('demo.mp4');
controller: VideoController = new VideoController();

Video({ src: this.videoSrc, controller: this.controller })
  .width('100%')
  .height(300)

支持的视频源类型:

类型 示例
本地 rawfile $rawfile('video.mp4')
网络地址 'https://example.com/video.mp4'
沙箱 URI 'file:///data/storage/.../video.mp4'

用网络地址需要在 module.json5 里申请 ohos.permission.INTERNET 权限。

Video 组件的常用属性:

Video({ src: this.videoSrc, controller: this.controller })
  .autoPlay(false)
  .loop(false)
  .controls(true)
  .objectFit(ImageFit.Contain)
  .onStart(() => { console.info('开始播放'); })
  .onPause(() => { console.info('暂停'); })
  .onFinish(() => { console.info('播放结束'); })
  .onPrepared((e) => {
    if (e) console.info(`视频时长: ${e.duration}`);
  })
  .onUpdate((e) => {
    if (e) console.info(`当前进度: ${e.time}`);
  })
  .onFullscreenChange((e) => {
    if (e) console.info(`全屏状态: ${e.fullscreen}`);
  })

属性讲解:

  • autoPlay — 是否自动播放,默认 false
  • loop — 是否循环播放
  • controls是否显示默认控制器,自定义控制器时要设为 false
  • objectFit — 视频填充模式,Contain 保持比例不裁剪
  • onPrepared — 视频加载完成,e.duration 是总时长
  • onUpdate — 播放进度回调,e.time 是当前秒数

自定义播放控制

默认控制器关掉后(controls(false)),所有交互都得自己实现。先看完整代码:

@Component
export struct CustomVideoPlayer {
  @State videoSrc: Resource = $rawfile('demo.mp4');
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;
  @State showControls: boolean = true;
  controller: VideoController = new VideoController();

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Video({ src: this.videoSrc, controller: this.controller })
        .width('100%')
        .height(300)
        .controls(false)
        .autoPlay(false)
        .onPrepared((e) => {
          if (e) this.duration = e.duration;
        })
        .onUpdate((e) => {
          if (e) this.currentTime = e.time;
        })
        .onFinish(() => {
          this.isPlaying = false;
        })
        .onClick(() => {
          this.showControls = !this.showControls;
        })

      if (this.showControls) {
        Column() {
          Slider({
            value: this.currentTime,
            min: 0,
            max: this.duration,
            step: 1
          })
            .width('90%')
            .onChange((value) => {
              this.controller.setCurrentTime(value);
            })

          Row({ space: 20 }) {
            Button(this.isPlaying ? '暂停' : '播放')
              .onClick(() => {
                if (this.isPlaying) {
                  this.controller.pause();
                } else {
                  this.controller.start();
                }
                this.isPlaying = !this.isPlaying;
              })
            Text(`${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)}`)
              .fontColor(Color.White)
              .fontSize(12)
          }
          .width('90%')
          .justifyContent(FlexAlign.Center)
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#88000000')
      }
    }
  }

  formatTime(seconds: number): string {
    let min = Math.floor(seconds / 60);
    let sec = Math.floor(seconds % 60);
    return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
  }
}

逐行讲解自定义控制器核心逻辑:

  1. controls(false) — 关掉默认控制器,这是自定义的前提
  2. onClick 切换 showControls — 点击视频区域显示/隐藏控制栏
  3. Slider 组件绑定 currentTimeduration — 进度条跟随播放进度实时更新
  4. onChange 里调用 controller.setCurrentTime(value) — 用户拖动进度条时跳转播放位置
  5. 播放/暂停按钮通过 controller.start() / controller.pause() 控制
  6. formatTime 把秒数格式化成 mm:ss 格式

关键点:isPlaying 状态要自己维护。onFinish 时别忘了置为 false,不然按钮显示会错。

全屏切换

全屏切换用 controller.requestFullscreen() 方法:

@State isFullscreen: boolean = false;

Button(this.isFullscreen ? '退出全屏' : '全屏')
  .onClick(() => {
    if (this.isFullscreen) {
      this.controller.exitFullscreen();
    } else {
      this.controller.requestFullscreen(true);
    }
  })

// 监听全屏状态变化
.onFullscreenChange((e) => {
  if (e) this.isFullscreen = e.fullscreen;
})

讲解:

  • requestFullscreen(true) — 参数 true 表示横屏全屏,false 为竖屏
  • exitFullscreen() — 退出全屏
  • 必须监听 onFullscreenChange 来同步状态,因为用户也可能通过系统手势退出全屏

全屏时视频容器的尺寸也要跟着变:

Video({ src: this.videoSrc, controller: this.controller })
  .width(this.isFullscreen ? '100%' : '100%')
  .height(this.isFullscreen ? '100%' : 300)

小坑:全屏状态下,自定义控制栏的布局可能需要调整。建议全屏时把控制栏放在底部,加个半透明背景。

手势控制进度

拖动手势调进度,这是短视频 App 的标配功能。用 PanGesture 实现:

@State panStartTime: number = 0;
@State panDeltaX: number = 0;

Video({ src: this.videoSrc, controller: this.controller })
  .controls(false)
  .gesture(
    PanGesture({ fingers: 1, direction: PanDirection.HORIZONTAL })
      .onActionStart(() => {
        this.panStartTime = this.currentTime;
        this.showControls = false;
      })
      .onActionUpdate((e) => {
        // 水平滑动距离转换为时间偏移
        let totalWidth = 300; // 视频容器宽度
        let deltaSeconds = (e.offsetX / totalWidth) * this.duration;
        let targetTime = Math.max(0, Math.min(this.duration, this.panStartTime + deltaSeconds));
        this.currentTime = targetTime;
      })
      .onActionEnd(() => {
        this.controller.setCurrentTime(this.currentTime);
        this.showControls = true;
      })
  )

逐行讲解:

  • PanDirection.HORIZONTAL — 只响应水平方向的手势
  • onActionStart 记录拖动开始时的播放时间
  • onActionUpdate 根据手指水平偏移量计算新的播放位置。偏移量 / 容器宽度 × 总时长 = 时间偏移
  • onActionEnd 手指抬起后,调用 setCurrentTime 跳到目标位置
  • Math.max(0, Math.min(this.duration, ...)) 防止进度超出范围

性能优化

视频播放是吃资源的大户,几个优化建议:

  1. 及时释放控制器:页面销毁时调用 controller.stop() 释放资源
  2. 预加载封面图:用 previewUri 设置视频封面,避免黑屏等待
Video({ src: this.videoSrc, controller: this.controller, previewUri: $rawfile('cover.jpg') })
  1. 按需加载:只有视频进入可视区域时才初始化播放器
  2. 避免同时播放多个视频:同时解码多个视频流非常吃内存
  3. 网络视频用 HLS 协议:支持分段加载,起播更快

写在最后

自定义视频播放器确实比直接用默认控制器费劲不少,但好处是 UI 完全可控,交互想怎么设计都行。我建议先从基础用法入手,跑通了再一步步加自定义控制、全屏、手势。一口吃不成胖子,但一步步来,最后的效果肯定比默认的好看。

Logo

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

更多推荐