鸿蒙自定义Video播放器

news2024/11/15 20:48:51

前言

DevEco Studio版本:4.0.0.600

使用效果

如何使用

参考文档:OpenHarmony Video使用说明

1、module创建

File-->New-->Module,选择Static Library

2、相关类创建

PlayControl:视频播放控制类

PlayProgress:视频播放器进度条

VideoConstant:视频播放状态配置类

VideoPlayer:视频播放器管理类

然后在VideoLibraryIndex.ets类中添加对外输出的引用

export { VideoPlayer } from './src/main/ets/VideoPlayer'

PlayControl类:

import { VideoConstant } from './VideoConstant';

/**
 * 播放控制器
 */
@Component
export struct PlayControl {
   private playVideoModel?: VideoController;
   @Link videoStatus: number

   build() {
      Row() {
         Image(this.videoStatus == VideoConstant.STATUS_START ? $r('app.media.start_press') : $r('app.media.video_play_press'))
            .width('92px')
            .height('92px')
            .margin({ left: '156px', right: '156px' })
            .onClick(() => {
               if (this.videoStatus == VideoConstant.STATUS_START) {
                  this.videoStatus = VideoConstant.STATUS_PAUSE
                  if (this.playVideoModel != undefined) {
                     this.playVideoModel.pause()
                  }
               } else {
                  this.videoStatus = VideoConstant.STATUS_START
                  if (this.playVideoModel != undefined) {
                     this.playVideoModel.start()
                  }
               }
            })
      }
   }
}

PlayProgress类:

/**
 * 播放进度条
 */
@Component
export struct PlayProgress {
   @Prop currentTime: number
   @Prop totalTime: number
   private playVideoModel?: VideoController;

   build() {
      Row() {
         Text(this.formatTime(this.currentTime))
            .fontSize('14px')
            .fontColor(Color.White)
            .margin({ left: '16px', right: '12px' })
         Slider({
            value: this.currentTime, //当前进度值
            max: this.totalTime, //最大值,默认值100
            step: 1, //设置Slider滑动步长
            style: SliderStyle.OutSet //设置Slider的滑块与滑轨显示样式。
         })
            .blockColor(Color.White)//设置滑块的颜色。
            .trackColor($r('app.color.track_color'))//设置滑轨的背景颜色
            .selectedColor(Color.White)//设置滑轨的已滑动部分颜色
            .trackThickness(2)//设置滑轨的粗细
            .layoutWeight(1)
            .height('60px')
            .onChange((value: number, mode: SliderChangeMode) => {
               if (this.playVideoModel != undefined) {
                  this.playVideoModel.setCurrentTime(value, SeekMode.Accurate) // 精准跳转到视频的10s位置
               }
            })
         Text(this.formatTime(this.totalTime))
            .fontSize('14px')
            .fontColor(Color.White)
            .margin({ left: '12px', right: '16px' })
      }
      .width('100%')
      .backgroundColor('#88000000')
   }

   /**
    * 格式化时间
    */
   private formatTime(time: number): string {
      if (time < 60) {
         if (time > 9) {
            return '00:' + time
         } else {
            return '00:0' + time
         }
      } else {
         let timeStr = ''
         let hours = Number.parseInt((time / 60).toString())
         let seconds = time % 60
         if (hours > 9) {
            timeStr = hours.toString()
         } else {
            timeStr = '0' + hours
         }
         if (seconds > 9) {
            timeStr = timeStr + ':' + seconds
         } else {
            timeStr = timeStr + ':0' + seconds
         }
         return timeStr
      }
   }
}

VideoConstant类:

export class VideoConstant {
   /**
    * 开始播放
    */
   static readonly STATUS_START: number = 1;

   /**
    * 暂停播放
    */
   static readonly STATUS_PAUSE: number = 2;

   /**
    * 停止播放
    */
   static readonly STATUS_STOP: number = 3;
}

VideoPlayer类:

import router from '@ohos.router'
import { PlayControl } from './video/PlayControl'
import { PlayProgress } from './video/PlayProgress'
import { VideoConstant } from './video/VideoConstant'

@Preview
@Component
export struct VideoPlayer {
   private videoWidth: Length = '1024px'
   private videoHeight: Length = '550px'
   //视频未播放时的预览图片路径,默认不显示图片
   private previewUris?: Resource
   //视频播放源的路径,支持本地视频路径和网络路径
   @Prop innerResource: Resource
   //设置是否循环播放
   private isLoop: boolean = false
   //设置是否静音
   private isMuted: boolean = false
   //设置是否自动播放
   private isAutoPlay: boolean = true
   //视频控制器,可以控制视频的播放状态
   private controller: VideoController = new VideoController()
   //是否显示控制视图
   @State isShowController: boolean = true
   //视频播放状态
   @State videoStatus: number = VideoConstant.STATUS_STOP
   //视频时长,单位:秒(S)
   @State duration: number = 0
   //视频播放当前时间,单位:秒(S)
   @State currentTime: number = 0

   build() {
      Stack() {
         Video({
            src: this.innerResource,
            previewUri: this.previewUris,
            controller: this.controller
         })
            .muted(this.isMuted)//设置是否静音
            .loop(this.isLoop)//设置是否循环播放
            .autoPlay(this.isAutoPlay)//设置是否自动播放
            .controls(false)//设置是否显示默认控制条
            .objectFit(ImageFit.Contain)//设置视频适配模式
            .width('100%')
            .height('100%')
            .onTouch((event) => {
               if (event.type == TouchType.Up) {
                  this.isShowController = true
               }
            })
            .onStart(() => { //播放时触发该事件。
               console.info('VideoCreateComponent---   onStart')
               this.videoStatus = VideoConstant.STATUS_START
            })
            .onPause(() => { //播放时触发该事件。
               console.info('VideoCreateComponent---   onPause')
               this.videoStatus = VideoConstant.STATUS_PAUSE
            })
            .onFinish(() => { //播放结束时触发该事件。
               console.info('VideoCreateComponent---   onFinish')
               this.videoStatus = VideoConstant.STATUS_STOP
            })
            .onError(() => { //播放失败时触发该事件。
               console.info('VideoCreateComponent---   onError')
            })
            .onPrepared((e) => { //视频准备完成时触发该事件,通过duration可以获取视频时长,单位为秒(s)
               console.info('VideoCreateComponent---   onPrepared is ' + e.duration)
               this.duration = e.duration
            })
            .onSeeking((e) => { //操作进度条过程时上报时间信息,单位为s。
               console.info('VideoCreateComponent---   onSeeking is ' + e.time)
            })
            .onSeeked((e) => { //操作进度条完成后,上报播放时间信息,单位为s
               console.info('VideoCreateComponent---   onSeeked is ' + e.time)
            })
            .onUpdate((e) => { //播放进度变化时触发该事件,单位为s。
               console.info('VideoCreateComponent---   onUpdate is ' + e.time)
               this.currentTime = e.time
            })

         RelativeContainer() {
            Image($r('app.media.video_back'))
               .width('48px')
               .height('48px')
               .alignRules({
                  top: { anchor: '__container__', align: VerticalAlign.Top },
                  left: { anchor: '__container__', align: HorizontalAlign.Start }
               })
               .margin({ left: '48px', top: '24px' })
               .id('imageBack')
               .onClick(() => {
                  router.back()
               })

            PlayControl({ playVideoModel: this.controller, videoStatus: $videoStatus })
               .id('playControl')
               .alignRules({
                  middle: { anchor: '__container__', align: HorizontalAlign.Center },
                  center: { anchor: '__container__', align: VerticalAlign.Center }
               })

            PlayProgress({ playVideoModel: this.controller, totalTime: this.duration, currentTime: this.currentTime })
               .id('playProgress')
               .alignRules({
                  middle: { anchor: '__container__', align: HorizontalAlign.Center },
                  bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
               })

         }.width('100%')
         .height('100%')
         .visibility(this.isShowController ? Visibility.Visible : Visibility.None)
         .onTouch((event) => {
            if (event.type == TouchType.Up) {
               this.isShowController = false
            }
         })
      }
      .width(this.videoWidth)
      .height(this.videoHeight)
   }
}

资源引用:

start_press.png

video_back.png

video_play_press.png

3、在Entry中引用HttpLibaray

参考链接:静态har共享包

Entry目录下的oh-package.json5文件中添加对VideoLibaray的引用,详细操作参考之前文章。

"dependencies": {
  "@app/videoLibrary": "file:../VideoLibrary"
}

4、代码调用

import { VideoPlayer } from "@app/videoLibrary"

@Entry
@Component
struct Index {
   build() {
      Stack() {
         VideoPlayer({
            // previewUris: $r('app.media.preview'), //未播放时的封面
            innerResource: $rawfile('adv_test_video.mp4')
         })
      }
      .width('100%')
      .height('100%')
   }
}
属性是否必须描述
videoWidth非必须视频播放器宽度
videoHeight非必须视频播放器高度
previewUris非必须视频未播放时的预览图片路径,默认不显示图片
innerResource必须视频播放源的路径,支持本地视频路径和网络路径
isLoop非必须设置是否循环播放
isMuted非必须设置是否静音
isAutoPlay非必须设置是否自动播放

因为我们的视频是横屏播放的,所以需要配置下界面横屏显示

可以参考之前的文章:OpenHarmony 实现屏幕横竖屏_openharmony 屏幕旋转-CSDN博客

横屏配置:在Entry目录下src/main/module.json5中的abilities添加orientation属性

其实鸿蒙官方提供了两种方式播放视频:Video组件、AVPlayer,上面的示例是以Video组件进行封装的,感兴趣的同学可以尝试用AVPlayer进行封装下。

我在尝试用AVPlayer进行视频播放时发现,其seek方法定位不准,大家在调用时也可以关注下这个问题。如果你这边解决了,可以在下方留言说明下如何解决的,感谢!!!

参考文档:OpenHarmony 视频播放

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1408625.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

淘宝扭蛋机小程序开发:从创意到实现

一、引言 近年来&#xff0c;随着移动互联网的快速发展&#xff0c;小程序已成为商家与消费者互动的重要平台。其中&#xff0c;扭蛋机小程序以其独特的互动性和趣味性&#xff0c;受到了广泛的欢迎。本文将详细介绍淘宝扭蛋机小程序的开发过程&#xff0c;包括创意产生、需求…

OSPF-(LSA+SPF)

Router LSA使用Link来承载路由器直连接口的信息。 Link Type&#xff1a;P2P&#xff0c;TransNet&#xff0c;StubNet。 Point-to-Point&#xff08;P2P&#xff09;&#xff1a;描述一个从本路由器到邻居路由器之间的点到点链路&#xff1b;属于网段信息&#xff1b; StubN…

mmpose 2d姿态预测值转json文件

目录 效果图: 参考 模板文件下载地址: python预测代码: 效果图: <

【Flink-1.17-教程】-【四】Flink DataStream API(7)输出算子(Sink)

【Flink-1.17-教程】-【四】Flink DataStream API&#xff08;7&#xff09;输出算子&#xff08;Sink&#xff09; 1&#xff09;连接到外部系统2&#xff09;输出到文件3&#xff09;输出到 Kafka4&#xff09;输出到 MySQL&#xff08;JDBC&#xff09;5&#xff09;自定义 …

数论问题(算法村第十三关黄金挑战)

辗转相除法 8 和 12 的最大公因数是 4&#xff0c;记作 gcd(8,12)4。辗转相除法最重要的规则是&#xff1a; 若 mod 是 a b 的余数, 则gcd(a, b) gcd(b, mod)&#xff0c;直到a % b 0时&#xff0c;返回 b的值 gcd(546, 429) gcd(429, 117) gcd(117, 78) gcd(78, 39) …

Pytorch神经网络模型nn.Sequential与nn.Linear

1、定义模型 对于标准深度学习模型&#xff0c;我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型&#xff0c;而不必关注层的实现细节。 我们首先定义一个模型变量net&#xff0c;它是一个Sequential类的实例。 Sequential类将多个层串联在一起。 当给…

Redis:定时清理垃圾图片

首先理解清理垃圾文件的原理 在填写表单信息时上传图片后就已经存入云中&#xff0c;但是此时取消表单的填写这个图片就变成垃圾图片&#xff0c;所以在点击新建填写表单的方法/upload中&#xff0c;把上传的图片名字存入Redis的value中&#xff0c;key&#xff08;key1&#…

深入理解badblocks

文章目录 一、概述二、安装2.1、源码编译安装2.2、命令行安装2.3、安装确认 三、重要参数详解3.1、查询支持的参数3.2、参数说明 四、实例4.1、全面扫描4.2、破坏性写入并修复4.3、非破坏性写入测试 五、实现原理六、注意事项 团队博客: 汽车电子社区 一、概述 badblocks命令是…

NODE笔记 2 使用node操作飞书多维表格

前面简单介绍了node与简单的应用&#xff0c;本文通过结合飞书官方文档 使用node对飞书多维表格进行简单的操作&#xff08;获取token 查询多维表格recordid&#xff0c;删除多行数据&#xff0c;新增数据&#xff09; 文章目录 前言 前两篇文章对node做了简单的介绍&#xff…

【若依】关于对象查询list返回,进行业务处理以后的分页问题

1、查询对象Jglkq返回 list&#xff0c;对 list 进行业务处理后返回&#xff0c;但分页出现问题。 /*** 嫁功率考勤查询*/RequiresPermissions("hr:kq:list")PostMapping("/list")ResponseBodypublic TableDataInfo list(Jglkq jglkq) throws ParseExcepti…

骨传导耳机综评:透视南卡、韶音和墨觉三大品牌的性能与特点

在当前的蓝牙音频设备领域中&#xff0c;骨传导蓝牙运动耳机以其出色的安全特性和舒适的体验&#xff0c;受到了健身爱好者们的广泛好评。这类耳机不同于我们常见的入耳式耳机&#xff0c;它的工作方式是直接通过振动将声音传递到用户的耳骨中&#xff0c;这样既可以享受音乐&a…

鸿蒙系统的APP开发

鸿蒙系统&#xff08;HarmonyOS&#xff09;是由华为公司开发的一款分布式操作系统。它被设计用于在各种设备上实现无缝的、统一的用户体验&#xff0c;包括智能手机、平板电脑、智能电视、智能穿戴等设备。鸿蒙系统的核心理念是支持多终端协同工作&#xff0c;使应用能够更灵活…

unity学习笔记----游戏练习06

一、豌豆射手的子弹控制 创建脚本单独控制子弹的运动 用transform来控制移动 void Update() { transform.Translate(Vector3.right * speed * Time.deltaTime); } 创建一个控制子弹速度的方法&#xff0c;方便速度的控制 private void SetSpeed(float spee…

DS:顺序表的实现(超详细!!)

创作不易&#xff0c;友友们给个三连呗&#xff01; 本文为博主在DS学习阶段的第一篇博客&#xff0c;所以会介绍一下数据结构&#xff0c;并在最后学习对顺序表的实现&#xff0c;在友友们学习数据结构之前&#xff0c;一定要对三个部分的知识——指针、结构体、动态内存管理的…

springboot118共享汽车管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的共享汽车管理系统 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获…

Java 集合List相关面试题

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于java面试题系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基…

图形用户界面(GUI)开发教程

文章目录 写在前面MATLAB GUI启动方式按钮&#xff08;Push Button&#xff09;查看属性tag的命名方式回调函数小小的总结 下拉菜单&#xff08;Pop-up Menu&#xff09;单选框&#xff08;Radio Button&#xff09;和复选框&#xff08;Check Box&#xff09;静态文本&#xf…

将vue组件发布成npm包

文章目录 前言一、环境准备1.首先最基本的需要安装nodejs&#xff0c;版本推荐 v10 以上&#xff0c;因为需要安装vue-cli2.安装vue-cli 二、初始化项目1.构建项目2.开发组件/加入组件3. 修改配置文件 三、调试1、执行打包命令2、发布本地连接包3、测试项目 四、发布使用1、注册…

开源客户沟通平台Chatwoot账号激活问题

安装docker docker-compose 安装git clone https://github.com/chatwoot/chatwoot 下载之后根目录有一个docker-compose.production.yaml将其复制到一个目录 重命名 docker-compose.yaml 执行docker-compose up -d 构建 构建之后所有容器都安装好了 直接访问http://ip:3…

基于 Docker 部署 Pingvin Share 文件共享平台

一、Pingvin Share 介绍 Pingvin Share 简介 Pingvin Share 是自托管文件共享平台&#xff0c;是 WeTransfer 的替代方案。 Pingvin Share 特点 在 2 分钟内启动您的实例使用可通过链接访问的文件创建共享没有文件大小限制&#xff0c;只有你的磁盘是你的限制设置共享到期时间…