资讯动态

鸿蒙 TTS 播放时序实战:让听书不丢字、不跳段,我用“完成信号 + EMA 校准“对齐朗读进度

发布时间:2026/8/27 19:44:05 来源:尧图企业网站定制
这是我开发的第三个鸿蒙 App已经上架华为应用市场。前两个是git仓库查看工具和批量加水印工具这一个是把文档变成声音的听书软件「BookVoice」。三个 App 做完我最想说的一句话是真正让你脱层皮的不是语言是系统思维——而这套思维第三个 App 时已经在复用了。这个 App 是干嘛的名字叫BookVoice一句话把 TXT / MD / PDF / DOCX / EPUB 等文档变成声音用耳朵听书。目标用户很具体——通勤路上想把积压的电子书读完的上班族、做家务时想解放双眼继续学习的人、出差途中想听长文但不想把私人文档交给在线工具的阅读爱好者。听起来很常规其实听书 App 有个天然的技术难题朗读不能丢字、不能跳段。你写前端、写工具类 App 时从没想过逐句读一段文本能成为核心难点——直到你真正去实现。先看它有哪些功能后面讲技术点你就知道每个功能硬在哪多格式即导即读TXT / MD / PDF / DOCX / EPUB 一键导入PDF 自动逐页提取文本并智能分段TXT 保持原文分段并合并被拆散的句子✨朗读跟随高亮当前朗读段落高亮 当前朗读字逐字金色跟随这个后面展开讲⏯️专业播放控制精确到句的快进快退、0.5x~2.0x 倍速、章节目录一键跳转⏰睡眠计时30/60/90/120 分钟定时停止到点自动停播后台持续朗读退出页面、锁屏、切 App 不中断系统控制中心播控听书数据统计今日 / 累计时长 聆听目标进度环 已听 / 未听书单聆听时长解锁 10 级勋章☁️云端同步华为账号登录听书进度云端同步换机无缝接续隐私保障文档全程本地处理、不上传、不存储下面挑三个花心思最多的技术点展开——它们正好是前两个 App 都没碰到过的领域文本解析、语音播放时序还有逐字位置。先看选型差异一张表说清楚维度传统 Web / AndroidHarmonyOSArkTS ArkUI Stage语言JS / Kotlin 等ArkTS TypeScript 严格子集no-any/no-untyped-obj-literals一票禁止UIDOM / XML 布局ArkUI 声明式ComponentState状态驱动生命周期Activity / 页面栈Stage 模型UIAbilityAbilityStage导航用NavPathStack语音第三方 SDKkit.CoreSpeechKit文本转语音callback 为主需手工 Promise 包装云自建后端AGC 端云一体化CloudDB / 匿名登录一条龙ArkTS 严格模式已经是老朋友了——前两个 App 就领教过。但真正让我意外的是语音合成 API 的回调地狱downloadVoice这种接口 SDK 只给 callback 重载没有 Promise得自己包装进度事件载荷还是个字符串得剥掉非数字字符解析成 0~100。三个核心技术点① 文本解析PDF 不是读出来就完了PDF 逐页提取文本后最烦人的是硬换行拆词——坐在两个字可能被拆成两行直接朗读就会断句奇怪。自研TextParser干三件事按句末标点。识别句子边界、识别章节标题「第一章」「Chapter 1」甚至裸阿拉伯数字「1」、按空行合并自然段落。这样解析出的文本才能按段落为单位朗读而不是一个字符一个字符地念。// 核心把被硬换行拆散的文本按句末标点重新合并成自然段落functionparseParagraphs(lines:string[]):string[]{constparas:string[][];letcur;for(consttoflines){if(isChapterTitle(t)){// 章节标题独立成段if(cur){paras.push(cur);cur;}paras.push(t);}elseif(endsSentence(t)){// 句末标点收尾 → 段落结束curt;paras.push(cur);cur;}else{curt;// 续行合并}}if(cur){paras.push(cur);}returnparas;}踩过的坑PDF 段落编号独立成行的「1」「2」会被并进下一段成为首行首字。最后给isChapterTitle加了整行仅 1~3 位数字即视为标题的规则与既有章节识别对称问题根治。② TTS 播放时序让朗读不丢字、不跳段听书 App 的命门是换段时机。最初按每秒 tick 反查段落推进结果段末 1~2 字被掐断、段首 1~3 字被跳过——用户听着听着就少几个字。根因是估算播放时长与真实 TTS 播报有偏差。改法是让真实 TTS 播报完成信号驱动换段onComplete置完成标记配合估算结束点next cap双重校验再加超时兜底同时用 EMA 指数加权校准播报速度让进度条与语音始终同步。模拟器上 TTS 每段真实触发 onComplete 但耗时只有 0.6~1.3 秒远低于估算一度被误判为播完连续跳段——加了个可信区间过滤realSec ∈ [0.5×, 2.5×]×估算失真耗时不参与校准。// 核心换段由「估算结束点已到」「完成信号或超时兜底」共同驱动if(nextcap(completionArrived||stallTicksstallLimit)){advanceParagraph();completionArrivedfalse;stallTicks0;}这段工程解决的是听书最基本的问题不丢字、不跳段。它不花哨但很基础——听书嘛听着顺耳最重要。③ 朗读跟随高亮逐字位置是「算」出来的这个功能源自一个很朴素的需求朗读到哪字就亮到哪。用户看着正文声音走到哪个字那个字上就有一个金色小框跟着移动。竞品 iOS 应用有鸿蒙上没有现成方案——因为两堵墙摆在那Text 组件没有逐字位置 API拿不到「第 N 个字符在屏幕上的 (x, y)」TTS 回调里也没有逐字进度不知道现在读到第几个字。只能自己算。位置用measureText逐字测宽再按和正文完全一致的断行规则中文逐字断、英文按空格断词、首行缩进一致做贪心换行模拟出每个字的坐标// 核心把第 N 个字符翻译成屏幕坐标规则与正文渲染完全一致functionlocateChar(text:string,n:number):{x:number;y:number}{letline0,x0;for(leti0;in;i){constwmeasureText(text[i]);// 逐字测宽if(xwlineWidth){line;x0;}// 贪心换行xw;}return{x,y:line*lineHeight};}进度同样没有回调——固定语速估算会漂移于是用真实播报耗时反推实测语速EMA 校准 前导系数补偿段内停顿让金框既不落后也不超前地跟住当前朗读的字。这大概是这个 App 里最「教科书上没有、只能自己造」的一段工程没有逐字 API 的鸿蒙上逐字高亮就是这么硬算出来的。做得好的 还能优化的做得好的文本解析 TTS 时序双管齐下朗读稳定不丢字逐字跟随高亮让看字听书有了跟读感AVSession 后台播控 书架迷你条让边做家务边听体验完整AGC 端云一体化让换机接续听书进度几乎零后端成本。还能优化的PDF 复杂版式多栏、表格、页眉页脚的解析准确率还有提升空间大文档首次解析耗时较长希望进一步优化分阶段解析策略听书时长统计目前只做了按日结算周 / 月 / 年维度统计在 TODO 里。给同行的话一个技术路线的价值在第二个、第三个 App 时才真正显现。git仓库查看、加水印、听书三个完全不同的 App但共用同一套 ArkTS 方法论严格模式逼出的类型意识、Stage 模型的生命周期管理、AGC 端云一体化的免后端打法。每做一个都在往这套方法论里加新的一块。如果你也在手机上读了大量电子书、长文想试试用耳朵读完华为应用市场搜「BookVoice」就能用。转载请注明文章来源链接https://hanhan.pro/my-third-harmony-app-book-voice/作者Reno

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价