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…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...

一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...

未授权访问事件频发,我们应当如何应对?
在当下,数据已成为企业和组织的核心资产,是推动业务发展、决策制定以及创新的关键驱动力。然而,未授权访问这一隐匿的安全威胁,正如同高悬的达摩克利斯之剑,时刻威胁着数据的安全,一旦触发,便可…...
P10909 [蓝桥杯 2024 国 B] 立定跳远
# P10909 [蓝桥杯 2024 国 B] 立定跳远 ## 题目描述 在运动会上,小明从数轴的原点开始向正方向立定跳远。项目设置了 $n$ 个检查点 $a_1, a_2, \cdots , a_n$ 且 $a_i \ge a_{i−1} > 0$。小明必须先后跳跃到每个检查点上且只能跳跃到检查点上。同时࿰…...