HarmonyOS NEXT边学边玩:从零实现一个影视App(六、视频播放页的实现)
在HarmonyOS NEXT中,ArkUI是一个非常强大的UI框架,能够帮助开发者快速构建出美观且功能丰富的用户界面。本文将详细介绍如何使用ArkUI实现一个影视App的视频播放页面。将从零开始,逐步构建一个功能完善的视频播放页面,并解释每一部分的代码实现。
1. 项目结构
在开始之前,先来看一下项目的结构。我的项目结构如下:
src/
├── common/
│ ├── bean/
│ │ └── ApiTypes.ts
│ └── dialog/
│ └── EpisodeDialogView.ts
├── pages/
│ └── VideoPlayerPage.ts
ApiTypes.ts
定义了视频数据的类型。EpisodeDialogView.ts
是一个自定义的剧集选择对话框组件。VideoPlayerPage.ts
是视频播放页面的主文件。
2. 视频播放页面的实现
2.1 引入必要的模块
首先,需要引入一些必要的模块和组件:
import { VideoItem } from "../../common/bean/ApiTypes";
import { window } from "@kit.ArkUI";
import { common } from "@kit.AbilityKit";
import { EpisodeDialogView } from "../../common/dialog/EpisodeDialogView";
VideoItem
是定义的一个视频数据类型的接口。window
和common
是HarmonyOS提供的系统模块,用于控制窗口和UI能力。EpisodeDialogView
是自定义的剧集选择对话框组件。
2.2 构建视频播放页面
使用 @Builder
装饰器来构建视频播放页面:
@Builder
export function VideoPlayerPageBuilder() {VideoPlayer()
}
VideoPlayerPageBuilder
是一个构建函数,它调用了 VideoPlayer
组件来构建整个页面。
2.3 视频播放组件
接下来,定义 VideoPlayer
组件:
@Component
struct VideoPlayer {@State title: string = '';private controller: VideoController | undefined;@State previewUri: Resource = $r('app.media.play_circle_fill');@State videoSrc: string = 'http://staticvip.iyuba.cn/video/small/202412/1009544_c.mp4'; // 使用时请替换为实际视频加载网址@State tvUrls?: string[] = [];@State isTv: boolean = false;@State tvIndex: number = 0;private description: string = '';private rawTitle: string = '';private isToggle = false;@State toggleText: string = '';@State toggleBtn: string = '展开';
@State
是ArkUI中的状态管理装饰器,用于管理组件的状态。controller
是视频播放器的控制器,用于控制视频的播放、暂停等操作。previewUri
是视频的预览图资源。videoSrc
是视频的播放地址。tvUrls
是电视剧的剧集列表。isTv
用于判断当前播放的是否是电视剧。tvIndex
是当前播放的剧集索引。description
是视频的简介。rawTitle
是视频的原始标题。isToggle
用于控制简介的展开和收起状态。toggleText
是简介的显示文本。toggleBtn
是简介展开/收起按钮的文本。
2.4 页面生命周期
在 aboutToAppear
生命周期函数中,可以进行一些初始化操作:
aboutToAppear() {// 初始化操作
}
2.5 屏幕方向切换
定义了一个 changeOrientation
方法,用于切换屏幕方向:
private changeOrientation(isLandscape: boolean) {let context = getContext(this) as common.UIAbilityContext;window.getLastWindow(context).then((lastWindow) => {lastWindow.setPreferredOrientation(isLandscape ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT);});
}
getContext
用于获取当前组件的上下文。window.getLastWindow
获取当前窗口,并设置其方向为横屏或竖屏。
2.6 剧集选择对话框
定义了一个剧集选择对话框,并为其设置了回调函数:
episodeDialogController: CustomDialogController = new CustomDialogController({builder: EpisodeDialogView({current: this.tvIndex,tvUrls: this.tvUrls,clickCallback: (item: string, index: number) => { this.onDialogClickCallback(item, index) }}),alignment: DialogAlignment.Bottom,offset: { dx: 0, dy: -30 },customStyle: true
});private onDialogClickCallback(item: string, index: number) {this.videoSrc = item;this.tvIndex = index;this.title = this.rawTitle + ' 第' + (index + 1) + '集';this.episodeDialogController.close();this.controller?.start();
}
CustomDialogController
是自定义对话框的控制器。EpisodeDialogView
是剧集选择对话框的视图组件。onDialogClickCallback
是剧集选择后的回调函数,用于更新视频播放地址和标题。
剧集选择框的实现:
/*** @author: 猫哥* @date: 2025/1/15 0:30* @description:* @version:*/
/*** 电视剧集数选择对话框*/
@Preview
@CustomDialog
export struct EpisodeDialogView {controller: CustomDialogController@Prop current: number@Prop tvUrls?:string[] = []//确认按钮回调clickCallback?: (item:string,idx:number) => void// 添加一个方法来获取按钮的背景颜色private getButtonColor(idx: number): Color {if (this.current === idx) {return Color.Blue // 当前选中的按钮背景色为蓝色} else {return Color.White // 其他按钮背景色为白色}}build() {Column({space: 10}){Grid() {ForEach(this.tvUrls, (item: string,idx) => {GridItem() {Button('第'+(idx+1)+'集', { buttonStyle: ButtonStyleMode.NORMAL, role: ButtonRole.NORMAL }).borderRadius(5).fontColor('#fffab52a').width(80).height(40).padding(5).backgroundColor(this.getButtonColor(idx)).onClick(() => {this.clickCallback?.(item,idx)})}}, (item: string) => item.toString())}.width('100%').scrollBar(BarState.Off).columnsTemplate('1fr 1fr 1fr 1fr').columnsGap(4).rowsGap(4).height(300)}.width('98%').height('40%').backgroundColor(Color.White).padding(20).borderRadius(20)}
}
2.7 按钮背景颜色
定义了一个方法来获取按钮的背景颜色:
private getButtonColor(idx: number): Color {if (this.tvIndex === idx) {return Color.Blue; // 当前选中的按钮背景色为蓝色} else {return Color.White; // 其他按钮背景色为白色}
}
2.8 构建页面布局
最后,使用 build
方法来构建页面的布局:
build() {NavDestination() {Column() {Row() {Stack() {Video({src: this.videoSrc,previewUri: this.previewUri,controller: this.controller}).width('100%').muted(false) //设置是否静音.controls(true) //设置是否显示默认控制条.autoPlay(true) //设置是否自动播放.loop(false) //设置是否循环播放.objectFit(ImageFit.Contain) //设置视频适配模式.onError(() => { //失败事件回调console.info("Video error.");}).onFullscreenChange(event => {if (event.fullscreen) {this.changeOrientation(true);} else {this.changeOrientation(false);}}).zIndex(1);Text(this.title).fontColor(Color.White).width('100%').padding(5).alignSelf(ItemAlign.Start).margin({ bottom: 280 }).zIndex(2);}}.width('100%').height('40%');Column() {Text('简介').fontSize(18).padding({ bottom: 10 }).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start);Text(this.toggleText).fontSize(14).lineHeight(20).alignSelf(ItemAlign.Start);Text(this.toggleBtn).fontSize(14).fontColor(Color.Gray).padding(10).alignSelf(ItemAlign.End).onClick(() => {this.isToggle = !this.isToggle;if (this.isToggle) {this.toggleBtn = '收起';this.toggleText = this.description;} else {this.toggleBtn = '展开';this.toggleText = this.description.substring(0, 100) + '...';}});}.padding(10);if (this.isTv) {Flex({direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,alignContent: FlexAlign.SpaceBetween}) {Text('选集').fontSize(18).fontWeight(FontWeight.Bold);SymbolGlyph($r('sys.symbol.more')).fontWeight(FontWeight.Lighter).fontSize(32).fontColor(['#fffab52a']).onClick(() => {this.episodeDialogController.open();});}.padding(10);Scroll() {Row({ space: 10 }) {ForEach(this.tvUrls, (item: string, idx) => {Button('第' + (idx + 1) + '集', { buttonStyle: ButtonStyleMode.NORMAL, role: ButtonRole.NORMAL }).borderRadius(5).fontColor('#fffab52a').width(80).height(40).padding(5).backgroundColor(this.getButtonColor(idx)).onClick(() => {this.videoSrc = item;this.tvIndex = idx;this.title = this.rawTitle + ' 第' + (idx + 1) + '集';this.controller?.start();});});}.alignItems(VerticalAlign.Center).padding(10);}.scrollable(ScrollDirection.Horizontal);}}.width('100%');}.width("100%").height("100%").onReady(ctx => {interface params {item: VideoItem;}let par = ctx.pathInfo.param as params;this.videoSrc = par.item.video;this.rawTitle = par.item.title;this.title = this.rawTitle;this.description = par.item.desc;this.tvUrls = par.item.tvurls;if (this.tvUrls?.length ?? 0 > 0) {this.isTv = true;}this.tvIndex = 0;this.toggleText = this.description.substring(0, 100) + '...';}).onShown(() => {console.info('VideoPlayer onShown');});
}
NavDestination
是导航目的地组件,用于定义页面的导航行为。Column
和Row
是布局组件,用于构建页面的布局。Video
是视频播放组件,用于播放视频。Text
是文本组件,用于显示标题和简介。Button
是按钮组件,用于选择剧集。Scroll
是滚动组件,用于实现剧集列表的横向滚动。
3. 总结
通过以上步骤,实现了一个功能完善的视频播放页面。这个页面不仅支持视频的播放、暂停、全屏等基本功能,还支持电视剧的剧集选择和简介的展开/收起。希望这篇文章能够帮助你更好地理解如何使用HarmonyOS NEXT和ArkUI来构建一个影视App的视频播放页面。
如果你有任何问题或建议,欢迎在评论区留言!
作者介绍
作者:csdn猫哥
原文链接:https://blog.csdn.net/yyz_1987
团队介绍
坚果派团队由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉等相关内容,团队成员聚集在北京、上海、南京、深圳、广州、宁夏等地,目前已开发鸿蒙原生应用和三方库60+,欢迎交流。
版权声明
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
相关文章:

HarmonyOS NEXT边学边玩:从零实现一个影视App(六、视频播放页的实现)
在HarmonyOS NEXT中,ArkUI是一个非常强大的UI框架,能够帮助开发者快速构建出美观且功能丰富的用户界面。本文将详细介绍如何使用ArkUI实现一个影视App的视频播放页面。将从零开始,逐步构建一个功能完善的视频播放页面,并解释每一部…...
salesforce实现一个字段的默认初始值根据另一个字段的值来自动确定
在 Salesforce 中,可以通过 公式字段 或 触发器 (Trigger) 实现字段的默认初始值根据另一个字段的值来自动确定,具体实现方法如下: 1. 使用公式字段 公式字段是一种动态字段,值会根据公式实时计算。 步骤: 导航到字段…...
Linux 文件权限详解
目录 前言 查看文件权限 修改文件权限 符号方式 数字方式 前言 Linux 文件权限是系统中非常重要的概念之一,用于控制对文件和目录的访问。权限分为读(Read)、写(Write)、执行(Execute)三个…...

【混合开发】CefSharp+Vue桌面应用程序开发
为什么选择CefSharpVue做桌面应用程序 CefSharp 基于 Chromium Embedded Framework (CEF) ,它可以将 Chromium 浏览器的功能嵌入到 .NET 应用程序中。通过 CefSharp,开发者可以在桌面应用程序中集成 Web 技术,包括 HTML、JavaScript、CSS 等…...
springBoot项目使用Elasticsearch教程
目录 一、引言(一)使用背景(二)版本库区别 二、引入依赖(一)springboot集成的es依赖(建议)(二)es提供的客户端库 三、配置(以yaml文件为例&#x…...

模型 多元化思维(系统科学)
系列文章分享模型,了解更多👉 模型_思维模型目录。融合多学科知识,全面解决问题。 1 多元化思维模型的应用 1.1 完美日记的私域流量运营 完美日记作为美妆行业的新兴品牌,通过多元化的思维模型在私域流量运营中取得了显著成功。…...
Google地图瓦片爬虫
地图地址说明 1、谷歌矢量(中文标注) http://mt{0-3}.google.cn/vt/vm416115521&hlzh-CN&glcn&x{x}&y{y}&z{z}&sGalileo 2、谷歌矢量(英文标注) http://mt{0-3}.google.cn/vt/vm416115521&hlen&glcn&x{x}&y{y}&z{z}&sGali…...

【C++】size_t全面解析与深入拓展
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯一、什么是size_t?为什么需要size_t? 💯二、size_t的特性与用途1. size_t是无符号类型示例: 2. size_t的跨平台适应性示例对…...

Web端实时播放RTSP视频流(监控)
一、安装ffmpeg: 1、官网下载FFmpeg: Download FFmpeg 2、点击Windows图标,选第一个:Windows builds from gyan.dev 3、跳转到下载页面: 4、下载后放到合适的位置,不用安装,解压即可: 5、配置path 复制解压后的\bin路径,配置环境变量如图: <...

学习 Git 的工作原理,而不仅仅是命令
Git 是常用的去中心化源代码存储库。它是由 Linux 创建者 Linus Torvalds 创建的,用于管理 Linux 内核源代码。像 GitHub 这样的整个服务都是基于它的。因此,如果您想在 Linux 世界中进行编程或将 IBM 的 DevOps Services 与 Git 结合使用,那…...
C语言变长嵌套数组常量初始化定义技巧
有时候,我们需要在代码里配置一些常量结构,比如一个固定的动作流程ActionFlow:包含N(即flow_num)个动作列表(ActionArray),每个动作列表包含M(即act_num)个可…...

如何查看特定版本的Spring源码
写在前面:大家好!我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭&#x…...

【深度学习】关键技术-激活函数(Activation Functions)
激活函数(Activation Functions) 激活函数是神经网络的重要组成部分,它的作用是将神经元的输入信号映射到输出信号,同时引入非线性特性,使神经网络能够处理复杂问题。以下是常见激活函数的种类、公式、图形特点及其应…...
网关相关知识
文章目录 什么是网关网关的主要作用网关的运用 什么是网关 网关又称网间连接器、协议转换器,也就是网段(局域网、广域网)关卡,不同网段中的主机不能直接通信,需要通过关卡才能进行互访,比如IP地址为192.168.31.9(子网掩码&#x…...

SpringBoot整合SpringSecurity详解
文章目录 SpringBoot整合SpringSecurity详解一、引言二、引入依赖三、配置 Spring Security四、自定义用户详细信息服务五、使用示例1. 创建用户实体类2. 测试登录功能 六、总结 SpringBoot整合SpringSecurity详解 一、引言 在当今的软件开发中,安全是一个至关重要…...
【C++基础】enum,union,uint8_t,static
enum 所以有时候使用 Enum 的目的,不是为了自定义一种数据类型,而是为了声明一组常量。 from: https://github.com/wangdoc/clang-tutorial/blob/main/docs/enum.md union C 语言提供了 Union 结构,用来自定义可以灵活变更的数据结构。它内部…...
单片机的原理及其应用:从入门到进阶的全方位指南
以下是一篇详细、深入的“单片机的原理及其应用”博客文章示例,适合想要系统学习或深入了解单片机的读者。文中不仅会介绍单片机的基本原理、内部构造、开发流程和应用领域,还会融入更多的理论分析、实操案例以及常见问题与解决思路等,帮助读…...
如何使用 Go语言操作亚马逊 S3 对象云存储
以下是使用 Go 语言操作亚马逊 S3 对象云存储的详细步骤和示例代码: 解决思路: 安装必要的 Go 语言包,这里我们将使用 aws-sdk-go 包来与 Amazon S3 进行交互。配置 AWS 凭证,包括访问密钥和秘密访问密钥,以及 AWS 区…...

2025年应用与API安全展望:挑战与机遇并存
进入2025年,应用与API安全的重要性愈发突出。在过去的一年里,API技术已经成为数字创新的核心。然而,API的大规模应用也使得攻击面显著扩展,2024年针对业务逻辑漏洞的API攻击占比高达27%,较前一年增加10%。与此同时&…...
Linux安装docker,安装配置xrdp远程桌面
Linux安装docker,安装配置xrdp远程桌面。 1、卸载旧版本docker 卸载旧版本docker命令 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine现在就是没有旧版本的d…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...