乒乓球口袋教练 HarmonyOS 学习应用(02):本地视频资源与播放器入口
一、课程 id 与视频文件不能直接拼接课程标题会随着教学内容调整rawfile 文件名却受构建产物约束。让页面直接从标题推导文件名会把改名和资源替换变成隐性风险。当前资源表使用 courseId 连接课程和视频资产播放器只接收已经解析过的资源信息。export interface CourseVideoAsset { courseId: string; fileName: string; url: string; } // Fill in your own video hosting base URL. Example: // https://your-domain.example.com/tabletennisballLearn/videos export const VIDEO_DOWNLOAD_BASE_URL: string ; interface CourseVideoFile { courseId: string; fileName: string; } const COURSE_VIDEO_FILES: CourseVideoFile[] [ { courseId: skill_penhold_grip, fileName: skill_penhold_grip.mp4 }, { courseId: skill_shakehand_grip, fileName: skill_penhold_grip.mp4 }, { courseId: skill_forehand_drive, fileName: skill_forehand_drive.mp4 }, { courseId: skill_backhand_block, fileName: skill_backhand_block.mp4 }, { courseId: skill_short_serve, fileName: skill_short_serve.mp4 }, { courseId: skill_forehand_loop, fileName: skill_forehand_loop.mp4 }, { courseId: skill_footwork_two_point, fileName: skill_footwork_two_point.mp4 }, { courseId: skill_receive_push, fileName: skill_receive_push.mp4 }, { courseId: skill_backhand_flick, fileName: skill_backhand_flick.mp4 }, { courseId: skill_forehand_flick, fileName: skill_forehand_flick.mp4 }, { courseId: skill_push_long, fileName: skill_push_long.mp4 }, { courseId: skill_sidespin_serve, fileName: skill_sidespin_serve.mp4 }, { courseId: skill_counter_loop, fileName: skill_counter_loop.mp4 }, { courseId: tactic_third_ball, fileName: tactic_third_ball.mp4 }, { courseId: tactic_placement, fileName: tactic_placement.mp4 },二、播放器入口如何先确认资源存在CourseVideoService 先查找资源条目再确认打包资源或已下载文件是否存在。详情页根据这个结果决定能否显示播放或下载入口播放器不负责猜测文件路径。这样资源缺失的原因能在进入媒体组件前被解释而不是把黑屏当作播放失败。事实唯一来源页面职责正常结果模型或服务的回读渲染可观察状态边界结果明确的缺项或失败分支说明原因并保留入口重进结果持久化或模型再次解析不复用旧页面变量export class CourseVideoService { static canDownload(courseId: string): boolean { const asset: CourseVideoAsset | undefined findCourseVideoAsset(courseId); return asset ! undefined asset.url.length 0; } static getDownloadFileName(courseId: string): string { const asset: CourseVideoAsset | undefined findCourseVideoAsset(courseId); return asset?.fileName ?? ; } static getDownloadedVideoPath(context: common.UIAbilityContext, courseId: string): string { const asset: CourseVideoAsset | undefined findCourseVideoAsset(courseId); if (asset undefined) { return ; } return CourseVideoService.videoDir(context) / asset.fileName; } static getDownloadedVideoUri(context: common.UIAbilityContext, courseId: string): string { const path: string CourseVideoService.getDownloadedVideoPath(context, courseId); if (path.length 0 || !CourseVideoService.exists(path)) { return ; } return file:// path; } static hasDownloadedVideo(context: common.UIAbilityContext, courseId: string): boolean { const path: string CourseVideoService.getDownloadedVideoPath(context, courseId); return path.length 0 CourseVideoService.exists(path); } static managedDownloads(context: common.UIAbilityContext): DownloadVideoItem[] { const result: DownloadVideoItem[] []; for (const courseId of MANAGED_COURSE_IDS) { const course: Course | undefined findCourse(courseId); const asset: CourseVideoAsset | undefined findCourseVideoAsset(courseId); if (course ! undefined asset ! undefined) { const path: string CourseVideoService.getDownloadedVideoPath(context, courseId); const item: DownloadVideoItem new DownloadVideoItem(); item.courseId courseId; item.title course.title; item.fileName asset.fileName; item.path path;三、下载状态为什么不能只存在按钮里下载完成也不能只写一个“已下载”布尔值。服务需要检查目标文件是否真实存在下载页、详情页和播放器复用这个判断。重启后重新读取文件状态才能避免详情页显示已下载而下载列表为空的错觉。} return TXT_NO_VIDEO_PLACEHOLDER; } private openPreferredVideo(c: Course): void { if (this.hasPlayableVideo(c)) { const params: PlayerParams { courseId: c.id, startSec: 0 }; router.pushUrl({ url: Routes.PLAYER, params }); return; } AppToast.show(TXT_NO_VIDEO); } private downloadActionText(): string { if (this.savingVideo) { return TXT_DOWNLOADING_LOCAL; } return this.downloadedVideo ? TXT_DOWNLOADED_LOCAL : TXT_DOWNLOAD_LOCAL; } private refreshDownloadState(c: Course): void { const ctx: common.UIAbilityContext getContext(this) as common.UIAbilityContext; this.downloadedVideo CourseVideoService.hasDownloadedVideo(ctx, c.id); } private confirmSaveVideo(c: Course): void { if (this.savingVideo || this.downloadedVideo) { return; } promptAction.showDialog({ title: TXT_DOWNLOAD_CONFIRM_TITLE, message: TXT_DOWNLOAD_CONFIRM_MSG, buttons: [ { text: TXT_DOWNLOAD_ONLY_LOCAL, color: this.palette.textSecondary }, { text: TXT_DOWNLOAD_TO_GALLERY, color: this.palette.primary } ] }).then((result: promptAction.ShowDialogSuccessResponse) {四、播放失败怎样留在可解释的页面异常时要区分三种情况课程不存在、资源表没有映射、文件没有落地。它们都不应生成空地址或默认跳到其他课程页面应保留返回或重新下载的操作。把三种问题混成一个“播放失败”会让维护者无法定位是模型还是文件出了问题。风险不能采用的做法当前链路的处理数据不存在用默认对象继续渲染停在可解释的空或失败状态页面重进复用上一次内存字段重新查询模型或服务重复动作追加同一业务事实通过稳定 id 或进入标记收敛AppSizes, AppText, ColorPalette, LightPalette, PlaceholderImage, AppToast, PlayerParams, Course, findCourse, nextCourse, prevCourse, Routes, ProgressService, formatDuration, courseCover, courseVideoSrc, CourseVideoService } from library1; const SPEEDS: number[] [0.5, 1.0, 1.25, 1.5, 2.0]; Component export struct FullscreenPlayerPage { StorageProp(palette) palette: ColorPalette LightPalette; State course: Course | null null; State playing: boolean true; State currentSec: number 0; State speedIdx: number 1; State showControl: boolean true; State videoReady: boolean false; State videoError: boolean false; State realDuration: number 0; State resolvedVideoSrc: ResourceStr ; State hasResolvedVideo: boolean false; private videoController: VideoController new VideoController(); // 占位模式计时器无视频文件时使用 private timerId: number -1; private hideTimer: number -1; aboutToAppear(): void { const p: PlayerParams router.getParams() as PlayerParams;五、本地视频的回读动作验收依次覆盖已映射课程、没有资源映射的课程和下载后重新进入。对已映射课程观察标题、媒体入口和控制层对不可用课程确认入口被解释性地收敛对下载后重进确认列表与详情得到同一个文件状态。验收阶段操作应观察到的结果建立事实完成一次与主题直接对应的动作服务或模型能回读该事实页面回读进入目标页或结果页标题、数据和状态相互一致重进验证返回、重启或切换模块后再次进入结果不依赖上一页残留六、取舍与后续维护当前实现把业务事实集中到模型与服务页面只负责呈现和触发操作。这样做会多出类型、查询和回读代码却能避免同一份统计或记录在多个页面分叉。需要扩展新课程、新资源或新题目时应先补齐稳定标识与服务合同再补页面入口。ArkTS 的响应式状态机制可参考 HarmonyOS ArkTS 状态管理文档。本文的代码和验收围绕同一个原则可见结果必须能回到对应模型、服务或持久化事实不以邻近页面和控件文字代替主题结果。七、围绕事实来源继续演进乒乓球口袋教练 HarmonyOS 学习应用02本地视频资源与播放器入口 关注的不是给页面再增加一层显示逻辑而是让“课程 id 与视频文件不能直接拼接”始终能回到唯一的业务事实。模型字段、服务返回值和页面展示应当保持单向关系事实先发生页面随后回读页面不保存第二份能够被误认为真实数据的临时副本。处理“播放器入口如何先确认资源存在”时新增条件必须先归属到课程模型、资源服务、日志事实或测验结果之一。把条件散落在多个组件里短期看起来省事后续就会出现入口不同、重进不同、统计不同的问题。把规则放回稳定入口页面只根据结果改变可见状态能让同一结论在不同入口被复查。“下载状态为什么不能只存在按钮里”说明当前实现选择了页面重建后重新查询而不是把对象和统计快照长期塞进路由参数。读取会增加少量成本却避免课程改名、资源替换、日志编辑或题库切换后继续显示旧内容。学习应用的结果需要能解释不能只追求一次渲染恰好看起来正确。异常分支也应保留语义异常时要区分三种情况课程不存在、资源表没有映射、文件没有落地。它们都不应生成空地址或默认跳到其他课程页面应保留返回或重新下载的操作。把三种问题混成一个“播放失败”会让维护者无法定位是模型还是文件出了问题。 这要求页面区分数据不存在、动作未完成和资源不可用并提供返回选择、补充输入、重新下载或再次答题等对应操作不能把不同原因压缩成同一条模糊的成功或失败提示。最后执行“本地视频的回读动作”时至少观察一次主题动作、一次目标页回读和一次重进或切换后的回读。三次观察都指向相同模型或服务结果才说明实现链路没有依赖旧页面残留。 最后执行“本地视频的回读动作”时至少观察一次主题动作、一次目标页回读和一次重进或切换后的回读。三次观察都指向相同模型或服务结果才说明实现链路没有依赖旧页面残留。面向维护的检查可以沿三个问题展开输入对象的稳定标识是什么哪个服务或模型决定结果页面如何把该结果显示出来。课程 id 先映射到受管理的视频资源再由详情和播放器统一判断可播放性避免资源缺失时仍创建空播放器。 这三个问题分别对应数据合同、调用边界和可见反馈只要其中一层被绕过新增入口就可能产生和原入口不同的事实。对失败路径也要保留同样的追踪能力。先让服务返回清晰的不可用、缺项或未完成状态再由页面提供与原因匹配的操作。这样日志、课程、资源和题目发生变化时回归测试能够看到具体的分歧点而不是只能面对一张没有信息的空白页面。这种组织不会承诺所有后续需求都零成本。新增字段需要同步更新模型、服务与验收但它把变更范围限制在可枚举的位置避免把同一判断复制到首页、详情、个人页和结果页后再分别修补。

相关新闻

2026年TikTok算法突变,我砸了15万才总结出这3条跨境社媒避坑铁律(深度复盘)

2026年TikTok算法突变,我砸了15万才总结出这3条跨境社媒避坑铁律(深度复盘)

一、一场悄然来临的变革:当流量逻辑开始“转弯” 2025年末至2026年初,许多跨境社交媒体从业者隐约感觉到一丝异样。曾经屡试不爽的内容策略,互动数据开始波动;稳定增长的流量曲线,出现了难以解释的平缓甚至下滑。起初&…

2026/10/19 15:57:33 阅读更多 →
Windows系统Mesa3D图形驱动终极指南:免费开源OpenGL/Vulkan加速解决方案

Windows系统Mesa3D图形驱动终极指南:免费开源OpenGL/Vulkan加速解决方案

Windows系统Mesa3D图形驱动终极指南:免费开源OpenGL/Vulkan加速解决方案 【免费下载链接】mesa-dist-win Pre-built Mesa3D drivers for Windows 项目地址: https://gitcode.com/gh_mirrors/me/mesa-dist-win 想要在Windows系统上获得强大的开源图形驱动支持…

2026/10/20 12:16:10 阅读更多 →
性价比高的工商变更服务商

性价比高的工商变更服务商

今天下午,我刚从一个客户那回来。他微信上跟我说了一句话,让我差点想把手机摔了。“兄弟,我上个月找的那家代办,收费是便宜,880块全包。结果呢?我公司地址变更了三个月都没下来,税务局那边还多了…

2026/10/21 5:41:41 阅读更多 →

最新新闻

DNS故障排查与监控实战:从原理到告警SOP

DNS故障排查与监控实战:从原理到告警SOP

作为常年跟线上故障打交道的运维,我最大的感受就是:DNS问题是最让人头疼的“隐形杀手”。表面上看,用户访问不了服务,监控面板上数据库、缓存、带宽全都正常,但服务就是进不去。前几天某知名国际化社交平台出现的大规模…

2026/10/21 6:15:31 阅读更多 →
Hadoop+Spark+Hive游戏推荐系统毕业设计全流程实战

Hadoop+Spark+Hive游戏推荐系统毕业设计全流程实战

1. 项目概述与整体认知如果你正在为毕业设计发愁,看到“hadoopsparkhive游戏推荐系统”这种组合,大概率心里没底:这三样东西都是大数据领域绕不开的重型框架,单拎一个出来都够写好几篇论文,三个放一起还要做一个推荐系…

2026/10/21 6:15:31 阅读更多 →
教培机构销售工作台:公海与私海客户流转机制设计

教培机构销售工作台:公海与私海客户流转机制设计

1. 公海与私海:先搞清楚客户池怎么分干教培销售管理这些年,我见过太多团队死磕“线索分配不均”这个问题。新顾问抱怨拿到的名单质量差,老顾问嫌手里的客户跟不动,主管夹在中间天天当裁判。其实问题往往不在人,而在客户…

2026/10/21 6:15:31 阅读更多 →
VSCode搭建ROS开发调试环境全攻略:从配置到断点调试

VSCode搭建ROS开发调试环境全攻略:从配置到断点调试

用VSCode搭建ROS开发调试环境这件事,我从抗拒到真香,中间隔了一次难熬的排查经历。之前写ROS(Robot Operating System,机器人操作系统)代码,我习惯gedit开文件、终端敲命令、printf打印调试,这套…

2026/10/21 6:15:31 阅读更多 →
技能清单总白写?从四分类到能力地图,教你建立可验证的技能管理体系

技能清单总白写?从四分类到能力地图,教你建立可验证的技能管理体系

1. 先搞清楚一件事:为什么你的技能清单写了等于白写我见过太多人在简历、项目复盘或个人主页里放一大串技能关键词,从 Python、Excel、项目管理一路写到沟通能力、团队协作,看着挺全,真到面试官追问"你的 Python 到底做到什么…

2026/10/21 6:15:31 阅读更多 →
AHP层次分析法从原理到实战:判断矩阵、权重计算与一致性检验全解析

AHP层次分析法从原理到实战:判断矩阵、权重计算与一致性检验全解析

这份学习笔记断断续续改了三版。第一次只是抄了公式,第二次照葫芦画瓢跑通了案例,第三次才算真正想明白:层次分析法(AHP)这套东西,表面上是在算权重,本质上是逼着你把脑子里那团模糊的“我觉得”…

2026/10/21 6:14:30 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →