
- 个人主页:VON
- 文章所属专栏:从0开始的开源鸿蒙6.0.0
- 个人抖音:清洒
目录
[初始化方法 initListener()](#初始化方法 initListener())
[2.播放 / 停止语音的交互逻辑](#2.播放 / 停止语音的交互逻辑)
[3. 语音播放的状态同步](#3. 语音播放的状态同步)
一、前言
刚才看了下官网说的是只能真机测试,虚拟机不可以实现此功能,所以本章节可能无法进行测试,这里就简单帮大家分析下代码,尽量理解里面的逻辑,我估计这种小功能可能会有完整的组件可以应用,当然只是我个人理解。大家主要还是去学习前6章,本章其实可以跳过,我这里是根据官方文档一点一点进行学习的,就不跳过了。
二、实践
本章节内容参考官方链接
2.1、原理分析

官方解释:
- 首先将实现文本转语音相关的模块导入工程。
- 设置创建TextToSpeechEngine引擎的参数,TextToSpeechEngine下文简称引擎。
- 调用createEngine()接口,创建TextToSpeechEngine实例。
- 得到TextToSpeechEngine实例对象后,实例化SpeakParams对象、SpeakListener对象。
- 调用speak()接口进行播报。
2.2、创建文本朗读引擎
要先创建一个Speaker用于完成朗读引擎的工作

创建实例完成初始化引擎
这里的参数详细解释如下,大家可以根据参数进行调整
language: 'zh-CN' 指定语音合成使用的语言,
zh-CN
表示中文(中国大陆)。person: 0 选择语音合成的发音人(音色),数值
0
通常对应默认的标准音色,不同引擎可能会有不同的数值对应不同的人声(如男声、女声、儿童声等)。online: 1 设置是否使用在线合成模式,
1
表示启用在线模式(通常能提供更丰富的音色和更好的合成效果),0
则表示使用离线模式。extraParams(额外参数,以 JSON 对象形式存在)
style: 'interaction-broadcast'
:指定语音的风格,interaction-broadcast
通常表示交互播报风格,适合对话式场景。locate: 'CN'
:指定地区,CN
表示中国地区,可能影响语音的本地化处理。name: 'EngineName'
:指定 TTS 引擎的名称,可能用于区分不同的引擎实例或版本。
2.3、监听播报状态
先定义一个语音合成(TTS)的事件监听器 speakListener
及其初始化方法 initListener()
,用于处理语音合成过程中的各种事件回调

核心作用
speakListener
是一个监听器对象,用于监听文本转语音(TTS)过程中的关键事件(开始、完成、停止、数据返回、错误等),并在对应事件发生时执行相应逻辑。
各部分说明
变量定义
speakListener?: textToSpeech.SpeakListener;
- 声明了一个可选的
speakListener
变量,类型为textToSpeech.SpeakListener
(TTS 引擎定义的监听器接口)。 ?
表示该变量可以为undefined
(非必需)。
初始化方法 initListener()
该方法为 speakListener
赋值,定义了各类事件的回调函数:
-
onStart
回调onStart(requestId: string, response: textToSpeech.StartResponse) {}
- 触发时机:语音合成开始时调用。
- 参数:
requestId
:当前合成请求的唯一标识(用于区分多个请求)。response
:开始合成的响应信息(可能包含合成任务的元数据)。
-
onComplete
回调onComplete(requestId: string, response: textToSpeech.CompleteResponse) { if (response.type === 1) { emitter.emit("eventId"); } }
- 触发时机:语音合成正常完成时调用。
- 逻辑:当
response.type
为1
时,通过emitter
触发名为eventId
的事件(可能用于通知上层逻辑合成已完成)。
-
onStop
回调onStop(requestId: string, response: textToSpeech.StopResponse) { if (response.type === 1) { emitter.emit("eventId"); } }
- 触发时机:语音合成被主动停止时调用(如调用了停止接口)。
- 逻辑:与
onComplete
类似,当response.type
为1
时,触发eventId
事件。
-
onData
回调onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {}
- 触发时机:合成过程中返回音频数据时调用(通常是流式返回,多次触发)。
- 参数:
audio
:合成的音频数据(二进制缓冲区,可用于播放)。response
:音频数据的相关信息(如格式、进度等)。
-
onError
回调onError(requestId: string, errorCode: number, errorMessage: string) {}
- 触发时机:合成过程中发生错误时调用(如网络异常、参数错误等)。
- 参数:
errorCode
:错误码(用于定位具体错误类型)。errorMessage
:错误描述信息(可读性文本)。
2.4、创建文本转语音(TTS)引擎实例
定义一个 createEngine()
方法,用于创建文本转语音(TTS)引擎实例,并进行初始化配置

异常捕获结构
try {
// 核心逻辑:创建引擎
} catch (error) {
// 捕获并处理创建过程中的异常
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
}
- 使用
try-catch
包裹核心逻辑,防止创建引擎时的同步错误导致程序崩溃 - 捕获到错误后,提取错误信息(
message
)和错误码(code
),便于问题定位(目前仅提取未做进一步处理,实际应用中可能会添加日志记录或用户提示)
创建引擎的核心调用
textToSpeech.createEngine(
this.initParamsInfo, // 参数1:初始化配置
// 参数2:回调函数(处理创建结果)
(err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
if (!err) {
// 成功逻辑
this.ttsEngine = textToSpeechEngine;
this.ttsEngine.setListener(this.speakListener);
} else {
// 失败逻辑(目前为空)
}
}
);
- 调用
textToSpeech.createEngine
接口创建引擎,接收两个参数:- 第一个参数 :
this.initParamsInfo
即之前定义的引擎初始化参数(语言、音色、在线模式等配置) - 第二个参数 :回调函数用于处理创建结果(异步返回),包含两个参数:
err
:错误信息(如果创建失败,会包含错误详情;成功则为null
或undefined
)textToSpeechEngine
:创建成功后的引擎实例对象
- 第一个参数 :
创建成功后的处理
if (!err) {
this.ttsEngine = textToSpeechEngine; // 保存引擎实例到当前对象
this.ttsEngine.setListener(this.speakListener); // 绑定事件监听器
}
- 当
err
不存在时,表示引擎创建成功 - 将创建好的引擎实例
textToSpeechEngine
保存到当前对象的this.ttsEngine
属性中,方便后续调用(如执行语音合成) - 通过
setListener
方法为引擎绑定之前初始化的speakListener
,使引擎在合成过程中能触发对应的事件回调(开始、完成、错误等)
2.5、初始化监听器和引擎
constructor() {
this.initListener()
this.createEngine()
}
最后别忘了导出,导出后才可以被其他模块访问

2.6、使用引擎

最后编写停止销毁引擎函数即可

三、测试
这里单独创建一个组件进行测试,这里使用quickstart组件

import { emitter } from '@kit.BasicServicesKit';
import { Speaker } from 'utils';
@Component
export struct Header {
@State message: string = '快速入门';
@State isPlaying: boolean = false;
speaker: Speaker = new Speaker();
content: string = '余承东邀请你开启鸿蒙体验之旅 欢迎来到HarmonyOS世界';
aboutToAppear(): void {
emitter.on('eventId', () => {
this.isPlaying = false;
});
}
build() {
Row() {
Text(this.message)
.fontFamily('HarmonyHeiTi-Bold')
.fontSize(24)
.textAlign(TextAlign.Start)
.lineHeight(33)
.fontWeight(700)
Image(this.isPlaying ? $r('app.media.ic_AI_read_on') : $r('app.media.ic_AI_read_normal'))
.width(40)
.height(40)
.onClick(() => {
this.isPlaying = !this.isPlaying;
if (this.isPlaying === true) {
this.speaker.startSpeak(this.content)
} else {
this.speaker.stopSpeak()
}
})
}
.padding({
left: 16,
right: 16
})
.justifyContent(FlexAlign.SpaceBetween)
.height(56)
.margin({
bottom: 11
})
.width('100%')
}
}
语音功能的触发与控制流程
1.初始化与事件监听
aboutToAppear(): void {
emitter.on('eventId', () => {
this.isPlaying = false; // 收到事件后,将播放状态设为"未播放"
});
}
aboutToAppear
是组件生命周期方法(组件出现前调用)。- 这里通过
emitter.on
监听名为eventId
的事件,当事件触发时(通常是语音播放完成或被停止时),会将isPlaying
设为false
,同步更新 UI 状态(如切换按钮图标)。
2.播放 / 停止语音的交互逻辑
Image(this.isPlaying ? $r('app.media.ic_AI_read_on') : $r('app.media.ic_AI_read_normal'))
.width(40)
.height(40)
.onClick(() => {
this.isPlaying = !this.isPlaying; // 切换播放状态
if (this.isPlaying === true) {
this.speaker.startSpeak(this.content); // 开始播放语音
} else {
this.speaker.stopSpeak(); // 停止播放语音
}
})
- 按钮图标 :根据
isPlaying
状态显示不同图标(播放中显示 "正在播放" 图标,停止时显示 "默认" 图标)。 - 点击事件 :
- 点击时先切换
isPlaying
状态(取反)。 - 若切换后为
true
(开始播放):调用speaker.startSpeak(this.content)
,传入需要朗读的文本content
,由Speaker
类处理文本转语音并播放。 - 若切换后为
false
(停止播放):调用speaker.stopSpeak()
,由Speaker
类终止当前语音播放。
- 点击时先切换
3. 语音播放的状态同步
当语音播放完成(正常结束)或被停止(主动调用停止)时:
Speaker
内部会通过之前定义的speakListener
监听器捕获onComplete
或onStop
事件(参考前面的speakListener
代码)。- 监听器中会触发
emitter.emit("eventId")
,发送eventId
事件。 - 本组件通过
emitter.on('eventId', ...)
收到事件后,将isPlaying
设为false
,自动更新按钮图标为 "未播放" 状态,完成 UI 与实际播放状态的同步。

这里在主页面中引用下就行了,因为我这里没有真机就用@Preview简单进行下预览

四、代码
核心文件:
Speaker
import { textToSpeech } from '@kit.CoreSpeechKit';
import { BusinessError, emitter } from '@kit.BasicServicesKit';
export class Speaker {
ttsEngine?: textToSpeech.TextToSpeechEngine;
extraParam: Record<string, Object> = {
"queueMode": 0,
"speed": 1,
"volume": 2,
"pitch": 1,
"languageContext": 'zh-CN',
"audioType": "pcm",
"soundChannel": 3,
"playType": 1
};
//初始化引擎
initParamsInfo: textToSpeech.CreateEngineParams = {
language: 'zh-CN',
person: 0,
online: 1,
extraParams: { "style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName' }
};
speakListener?: textToSpeech.SpeakListener;
constructor() {
this.initListener()
this.createEngine()
}
initListener() {
this.speakListener = {
onStart(requestId: string, response: textToSpeech.StartResponse) {
},
onComplete(requestId: string, response: textToSpeech.CompleteResponse) {
if (response.type === 1) {
emitter.emit("eventId");
}
},
onStop(requestId: string, response: textToSpeech.StopResponse) {
if (response.type === 1) {
emitter.emit("eventId");
}
},
onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
},
onError(requestId: string, errorCode: number, errorMessage: string) {
}
};
}
createEngine() {
try {
textToSpeech.createEngine(this.initParamsInfo,
(err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
if (!err) {
this.ttsEngine = textToSpeechEngine;
this.ttsEngine.setListener(this.speakListener);
} else {
}
});
} catch (error) {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
}
}
startSpeak(content: string) {
let speakParams: textToSpeech.SpeakParams = {
requestId: Date.now().toString(),
extraParams: this.extraParam
};
this.ttsEngine?.speak(content, speakParams);
}
stopSpeak() {
this.ttsEngine?.stop();
}
shutdownEngine() {
this.ttsEngine?.shutdown();
}
}