如何使用OpenHarmony实现视频暂停、播放、切换、倍速播放

介绍

本篇Codelab使用ArkTS语言实现视频播放器,主要包括主页面和视频播放页面,我们将一起完成以下功能:

  1. 获取本地视频和网络视频。
  2. 通过AVPlayer进行视频播放。
  3. 通过手势调节屏幕亮度和视频播放音量。

相关概念

  • AVPlayer:播放管理类,用于管理和播放媒体资源。
  • XComponent:可用于EGL/OpenGLES和媒体数据写入,并显示在XComponent组件。
  • PanGesture手势:用于触发拖动手势事件,滑动的最小距离为5vp时拖动手势识别成功。

相关权限

本篇Codelab使用了网络连接,需要在配置文件module.json5文件里添加权限:ohos.permission.INTERNET。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:润和RK3568开发板。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:

2.搭建烧录环境。

  1. 完成DevEco Device Tool的安装
  2. 完成RK3568开发板的烧录

3.搭建开发环境。

  1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
  2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择"Empty Ability")。
  3. 工程创建完成后,选择使用真机进行调测。

代码结构解读

本篇Codelab只对核心代码进行讲解。

├──entry/src/main/ets	                   // 代码区
│  ├──common
│  │  ├──constants
│  │  │  ├──CommonConstants.ets	           // 公共常量类
│  │  │  ├──HomeConstants.ets	           // 首页常量类
│  │  │  └──PlayConstants.ets	           // 视频播放页面常量类
│  │  ├──model
│  │  │  ├──HomeTabModel.ets	           // 首页参数模型
│  │  │  └──PlayerModel.ets	               // 播放参数模型
│  │  └──util
│  │     ├──DateFormatUtil.ets	           // 日期工具类
│  │     ├──GlobalContext.ets	           // 全局工具类
│  │     ├──Logger.ets	                   // 日志工具类
│  │     └──ScreenUtil.ets                 // 屏幕工具类
│  ├──controller
│  │  └──VideoController.ets	           // 视频控制类
│  ├──entryability
│  │  └──EntryAbility.ts                   // 程序入口类
│  ├──pages
│  │  ├──HomePage.ets                      // 首页页面
│  │  └──PlayPage.ets                      // 视频播放页面
│  ├──view
│  │  ├──HomeTabContent.ets                // 首页Tab页面
│  │  ├──HomeTabContentButton.ets          // 首页按钮子组件
│  │  ├──HomeTabContentDialog.ets          // 添加网络视频弹框子组件
│  │  ├──HomeTabContentList.ets            // 视频列表子组件
│  │  ├──HomeTabContentListItem.ets        // 视频对象子组件
│  │  ├──PlayControl.ets                   // 播放控制子组件
│  │  ├──PlayPlayer.ets                    // 视频播放子组件
│  │  ├──PlayProgress.ets                  // 播放进度子组件
│  │  ├──PlayTitle.ets                     // 播放标题子组件
│  │  └──PlayTitleDialog.ets               // 播放速度设置子组件
│  └──viewmodel
│     ├──HomeDialogModel.ets         	   // 添加网络视频弹框类
│     ├──HomeVideoListModel.ets            // 获取视频列表数据类
│     ├──VideoItem.ets         	           // 视频对象
│     └──VideoSpeed.ets                    // 播放速度类
└──entry/src/main/resource                 // 应用静态资源目录

获取视频

视频来源主要有本地视和网络视频两种方式,效果如图所示:

获取本地视频,通过resourceManager.getRawFd方法获取rawfile文件夹中的视频资源文件描述符,构造本地视频对象。

// HomeVideoListModel.ets
// 获取本地视频
async getLocalVideo() {
  this.videoLocalList = [];
  await this.assemblingVideoBean();
  GlobalContext.getContext().setObject('videoLocalList', this.videoLocalList);
  return this.videoLocalList;
}

// HomeVideoListModel.ets
// 组装本地视频对象
async assemblingVideoBean() {
  VIDEO_DATA.forEach(async (item: VideoItem) => {
    let videoBean = await getContext().resourceManager.getRawFd(item.iSrc);
    let uri = videoBean;
    this.videoLocalList.push(new VideoItem(item.name, uri, ''));
  });
}

网络视频是通过手动输入地址,在有网的环境下点击"链接校验",通过地址获取视频时长,当视频时长小于等于零时弹出"链接校验失败"提示,否则弹出"链接校验成功"提示。

// HomeDialogModel.ets
// 设置网络视频路径
async checkSrcValidity(checkFlag: number) {
  if (this.isLoading) {
    return;
  }
  this.isLoading = true;
  this.homeTabModel.linkCheck = $r('app.string.link_checking');
  this.homeTabModel.loadColor = $r('app.color.index_tab_unselected_font_color');
  this.checkFlag = checkFlag;
  this.createAvPlayer();
}

// 校验链接有效性
checkUrlValidity() {
  this.isLoading = false;
  this.homeTabModel.linkCheck = $r('app.string.link_check');
  this.homeTabModel.loadColor = $r('app.color.index_tab_selected_font_color');
  if (this.avPlayer !== null) {
    this.avPlayer.release();
  }
  if (this.duration === HomeConstants.DURATION_TWO) {
    // Failed to verify the link
    this.showPrompt($r('app.string.link_check_fail'));
  } else if (this.duration === HomeConstants.DURATION_ONE) {
    // The address is incorrect or no network is available
    this.showPrompt($r('app.string.link_check_address_internet'));
  } else {
    this.duration = 0;
    if (this.checkFlag === 0) {
      this.showPrompt($r('app.string.link_check_success'));
    } else {
      this.homeTabModel!.confirm();
      this.homeTabModel!.controller!.close();
    }
  }
}

视频播放

视频播放主要包括视频的暂停、播放、切换、倍速播放、拖动进度条设置当前进度、显示当前播放时间、音量调节等功能,本章节主要针对播放管理类(下面简称:AVPlayer)进行讲解,具体细节请参考gitee源码,效果如图所示:

播放的全流程包含:创建AVPlayer,设置播放资源,设置播放参数(音量/倍速),播放控制(播放/暂停/上一个视频/下一个视频),重置,销毁资源。状态机变化如图所示:

视频播放之前需要初始化XComponent组件用于展示视频画面。XComponent组件初始化成功之后在onLoad()中获取surfaceID用于与AVPlayer实例关联。

// PlayPlayer.ets
XComponent({
  ...
  controller: this.xComponentController
})
  .onLoad(async () => {
    ...
    this.surfaceID = this.xComponentController.getXComponentSurfaceId();
    ...
  })
  ...

使用AVPlayer前需要通过createAVPlayer()构建一个实例对象,并为AVPlayer实例绑定状态机,状态机具体请参考AVPlayerState。

// VideoController.ets
async createAVPlayer() {
  let avPlayer: media.AVPlayer = await media.createAVPlayer();
  this.avPlayer = avPlayer;
  this.bindState();
}

// VideoController.ets
async bindState() {
  if (this.avPlayer === null) {
    return;
  }
  this.avPlayer.on(Events.STATE_CHANGE, async (state: media.AVPlayerState) => {
    let avplayerStatus: string = state;
    if (this.avPlayer === null) {
      return;
    }
    switch (avplayerStatus) {
      case AvplayerStatus.IDLE:
        ...
      case AvplayerStatus.INITIALIZED:
        ...
      case AvplayerStatus.PREPARED:
        ...
      case AvplayerStatus.PLAYING:
        ...
      case AvplayerStatus.PAUSED:
        ...
      case AvplayerStatus.COMPLETED:
        ...
      case AvplayerStatus.RELEASED:
        ...
      default:
        ...
    }
  });
  this.avPlayer.on(Events.TIME_UPDATE, (time: number) => {
    this.initProgress(time);
  });
  this.avPlayer.on(Events.ERROR, () => {
    this.playError();
  })
}

AVPlayer实例需设置播放路径和XComponent中获取的surfaceID,设置播放路径之后AVPlayer状态机变为initialized状态,在此状态下调用prepare(),进入prepared状态。

// VideoController.ets
async firstPlay(index: number, url: resourceManager.RawFileDescriptor, iUrl: string, surfaceId: string) {
  this.index = index;
  this.url = url;
  this.iUrl = iUrl;
  this.surfaceId = surfaceId;
  if (this.avPlayer === null) {
    await this.createAVPlayer();
  }
  if (this.avPlayer !== null) {
    if (this.iUrl) {
      this.avPlayer.url = this.iUrl;
    } else {
      this.avPlayer.fdSrc = this.url;
    }
  }
}

// VideoController.ets
async bindState() {
  ...
  this.avPlayer.on(Events.STATE_CHANGE, async (state: media.AVPlayerState) => {
    let avplayerStatus: string = state;
    if (this.avPlayer === null) {
      return;
    }
    switch (avplayerStatus) {
      case AvplayerStatus.IDLE:
        ...
      case AvplayerStatus.INITIALIZED:
        this.avPlayer.surfaceId = this.surfaceId;
        this.avPlayer.prepare();
        break;
      ...
    }
  });
  ...
}

在prepared状态下可获取当前播放路径对应视频的总时长,并执行play()进行视频播放。

// VideoController.ets
async bindState() {
  ...
  this.avPlayer.on(Events.STATE_CHANGE, async (state: media.AVPlayerState) => {
    ...
    switch (avplayerStatus) {
      ...
      case AvplayerStatus.PREPARED:
        this.avPlayer.videoScaleType = 0;
        this.setVideoSize();
        this.avPlayer.play();
        this.duration = this.avPlayer.duration;
        break;
      ...
    }
  });
  ...
}

视频播放后,变为playing状态,可通过"播放/暂停"按钮切换播放状态,当视频暂停时状态机变为paused状态。

// VideoController.ets
switchPlayOrPause() {
  if (this.avPlayer === null) {
    return;
  }
  if (this.status === CommonConstants.STATUS_START) {
    this.avPlayer.pause();
  } else {
    this.avPlayer.play();
  }
}

// VideoController.ets
async bindState() {
  ...
  this.avPlayer.on(Events.STATE_CHANGE, async (state: media.AVPlayerState) => {
    ...
    switch (avplayerStatus) {
      ...
      case AvplayerStatus.PLAYING:
        this.avPlayer.setVolume(this.playerModel.volume);
        this.setBright();
        this.status = CommonConstants.STATUS_START;
        this.watchStatus();
        break;
      ...
    }
  });
  ...
}

可拖动进度条设置视频播放位置,也可滑动音量调节区域设置视频播放音量、设置播放速度。

// VideoController.ets
// 设置当前播放位置
setSeekTime(value: number, mode: SliderChangeMode) {
  if (mode === Number(SliderMode.MOVING)) {
    this.playerModel.progressVal = value;
    this.playerModel.currentTime = DateFormatUtil.secondToTime(Math.floor(value * this.duration /
    CommonConstants.ONE_HUNDRED / CommonConstants.A_THOUSAND));
  }
  if (mode === Number(SliderMode.END) || mode === Number(SliderMode.CLICK)) {
    this.seekTime = value * this.duration / CommonConstants.ONE_HUNDRED;
    if (this.avPlayer !== null) {
      this.avPlayer.seek(this.seekTime, media.SeekMode.SEEK_PREV_SYNC);
    }
  }
}

// VideoController.ets
// 设置播放音量
onVolumeActionUpdate(event?: GestureEvent) {
  if (!event) {
    return;
  }
  if (this.avPlayer === null) {
    return;
  }
  if (CommonConstants.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
    return;
  }
  if (this.playerModel.brightShow === false) {
    this.playerModel.volumeShow = true;
    let screenWidth = GlobalContext.getContext().getObject('screenWidth') as number;
    let changeVolume = (event.offsetX - this.positionX) / screenWidth;
    let volume: number = this.playerModel.volume;
    let currentVolume = volume + changeVolume;
    let volumeMinFlag = currentVolume <= PlayConstants.MIN_VALUE;
    let volumeMaxFlag = currentVolume > PlayConstants.MAX_VALUE;
    this.playerModel.volume = volumeMinFlag ? PlayConstants.MIN_VALUE :
      (volumeMaxFlag ? PlayConstants.MAX_VALUE : currentVolume);
    this.avPlayer.setVolume(this.playerModel.volume);
    this.positionX = event.offsetX;
  }
}

// VideoController.ets
// 设置播放速度
setSpeed(playSpeed: number) {
  if (this.avPlayer === null) {
    return;
  }
  if (CommonConstants.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
    return;
  }
  this.playerModel.playSpeed = playSpeed;
  this.avPlayer.setSpeed(this.playerModel.playSpeed);
}

视频播放完成之后,进入completed状态,需调用reset()对视频进行重置,此时变为idle转态,在idle状态下设置下一个视频的播放地址,又会进入initialized状态。

// VideoController.ets 
sync bindState() {
  ...
  this.avPlayer.on(Events.STATE_CHANGE, async (state: media.AVPlayerState) => {
    let avplayerStatus: string = state;
    ...
    switch (avplayerStatus) {
      case AvplayerStatus.IDLE:
        this.resetProgress();
        if (this.iUrl) {
          this.avPlayer.url = this.iUrl;
        } else {
          this.avPlayer.fdSrc = this.url;
        }
        break;
      case AvplayerStatus.INITIALIZED:
        this.avPlayer.surfaceId = this.surfaceId;
        this.avPlayer.prepare();
        break;
      ...
      case AvplayerStatus.COMPLETED:
        ...
        this.avPlayer.reset();
        break;
      ...
    }
  });
  ...
}

手势控制

播放页面通过绑定平移手势(PanGesture),上下滑动调节屏幕亮度,左右滑动调节视频音量,效果如图所示:

// PlayPage.ets
Column() {
  ...
  Column()
    ...
    .gesture(
      PanGesture(this.panOptionBright)
        .onActionStart((event?: GestureEvent) => {
          this.playVideoModel.onBrightActionStart(event);
        })
        .onActionUpdate((event?: GestureEvent) => {
          this.playVideoModel.onBrightActionUpdate(event);
        })
        .onActionEnd(() => {
          this.playVideoModel.onActionEnd();
        })
    )
  ...
  Column()
    ...
    .gesture(
      PanGesture(this.panOptionVolume)
        .onActionStart((event?: GestureEvent) => {
          this.playVideoModel.onVolumeActionStart(event);
        })
        .onActionUpdate((event?: GestureEvent) => {
          this.playVideoModel.onVolumeActionUpdate(event);
        })
        .onActionEnd(() => {
          this.playVideoModel.onActionEnd();
        })
    )
  ...
}
...

本章节以音量调节介绍手势控制,当手指触摸音量调节区域时获取当前屏幕坐标,滑动手指实时获取屏幕坐标并计算音量。

// VideoController.ets
// 手指触摸到音量调节区域
onVolumeActionStart(event?: GestureEvent) {
  if (!event) {
    return;
  }
  this.positionX = event.offsetX;
}

// 手指在音量调节区域水平滑动
onVolumeActionUpdate(event?: GestureEvent) {
  if (!event) {
    return;
  }
  if (this.avPlayer === null) {
    return;
  }
  if (CommonConstants.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
    return;
  }
  if (this.playerModel.brightShow === false) {
    this.playerModel.volumeShow = true;
    let screenWidth = GlobalContext.getContext().getObject('screenWidth') as number;
    let changeVolume = (event.offsetX - this.positionX) / screenWidth;
    let volume: number = this.playerModel.volume;
    let currentVolume = volume + changeVolume;
    let volumeMinFlag = currentVolume <= PlayConstants.MIN_VALUE;
    let volumeMaxFlag = currentVolume > PlayConstants.MAX_VALUE;
    this.playerModel.volume = volumeMinFlag ? PlayConstants.MIN_VALUE :
      (volumeMaxFlag ? PlayConstants.MAX_VALUE : currentVolume);
    this.avPlayer.setVolume(this.playerModel.volume);
    this.positionX = event.offsetX;
  }
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

  1. AVPlayer组件的使用。
  2. XComponent组件的使用。
  3. PanGesture手势的使用。

为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源, 获取完整版方式请点击→《HarmonyOS教学视频** 》**

HarmonyOS教学视频:语法ArkTS、TypeScript、ArkUI等.....视频教程

鸿蒙生态应用开发白皮书V2.0PDF:

获取完整版白皮书方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF

鸿蒙 (Harmony OS)开发学习手册

一、入门必看

  1. 应用开发导读(ArkTS)
  2. ......

二、HarmonyOS 概念

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全
  5. ........

三、如何快速入门?《做鸿蒙应用开发到底学习些啥?

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ......

四、开发基础知识

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ......

五、基于ArkTS 开发

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ......

更多了解更多鸿蒙开发的相关知识 可以参考:****《鸿蒙 (Harmony OS)开发学习手册

相关推荐
yuwinter3 分钟前
鸿蒙HarmonyOS学习笔记(2)
笔记·学习·harmonyos
ZZZCY200321 分钟前
华为ENSP--IP编址及静态路由配置
网络·华为
jikuaidi6yuan2 小时前
鸿蒙系统(HarmonyOS)分布式任务调度
分布式·华为·harmonyos
智慧化智能化数字化方案8 小时前
华为IPD流程管理体系L1至L5最佳实践-解读
大数据·华为
ZZZCY20039 小时前
华为VER系统及CLI命令熟悉
华为
SameX9 小时前
HarmonyOS Next 安全生态构建与展望
前端·harmonyos
SameX9 小时前
HarmonyOS Next 打造智能家居安全系统实战
harmonyos
Random_index17 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
鸿蒙自习室20 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu71 天前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成