【案例+1】HarmonyOS官方模板优秀案例(第10期:教育行业 · 课堂应用)
为支持开发者单独获取特定场景的页面和功能,本模板将功能完全自闭环的部分能力抽离出独立的行业组件模块,不依赖公共基础能力包,开发者可以单独集成,开箱即用,降低使用难度。基于以上行业分析,本期将介绍鸿蒙生态市场教育类行业模板——课堂应用模板,为行业提供常用功能的开发案例,模板主要分首页、学习、我的三大模块。若开发者已搭建好自己的应用工程,但暂未实现其中的部分场景能力,可以选择取用其中的业务组件,集成在
鸿蒙生态为开发者提供海量的HarmonyOS模板/组件,助力开发效率原地起飞
★ 一键直达生态市场组件&模板市场 , 快速应用DevEco Studio插件市场集成组件&模板 ★
在之前第三期为大家介绍了“教育备考案例”(^_^点这里立即回顾)
本期带来新上模板“课堂应用模板”的案例解析
覆盖20+行业,点击查看往期案例汇总贴,持续更新,点击收藏!一键三连!常看常新!
【第10期】教育行业 · 课堂应用
一、概述
1.行业洞察
1)行业诉求:
- 精准分发:面对不同的受众教育类应用有不同的业务场景,产出适配内容、精准题库等,并以高效且契合场景的方式进行分发,最终实现优质教育资源的有效传递。
- 高效流畅、操作敏捷是教育类应用不同场景重要诉求,在线学习、考试等场景出现卡顿会严重影响学习、考试的。
- 需具备智能刷题与精准辅导能力:基于大数据和算法,依据答题情况判断用户知识掌握状况,动态调整出题难度,推送契合的学习内容。
- 多端协同能力:实现手机、平板、PC 端数据实时互通,提供离线功能,便于用户利用碎片化时间用于刷题或知识点复习,并可在线后同步学习进度。
2)行业常用三方SDK
|
分类 |
三方库名称 |
功能 |
SDK链接 |
|
第三方登录类 |
创蓝闪验SDK、阿里一键登录SDK、中国移动号码认证服务、中国联通号码认证服务、中国电信号码认证服务 |
一键登录和验证用户手机号码与本机流量卡是否一致的服务 |
|
|
数据分析 |
com.umeng(友盟)SDK、百度统计SDK、com.efs SDK、GrowingIO SDK、阿里云数据分析 |
移动统计 |
|
|
浏览引擎 |
腾讯X5浏览内核SDK |
用于展现html页面 |
|
|
文档浏览 |
com.tencent.tbs(腾讯浏览服务)SDK、福昕SDK |
本地文档浏览 |
|
|
第三方应用开发支持 |
微信开放SDK |
支持微信授权登录、微信分享和微信支付以及ApplePay支付 |
|
|
三方支付 |
阿里支付 SDK、微信支付SDK、京东支付SDK、招商支付SDK |
支持支付宝支付 |
|
|
应用维护 |
阿里云热修复SDK、Logan、腾讯Bugly SDK |
线上问题修复、日志分析、提高服务稳定性,服务崩溃后快速定位问题 |
|
|
应用安全 |
阿里云HttpDNS SDK、网易易盾SDK、数盟、同盾业务安全 SDK、阿里APP防护 SDK |
防止域名劫持、提升应用安全;登录验证码 ;检测是否为风险设备;提供应用安全防护 |
|
|
地图 |
高德定位 SDK、百度地图SDK |
实现定位 |
|
|
推送 |
极光推送SDK、腾讯Push SDK、七陌Push SDK |
推送消息 |
|
|
互动小游戏 |
org.cocos2dx.lib(Cocos) SDK |
互动题目引擎 |
|
|
分享 |
腾讯QQ分享 SDK、QQ分享、小红书分享、抖音分享 |
动态分享 |
|
|
图片识别 |
科大讯飞SDK、随时问SDK、人教点读SDK |
文字转语音、图片识别、点读 |
|
|
APP投屏 |
乐播SDK |
APP投屏 |
|
|
音视频 |
火山引擎SDK、声网SDK、即构SDK、声通语音SDK |
在线直播、观看录播视频、语音识别 |
|
|
课堂管理 |
腾讯云课堂SDK |
课程列表、课程详情、在线直播、互动白板、聊天室等功能 |
说明:“以上三方库及链接仅为示例,三方库由三方开发者独立提供,以其官方内容为准”
2.案例概览(下载模板)
基于以上行业分析,本期将介绍鸿蒙生态市场教育类行业模板——课堂应用模板,为行业提供常用功能的开发案例,模板主要分首页、学习、我的三大模块。
- Stage开发模型 + 声明式UI开发范式。
- 分层架构设计 + 组件化拆分,支持开发者在开发时既可以选择完整使用模板,也可以根据需求单独选用其中的业务组件。
- 本模板已集成华为账号、广告等服务,只需做少量配置和定制即可快速实现华为账号的登录、媒体播放等功能。

本模板主要页面及核心功能如下所示:
教育学习应用模板
├──首页
│ ├──顶部栏-搜索
│ │ ├── 搜索
│ │ └── 职级分类选择
│ │
│ ├──课堂列表
│ │ ├── 动态布局
│ │ ├── 信息流
│ │ └── 功能区
│ └──课堂详情
│ ├── 图文
│ ├── 收藏、评论
│ ├── 分享(微信、朋友圈、QQ、生成海报、复制链接等)
│ └── 视频
├──学习
│ ├──顶部栏
│ │ └── 搜索
│ ├──课堂列表
│ └──视频详情页
│ ├── 竖屏播放
│ ├── 横屏播放
│ ├── 暂停、播放、进度调节、倍速、选集、画中画
│ └── 收藏、评论、分享
│
└──我的
├──登录
│ ├── 华为账号一键登录
│ ├── 微信登录
│ ├── 账密登录
│ └── 用户隐私协议同意
│
├──个人主页
│ └── 头像、昵称、简介
│
├──功能栏
│ ├── 我的课程
│ ├── 我的收藏
│ ├── 我的下载
│ └── 我的错题
│
└──常用服务
├── 订单
├── 消息
├── 评论
├── 观看记录
├── 意见反馈
└── 设置
├── 编辑个人信息
├── 隐私设置
├── 播放设置
├── 清理缓存
├── 检测版本
├── 关于我们
└── 退出登录
二、应用架构设计
- 分层化模块设计
- 产品定制层:专注于满足不同设备或使用场景的个性化需求,作为应用的入口,是用户直接互动的界面。
- 本实践支持直板机、折叠机,为单HAP包形式,包含路由根节点、底部导航栏等。
- 基础特性层:用于存放相对独立的功能UI和业务逻辑实现。
- 本实践的基础特性层将应用底部导航栏的每个选项拆分成一个独立的业务功能模块。
- 每个功能模块都具备高内聚、低耦合、可定制的特点,支持产品的灵活部署。
- 公共能力层:存放公共能力,包括公共UI组件、数据管理、外部交互和工具库等共享功能。
- 本实践的公共能力层分为公共基础能力和可分可合组件,均打包为HAR包被上层业务组件引用。
- 公共基础能力包含日志、文件处理等工具类,公共类型定义,网络库,以及弹窗、加载等公共组件。
- 可分可合组件将包含行业特点、可完全自闭环的能力抽出独立的组件模块,支持开发者在开发中单独集成使用,详见业务组件设计章节。

本模板详细工程结构可见工程结构章节。
2.业务组件设计
为支持开发者单独获取特定场景的页面和功能,本模板将功能完全自闭环的部分能力抽离出独立的行业组件模块,不依赖公共基础能力包,开发者可以单独集成,开箱即用,降低使用难度。

三、行业场景技术方案
- 视频播放控制层定制化
1)场景说明
用户可播放网络视频,视频播放支持加解锁、画中画、倍速、等功能。

2)技术方案
- 基于AVPlayer实现视频播放能力。
- 使用XComponent渲染视频画面。
- ArkUI搭建控制层操作能力。
- 画中画
1)场景说明
用户可启动画中画功能同时操作其他界面不影响视频观看。

2)技术方案
- 采用XComponent实现画中画能力。
- 封装PipManager工具类封装画中画相关API。
四、模板代码
3.工程结构(下载模板)
详细代码结构如下所示:
Course
├──commons
│ ├──commonlib/src/main/ets // 基础模块
│ │ ├──components
│ │ │ ├──BottomEditBar.ets // 底部编辑组件
│ │ │ ├──CourseItemComponent.ets // 课程列表组件
│ │ │ ├──DeleteDialog.ets // 删除按钮组件
│ │ │ ├──DialogComponent.ets // 自适应弹框组件
│ │ │ ├──GlobalAttributeModifier.ets // 自定义动态属性
│ │ │ ├──LeftSwipeComponent.ets // 编辑组件
│ │ │ ├──NoData.ets // 空数据页面
│ │ │ └──TopBar.ets // 标题栏
│ │ ├──constants
│ │ │ ├──CommonConstants.ets // 常量
│ │ │ └──CommonEnums.ets // 路由页面
│ │ ├──models
│ │ │ ├──CollectionModel.ets // 收藏
│ │ │ ├──DownloadModel.ets // 下载
│ │ │ ├──MyCourseModel.ets // 我的课程
│ │ │ ├──OrderInfo.ets // 订单数据模型
│ │ │ ├──TopicItemModel.ets // 题目数据模型
│ │ │ ├──UserInfo.ets // 用户信息
│ │ │ ├──WindowSize.ets // 窗口数据模型
│ │ │ └──WrongQuestionModel.ets // 错题数据模型
│ │ └──utils
│ │ ├──DialogUtil.ets // 弹框
│ │ ├──Logger.ets // 日志
│ │ ├──NumberUtil.ets // 数据格式化
│ │ ├──PermissionUtil.ets // 权限
│ │ ├──PreferenceUtil.ets // 首选项
│ │ └──RouterModule.ets // 路由
│ │
│ │
│ └──http/src/main/ets // 网络模块
│ ├──apis
│ │ ├──Apis.ets // 请求接口
│ │ └──HttpRequest.ets // 请求对象
│ ├──mocks
│ │ ├──AxiosMock.ets // mock
│ │ ├──RequestMock.ets // 请求mock数据
│ │ └──Data
│ │ └──CourseData.ets // 数据源
│ ├──model
│ │ └──CourseModel.ets // 课程数据模型
│ ├──tools
│ │ └──ConvertTool.ets // 模型转换工具
│ └──types
│ └──Course.ets // 课程数据协议
├──components
│ ├──aggregated_payment // 支付组件
│ ├──aggregated_share // 分享组件
│ ├──classification // 分类组件
│ ├──feed_back // 意见反馈组件
│ ├──login_info // 登录组件
│ ├──open_ads // 广告组件
│ ├──search // 搜索组件
│ └──recorded_player // 视频播放组件
├──features
│ ├──course/src/main/ets // 学习课程模块
│ │ ├──component
│ │ │ └──CourseStudyComponent.ets // 课程数据源
│ │ ├──viewModels
│ │ │ └──CourseVM.ets // 课程数据源
│ │ └──views
│ │ └──MyLearnPage.ets // 学习页面
│ │
│ ├──home/src/main/ets // 首页模块
│ │ ├──components
│ │ │ ├──AnswerQuestionsComponent.ets // 答题view
│ │ │ ├──CommentPopup.ets // 评分弹框
│ │ │ ├──CourseCatalogComponents.ets // 课程章节弹框
│ │ │ ├──LiveBroadcastSign.ets // 直播标签view
│ │ │ ├──LiveNumberOverlay.ets // 数量view
│ │ │ ├──LiveStreamingItem.ets // 直播列表item
│ │ │ ├──AnswerSheetDialog.ets // 答题卡view
│ │ │ └──HomeTop.ets // 首页职能类型选择页面
│ │ ├──models
│ │ │ ├──Practice.ets // 模块数据
│ │ │ ├──HomeMenuModel.ets // 课程数据
│ │ │ ├──SelectRoleModel.ets // 职称级别数据
│ │ │ └──Score.ets // 打分数据
│ │ ├──viewModels
│ │ │ ├──CourseCatalogVM.ets // 章节数据刷新
│ │ │ └──HomeVM.ets // 首页数据刷新
│ │ └──pages
│ │ ├──AnswerQuestionsExamPage.ets // 答题页面
│ │ ├──AnswerQuestionsPage.ets // 答题view
│ │ ├──AnswerSheetPage.ets // 答题卡页面
│ │ ├──ChapterDetail.ets // 章节详情页面
│ │ ├──CourseListPage.ets // 课程页页面
│ │ ├──ExamResultPage.ets // 答题结果页面
│ │ ├──HomePage.ets // 首页
│ │ ├──HomeSearch.ets // 搜索页面
│ │ ├──LiveStreamingListPage.ets // 直播页面
│ │ ├──MaterialPage.ets // 资料页面
│ │ ├──TestReportPage.ets // 结果页面
│ │ └──RecordedDetail.ets // 课程详情页面
│ └──mine/src/main/ets // 个人模块
│ ├──viewModel
│ │ └──MyVM.ets // 数据处理层
│ ├──models
│ │ ├──MessageModel.ets // 消息数据
│ │ ├──SystemMessageModel.ets // 系统消息数据
│ │ └──ServiceModel.ets // 模块服务数据
│ ├──component
│ │ ├──CourseHistoryComponent.ets // 观看记录item
│ │ └──TopicItemComponent.ets // 题目标题
│ └──views
│ ├──AboutAndVersion.ets // 关于页面
│ ├──AboutPage.ets // 设置页面
│ ├──Authentication.ets // 用户协议页面
│ ├──CourseUpdateMessagePage.ets // 课程动态消息页面
│ ├──DataSharingPage.ets // 三方信息页面
│ ├──FeedbackPage.ets // 意见反馈页面
│ ├──FeedbackRecordsPage.ets // 反馈记录
│ ├──MessageListCard.ets // 消息列表组件页面
│ ├──MessagePage.ets // 消息页面
│ ├──MinePage.ets // 个人页面
│ ├──MultiLineText.ets // 消息组件页面
│ ├──MyCollectionPage.ets // 收藏页面
│ ├──MyCourseComments.ets // 课程评论页面
│ ├──MyCoursePage.ets // 课程页面
│ ├──MyDownloadPage.ets // 下载页面
│ ├──MyOrderPage.ets // 订单页面
│ ├──MyWrongQuestionPage.ets // 错题页面
│ ├──OrderListPage.ets // 订单列表页面
│ ├──OrderDetailPage.ets // 订单列表页面
│ ├──PersonalInfoPage.ets // 个人信息页面
│ ├──PersonalInformationCollectionPage.ets // 个人信息收集页面
│ ├──PrivacyAgreement.ets // 隐私协议页面
│ ├──PrivacySetPage.ets // 隐私设置页面
│ ├──PrivacyStatement.ets // 隐私声明页面
│ ├──SetUpPage.ets // 设置页面
│ ├──SystemMessage.ets // 系统消息页面
│ ├──UpdateVersionContentCard.ets // 版本更新页面
│ ├──WatchHistoryPage.ets // 观看记录页面
│ └──UserAgreement.ets // 服务协议页面
│
└──product
└──entry/src/main/ets
├──viewmodels // 首页数据
│ └──MainEntryVM.ets
├──entryability // 入口
│ └──EntryAbility.ets
├──entrybackupability // 后台
│ └──EntryBackupAbility.ets
├──models
│ ├──RouterTable.ets // 路由表
│ └──Types.ets // tab数据类型
└──pages
├──Index.ets // 入口页面
├──Launch.ets // 启动页面
├──LaunchAd.ets // 广告页面
├──Login.ets // 登录页面
├──MainEntry.ets // 主页面
├──PrivacyPolicy.ets // 隐私页面
└──PrivacyPolicyAlert.ets // 隐私弹框页面
4.关键代码解读
本篇代码非应用的全量代码,只包括应用的部分能力的关键代码。
1) 播放器工具封装
@ObservedV2
export class AVPlayer {
callbackComplete: () => void = () => {
};
callbackTimeUpdate: (nol: number, total: number) => void = (nol: number) => {
};
callbackBitrateUpdate: (bitrateList: number[]) => void = (bitrateList: number[]) => {
};
callbackErrorUpdate: (error: string) => void = (error: string) => {
};
@Trace currentTime: number = 0;
@Trace totalDuration: number = 0;
@Trace isPlaying: boolean = false;
@Trace isLoading: boolean = true;
@Trace isLoadError: boolean = false;
// surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取,相关文档链接见上面XComponent创建方法
surfaceID: string = '';
// 画中画是否开启
isPipOpen: boolean = false;
// 复原
isPiPWindowRestore: boolean = false;
avPlayer: media.AVPlayer = {} as media.AVPlayer;
isCreate: boolean = false;
seekTime: number = 0;
// 碰到特殊场景进入其他页时,即便加载成功也不需要播放
isResetPause: boolean = false;
videoUrl: string = '';
async init(url: string, seekTime: number): Promise<void> {
await this.release();
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
await this.setStateChangeCallback();
await this.setSourceInfoCallback();
this.isCreate = true;
this.isResetPause = false;
this.videoUrl = url
this.avPlayer.url = url;
this.isLoading = true;
this.seekTime = seekTime;
}
getPlay() {
if (this.avPlayer) {
this.avPlayer.play();
}
}
getPause() {
if (this.avPlayer) {
this.avPlayer.pause();
}
}
getStop() {
if (this.avPlayer) {
this.avPlayer.stop();
}
}
changeVolume(volume: number) {
if (this.avPlayer) {
this.avPlayer.setVolume(volume)
}
}
changeSpeed(speed: number) {
if (this.avPlayer) {
this.avPlayer.setSpeed(speed)
}
}
changeSeek(seekTime: number, mode: SliderChangeMode) {
if (this.avPlayer) {
this.avPlayer.seek(seekTime, 2);
}
}
setSurfaceId(surfaceId: string): void {
if (this.avPlayer.surfaceId === surfaceId) {
return
}
this.surfaceID = surfaceId
this.avPlayer.surfaceId = this.surfaceID;
}
// 切换播放
async changePlay(url: string, seekTime: number) {
if (this.avPlayer) {
setTimeout(() => {
this.isLoadError = false
}, 1000)
this.isPlaying = false
this.isLoading = true
this.isResetPause = false;
await this.avPlayer.reset()
this.totalDuration = 0
this.seekTime = seekTime
this.videoUrl = url
this.avPlayer.url = url
}
}
setCompleteCallback(func: () => void) {
this.callbackComplete = func;
this.isPlaying = false
}
setTimeUpdateCallback(func: (nol: number, total: number) => void) {
this.callbackTimeUpdate = func;
}
setBitrateUpdateCallback(func: (bitrateList: number[]) => void) {
this.callbackBitrateUpdate = func;
}
setErrorCallback(func: (error: string) => void) {
this.callbackErrorUpdate = func;
}
// 资源释放
async release() {
if (this.isCreate) {
this.isCreate = false;
this.isPlaying = false;
this.isLoading = false;
this.isLoadError = false;
this.isResetPause = false;
await this.avPlayer.release();
}
}
// 视频信息上报函数
async setSourceInfoCallback(): Promise<void> {
// 时间上报函数
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentTime = time
this.callbackTimeUpdate(time, this.totalDuration);
});
// 音量变化回调函数
this.avPlayer.on('volumeChange', (vol: number) => {
});
// 视频播放结束触发回调
this.avPlayer.on('endOfStream', () => {
});
// seek操作回调函数
this.avPlayer.on('seekDone', (seekDoneTime: number) => {
});
// 视频总时长上报函数
this.avPlayer.on('durationUpdate', (duration: number) => {
});
// 设置倍速播放回调函数
this.avPlayer.on('speedDone', (speed: number) => {
});
// bitrate设置成功回调函数
this.avPlayer.on('bitrateDone', (bitrate: number) => {
});
// 缓冲上报回调函数
this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
});
// 首帧上报回调函数
this.avPlayer.on('startRenderFrame', () => {
});
// 视频宽高上报回调函数
this.avPlayer.on('videoSizeChange', (width: number, height: number) => {
});
// 焦点上报回调函数
this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => {
});
// HLS上报所有支持的比特率
this.avPlayer.on('availableBitrates', (bitrates: number[]) => {
this.callbackBitrateUpdate(bitrates);
});
// 设置错误监听
this.avPlayer.on('error', (error) => {
this.callbackErrorUpdate('Error ' + error.code + ' - ' + error.message);
});
}
// 状态机
async setStateChangeCallback(): Promise<void> {
this.avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
if (this.avPlayer == null) {
return;
}
switch (state) {
// 闲置状态,成功调用reset接口后触发该状态机上报
case AVPlayerState.IDLE:
this.isLoadError = false
break;
// 资源初始化,avplayer设置播放源后触发该状态上报
case AVPlayerState.INITIALIZED:
if (this.surfaceID) {
this.setSurfaceId(this.surfaceID); // 设置显示画面,当播放的资源为纯音频时无需设置
this.avPlayer.prepare(); // 进入准备状态
}
break;
// 已准备状态
case AVPlayerState.PREPARED:
this.avPlayer.audioInterruptMode = audio.InterruptMode.INDEPENDENT_MODE;
this.totalDuration = this.avPlayer.duration;
this.currentTime = this.totalDuration * this.seekTime / 100
this.avPlayer.seek(this.currentTime, 2);
if (this.isResetPause) {
this.isPlaying = false;
this.avPlayer.pause()
}else {
this.isPlaying = true;
this.avPlayer.play();
}
this.changeSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X)
this.isLoadError = false;
break;
// 正在播放状态
case AVPlayerState.PLAYING:
this.isLoading = false;
this.isPlaying = true;
break;
// 暂停状态
case AVPlayerState.PAUSED:
this.isLoading = false;
this.isPlaying = false;
break;
// 播放至结尾状态
case AVPlayerState.COMPLETED:
this.isPlaying = false
this.callbackComplete();
break;
// 停止状态
case AVPlayerState.STOPPED:
break;
// 销毁状态,销毁与当前AVPlayer关联的播放引擎,无法再进行状态转换
case AVPlayerState.RELEASED:
this.isLoading = true;
break;
// 错误状态,当播放引擎发生不可逆的错误,则会转换至当前状态
case AVPlayerState.ERROR:
setTimeout(() => {
this.isPlaying = false;
this.isLoading = false;
this.isLoadError = true;
}, 1000)
break;
default:
break;
}
})
}
}
2) 画中画工具类封装
const CONTEXT_WIDTH: number = 1600;
const CONTEXT_HEIGHT: number = 900;
export class CustomXComponentController extends XComponentController {
onSurfaceCreated(surfaceId: string): void {
if (PipManager.getInstance().player.surfaceID === surfaceId) {
return;
}
PipManager.getInstance().player.setSurfaceId(surfaceId);
}
}
// 画中画控制器
export class PipManager {
private static instance: PipManager = new PipManager();
private pipController?: PiPWindow.PiPController;
private mXComponentController: XComponentController;
player: AVPlayer;
constructor() {
this.player = new AVPlayer();
this.mXComponentController = new CustomXComponentController();
}
public static getInstance(): PipManager {
return PipManager.instance;
}
getXComponentController(): CustomXComponentController {
return this.mXComponentController;
}
init(ctx: Context, navigationId: string) {
// 当前设备如若不支持画中画则退出
if (!PiPWindow.isPiPEnabled()) {
return;
}
let config: PiPWindow.PiPConfiguration = {
context: ctx,
// XComponent组件绑定同一个
componentController: this.mXComponentController,
navigationId: navigationId,
// 画中画媒体类型枚举,当前使用的视频播放模版
templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY,
contentWidth: CONTEXT_WIDTH,
contentHeight: CONTEXT_HEIGHT,
};
PiPWindow.create(config).then((controller: PiPWindow.PiPController) => {
this.pipController = controller;
this.pipController?.setAutoStartEnabled(false);
this.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => {
this.onStateChange(state, reason);
});
this.pipController.on('controlEvent', (control: PiPWindow.ControlEventParam) => {
this.onActionEvent(control);
});
}).catch((err: BusinessError) => {
})
}
startPip() {
this.pipController?.startPiP().then(() => {
}).catch((err: BusinessError) => {
});
}
stopPip() {
this.pipController?.stopPiP().then(() => {
}).catch((err: BusinessError) => {
});
}
setAutoStart(autoStart: boolean): void {
this.pipController?.setAutoStartEnabled(autoStart);
}
updateContentSize(width: number, height: number) {
if (this.pipController) {
this.pipController.updateContentSize(width, height);
}
}
updatePiPControlStatus() {
let controlType: PiPWindow.PiPControlType = PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE;
let status: PiPWindow.PiPControlStatus = PiPWindow.PiPControlStatus.PLAY;
this.pipController?.updatePiPControlStatus(controlType, status);
}
unregisterPipStateChangeListener() {
this.pipController?.off('stateChange');
this.pipController?.off('controlEvent');
}
// 监听画中画生命周期
onStateChange(state: PiPWindow.PiPState, _reason: string) {
switch (state) {
// 表示画中画将要启动
case PiPWindow.PiPState.ABOUT_TO_START:
break;
// 表示画中画已经启动
case PiPWindow.PiPState.STARTED:
// 启动画中画
PipManager.getInstance().player.isPipOpen = true;
break;
// 表示画中画将要停止
case PiPWindow.PiPState.ABOUT_TO_STOP:
// 画中画关闭
PipManager.getInstance().player.isPipOpen = false;
break;
// 表示画中画已经停止
case PiPWindow.PiPState.STOPPED:
// 画中画关闭
PipManager.getInstance().player.isPipOpen = false;
// // 小窗口点击关闭画中画,视频暂停播放
PipManager.getInstance().player.isPiPWindowRestore = false;
break;
// 表示画中画将从小窗播放恢复到原始播放界面
case PiPWindow.PiPState.ABOUT_TO_RESTORE:
// 小窗口复原关闭画中画
PipManager.getInstance().player.isPipOpen = false;
// 从小窗口复原
PipManager.getInstance().player.isPiPWindowRestore = true;
break;
// 表示画中画生命周期执行过程出现了异常
case PiPWindow.PiPState.ERROR:
break;
default:
break;
}
}
// 监听画中画控制面板控件动作事件
onActionEvent(control: PiPWindow.ControlEventParam) {
switch (control.controlType) {
// 视频播放、停止
case PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE:
if (control.status === PiPWindow.PiPControlStatus.PAUSE) {
// 停止视频
PipManager.getInstance().player.isPlaying = false;
} else if (control.status === PiPWindow.PiPControlStatus.PLAY) {
// 播放视频
PipManager.getInstance().player.isPlaying = true;
}
break;
// 切换到下一个视频
case PiPWindow.PiPControlType.VIDEO_NEXT:
break;
// 切换到上一个视频
case PiPWindow.PiPControlType.VIDEO_PREVIOUS:
break;
// 视频进度快进
case PiPWindow.PiPControlType.FAST_FORWARD:
break;
// 视频进度后退
case PiPWindow.PiPControlType.FAST_BACKWARD:
break;
default:
break;
}
}
}
5. 模板集成
本模板提供了两种代码集成方式,供开发者自由使用。
- 整体集成
开发者可以选择直接基于模板工程开发自己的应用工程。
- 模板代码获取:
- 打开模板工程,根据README说明中的快速入门章节,将自己的应用信息配置在模板工程内,即可运行并查看模板效果。

- 对接开发者自己的服务器接口,转换数据结构,展示真实的云侧数据。
将commons/http/src/main/ets/apis/Apis.ets文件中的mock接口替换为真实的服务器接口。

在commons/http/src/main/ets/types/Course.ets文件中将云侧开发者自定义的数据结构转换为端侧数据结构。

- 根据自己的业务内容修改模板,进行定制化开发。
2) 按需集成
若开发者已搭建好自己的应用工程,但暂未实现其中的部分场景能力,可以选择取用其中的业务组件,集成在自己的工程中。
- 组件代码获取:
- 下载组件源码,根据README中的说明,将组件包配置在自己的工程中。
- 根据API参考和示例代码,将组件集成在自己的对应场景中。
以上是第10期“课堂应用”行业优秀案例的内容,更多行业敬请期待~
欢迎下载使用行业模板“点击下载”,若您有体验和开发问题,或者迫不及待想了解XX行业的优秀案例,欢迎在评论区留言,小编会快马加鞭为您解答~
同时诚邀您添加下方二维码加入“组件模板活动社群”,精彩上新&活动不错过!

HarmonyOS官方模板优秀案例系列持续更新,点击查看往期案例汇总贴,点击收藏 “
”方便查找!
【集成有礼】HarmonyOS官方模板集成创新活动,挥洒创意,赢精美大礼!点击参加
【HarmonyOS行业解决方案】为各行业鸿蒙应用提供全流程技术方案。点击查看
更多推荐

所有评论(0)