OpenHarmony 英语学习 App 实战:TTS 听力训练播放器与异常兜底设计
OpenHarmony 英语学习 App 实战:TTS 听力训练播放器与异常兜底设计
摘要
听力训练是英语学习 App 的核心能力之一。相比直接播放预录音频,TTS 文本转语音更灵活:可以朗读单词、例句、听力材料、每日一句,甚至可以给 AI 讲解配音。本文以「英语视界 YingYu」项目中的 ListeningTtsHelper.ets 为例,分享如何在 OpenHarmony/HarmonyOS 中封装一个更稳定的 TTS 播放辅助类。🎧
本文重点不是简单调用 speak(),而是如何处理真实项目中的问题:引擎创建、在线/离线兜底、播放状态监听、超时保护、重复点击、停止播放和错误回调。
一、为什么 TTS 需要单独封装?
如果页面直接调用 TTS API,会遇到很多问题:
- 多个页面重复写初始化逻辑;
- 引擎创建失败不好处理;
- 播放中重复点击容易状态混乱;
- 无声失败时 UI 卡住;
- 页面退出时没有清理;
- 在线引擎不可用时没有兜底。
所以项目使用单例类 ListeningTtsHelper 统一管理 TTS。
export class ListeningTtsHelper {
private static instance: ListeningTtsHelper
private ttsEngine: textToSpeech.TextToSpeechEngine | null = null
private engineCreating: boolean = false
private constructor() {}
static getInstance(): ListeningTtsHelper {
if (!ListeningTtsHelper.instance) {
ListeningTtsHelper.instance = new ListeningTtsHelper()
}
return ListeningTtsHelper.instance
}
}
单例的好处是:整个 App 共用一个 TTS 管理器,避免重复创建引擎。
二、核心状态设计
ListeningTtsHelper 中维护了播放回调、当前文本和安全定时器:
private playbackEndCallback: (() => void) | null = null
private playbackErrorCallback: (() => void) | null = null
private activeSpeakText: string = ''
private suppressNextStopCallback: boolean = false
private safetyTimerId: number = -1
private readonly SAFETY_TIMEOUT_MS: number = 12000
这些状态分别解决:
- 播放结束后通知页面;
- 播放失败后恢复 UI;
- 保存当前正在朗读的文本;
- 避免主动 stop 触发重复回调;
- 防止静默失败。
三、创建 TTS 引擎:在线优先
项目优先创建在线引擎:
private createEngine(onReady: () => void, onFail: () => void): void {
if (this.ttsEngine !== null) {
onReady()
return
}
if (this.engineCreating) {
return
}
this.engineCreating = true
const initParams: textToSpeech.CreateEngineParams = {
language: 'en-US',
person: 0,
online: 1,
extraParams: {
'style': 'interaction-broadcast',
'locate': 'US',
'name': 'ListeningTts'
}
}
}
这里有两个保护:
- 如果引擎已经存在,直接回调;
- 如果引擎正在创建,避免重复创建。
四、在线失败后离线兜底
在线引擎创建失败后,项目会切换到离线模式:
textToSpeech.createEngine(initParams, (err, engine) => {
this.engineCreating = false
if (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 = eng
this.attachListener()
onReady()
}
})
return
}
this.ttsEngine = engine
this.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 封装里最重要的部分。
private attachListener(): void {
if (!this.ttsEngine) return
this.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.suppressNextStopCallback = false
return
}
this.invokeEnd()
},
onError: (_rid, code: number, msg: string) => {
console.error(`[ListeningTts] onError code=${code} msg=${msg}`)
this.invokeError()
}
})
}
这里将 TTS 生命周期映射为页面可以理解的状态:
onStart:开始播放;onComplete:正常结束;onStop:停止;onError:出错。
六、安全定时器:解决无声失败
项目中有一个很实用的设计:安全定时器。
private startSafetyTimer(onTimeout: () => void): void {
this.clearSafetyTimer()
this.safetyTimerId = setTimeout(() => {
this.safetyTimerId = -1
console.error('[ListeningTts] safety timer fired, triggering timeout')
onTimeout()
}, this.SAFETY_TIMEOUT_MS) as number
}
清理定时器:
private clearSafetyTimer(): void {
if (this.safetyTimerId !== -1) {
try {
clearTimeout(this.safetyTimerId)
} catch (_e) {}
this.safetyTimerId = -1
}
}
这个机制可以防止一种糟糕体验:系统没有明确报错,但用户也听不到声音,页面一直显示播放中。
七、播放参数:更适合英语学习
播放时设置 SpeakParams:
const params: 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 = onEnd
this.playbackErrorCallback = onError
this.activeSpeakText = text
if (!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.activeSpeakText = ''
this.clearSafetyTimer()
}
这些方法适合在页面退出、切换材料、用户点击停止时调用。
十、适合扩展的功能
基于当前封装,可以继续做很多英语学习功能:
- 单词发音;
- 例句朗读;
- 每日一句朗读;
- 听力材料播放;
- 慢速跟读模式;
- 单句循环;
- 播放进度 UI;
- AI 讲解语音播报。
只要底层 TTS helper 稳定,上层功能扩展就会很轻松。
十一、小结
本文结合 ListeningTtsHelper.ets,分享了 OpenHarmony 英语学习 App 中 TTS 播放器的封装思路:
- 使用单例管理 TTS 引擎;
- 在线引擎优先,离线引擎兜底;
- 监听
onStart、onComplete、onStop、onError; - 使用安全定时器防止静默失败;
- 播放前停止旧任务,避免状态混乱;
- 通过
speak()暴露统一调用入口。
TTS 看起来只是一个 API 调用,但真实产品里稳定性非常关键。听力训练如果播放不可靠,用户很快就会放弃。因此,封装一个健壮的 TTS Helper,是学习类 App 非常值得投入的基础能力。🎧

更多推荐

所有评论(0)