OpenHarmony 英语学习 App 实战TTS 听力训练播放器与异常兜底设计摘要听力训练是英语学习 App 的核心能力之一。相比直接播放预录音频TTS 文本转语音更灵活可以朗读单词、例句、听力材料、每日一句甚至可以给 AI 讲解配音。本文以「英语视界 YingYu」项目中的ListeningTtsHelper.ets为例分享如何在 OpenHarmony/HarmonyOS 中封装一个更稳定的 TTS 播放辅助类。本文重点不是简单调用speak()而是如何处理真实项目中的问题引擎创建、在线/离线兜底、播放状态监听、超时保护、重复点击、停止播放和错误回调。一、为什么 TTS 需要单独封装如果页面直接调用 TTS API会遇到很多问题多个页面重复写初始化逻辑引擎创建失败不好处理播放中重复点击容易状态混乱无声失败时 UI 卡住页面退出时没有清理在线引擎不可用时没有兜底。所以项目使用单例类ListeningTtsHelper统一管理 TTS。exportclassListeningTtsHelper{privatestaticinstance:ListeningTtsHelperprivatettsEngine: textToSpeech.TextToSpeechEngine|nullnullprivateengineCreating:booleanfalseprivateconstructor() {}staticgetInstance():ListeningTtsHelper{if(!ListeningTtsHelper.instance) {ListeningTtsHelper.instancenewListeningTtsHelper() }returnListeningTtsHelper.instance} }单例的好处是整个 App 共用一个 TTS 管理器避免重复创建引擎。二、核心状态设计ListeningTtsHelper中维护了播放回调、当前文本和安全定时器privateplaybackEndCallback: (() void) |nullnullprivateplaybackErrorCallback: (() void) |nullnullprivateactiveSpeakText:stringprivatesuppressNextStopCallback:booleanfalseprivatesafetyTimerId:number -1privatereadonlySAFETY_TIMEOUT_MS:number12000这些状态分别解决播放结束后通知页面播放失败后恢复 UI保存当前正在朗读的文本避免主动 stop 触发重复回调防止静默失败。三、创建 TTS 引擎在线优先项目优先创建在线引擎privatecreateEngine(onReady: () void, onFail: () void): void {if(this.ttsEngine !null) { onReady()return}if(this.engineCreating) {return}this.engineCreating trueconstinitParams: textToSpeech.CreateEngineParams { language:en-US, person:0, online:1, extraParams: {style:interaction-broadcast,locate:US,name:ListeningTts} } }这里有两个保护如果引擎已经存在直接回调如果引擎正在创建避免重复创建。四、在线失败后离线兜底在线引擎创建失败后项目会切换到离线模式textToSpeech.createEngine(initParams,(err, engine) {this.engineCreatingfalseif(err) {console.error([ListeningTts] createEngine(online) failed:${err.code}${err.message})this.createEngineOffline((e2, eng) {if(e2) {console.error([ListeningTts] createEngine(offline) also failed:${e2.code}${e2.message})onFail() }else{this.ttsEngine engthis.attachListener()onReady() } })return}this.ttsEngine enginethis.attachListener()onReady() })离线创建函数private createEngineOffline( callback:(err: BusinessError, engine: textToSpeech.TextToSpeechEngine)void):void{ const initParams: textToSpeech.CreateEngineParams { language:en-US, person:0, online:0, extraParams: {style:interaction-broadcast,locate:US,name:ListeningTts} } textToSpeech.createEngine(initParams,(err, engine){ callback(err, engine) }) }学习类产品很适合这种策略在线优先保证效果离线兜底保证可用。五、监听播放生命周期播放状态监听是 TTS 封装里最重要的部分。privateattachListener():void{if(!this.ttsEngine)returnthis.ttsEngine.setListener({onStart:(_rid, _resp) {console.info([ListeningTts] onStart)this.clearSafetyTimer()this.startSafetyTimer(() {this.safeStop()this.invokeEnd() }) },onComplete:(_rid, _resp) {console.info([ListeningTts] onComplete)this.invokeEnd() },onStop:(_rid, _resp) {if(this.suppressNextStopCallback) {this.suppressNextStopCallbackfalsereturn}this.invokeEnd() },onError:(_rid, code:number, msg:string) {console.error([ListeningTts] onError code${code}msg${msg})this.invokeError() } }) }这里将 TTS 生命周期映射为页面可以理解的状态onStart开始播放onComplete正常结束onStop停止onError出错。六、安全定时器解决无声失败项目中有一个很实用的设计安全定时器。privatestartSafetyTimer(onTimeout:() void):void{this.clearSafetyTimer()this.safetyTimerIdsetTimeout(() {this.safetyTimerId -1console.error([ListeningTts] safety timer fired, triggering timeout)onTimeout() },this.SAFETY_TIMEOUT_MS)asnumber}清理定时器privateclearSafetyTimer(): void {if(this.safetyTimerId ! -1) {try{ clearTimeout(this.safetyTimerId) }catch(_e) {}this.safetyTimerId -1} }这个机制可以防止一种糟糕体验系统没有明确报错但用户也听不到声音页面一直显示播放中。七、播放参数更适合英语学习播放时设置SpeakParamsconstparams: textToSpeech.SpeakParams {requestId:s_${Date.now()},extraParams: {queueMode:0,speed:0.92,volume:1,pitch:1,languageContext:en-US,soundChannel:0,playType:0} }this.ttsEngine.speak(text, params)几个参数值得注意speed: 0.92稍慢一点适合学生听写和跟读languageContext: en-US英语语境volume: 1保证音量requestId每次请求唯一标识。后续还可以让用户在设置页调整语速。八、speak 方法统一入口页面调用的核心方法是speak()speak(text: string, onEnd: () void, onError: () void): void {if(!text?.trim()) { onEnd()return}this.safeStop()this.playbackEndCallback onEndthis.playbackErrorCallback onErrorthis.activeSpeakText textif(!this.ttsEngine) {this.createEngine(() {this.checkAndSpeak() }, () {this.invokeError() })return}this.startSafetyTimer(() {this.checkAndSpeak() })this.checkAndSpeak() }这个方法处理了空文本播放前停止上一次朗读保存回调引擎不存在时先创建引擎存在时直接播放启动安全定时器。页面只需要关心成功和失败不需要处理底层细节。九、停止播放和取消请求停止当前播放stop(): void {this.clearSafetyTimer()this.safeStop()this.invokeEnd() }取消待处理文本cancelPending(): void { this.activeSpeakTextthis.clearSafetyTimer() }这些方法适合在页面退出、切换材料、用户点击停止时调用。十、适合扩展的功能基于当前封装可以继续做很多英语学习功能单词发音例句朗读每日一句朗读听力材料播放慢速跟读模式单句循环播放进度 UIAI 讲解语音播报。只要底层 TTS helper 稳定上层功能扩展就会很轻松。十一、小结本文结合ListeningTtsHelper.ets分享了 OpenHarmony 英语学习 App 中 TTS 播放器的封装思路使用单例管理 TTS 引擎在线引擎优先离线引擎兜底监听onStart、onComplete、onStop、onError使用安全定时器防止静默失败播放前停止旧任务避免状态混乱通过speak()暴露统一调用入口。TTS 看起来只是一个 API 调用但真实产品里稳定性非常关键。听力训练如果播放不可靠用户很快就会放弃。因此封装一个健壮的 TTS Helper是学习类 App 非常值得投入的基础能力。