在 airi 项目中用 VueUse useSpeechSynthesis 实现响应式语音朗读
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
useSpeechSynthesis是 VueUse 对 Web Speech Synthesis API(SpeechSynthesis)的响应式封装,属于 Sensors 分类下的组合式函数。它把浏览器朗读能力抽象为isSupported、isPlaying、status、utterance等响应式状态,并暴露speak()、toggle()、stop()等控制方法,让你在 Vue 3 / Nuxt 3 组件里用几行代码即可完成"文本转语音"的完整交互。阅读本文后,你将掌握该组合式函数的所有参数、返回值与类型约束,并能直接把它接入 airi 仓库中的任意 Vue 应用(如 apps/stage-web、apps/stage-tamagotchi)实现 TTS 朗读能力。
本文主体来源于仓库内文档 .agents/skills/vueuse-functions/references/useSpeechSynthesis.md,并辅以 .agents/skills/vueuse-functions/SKILL.md 中关于函数选用规则(Invocation 规则)的说明展开。
为什么需要响应式封装
Web Speech Synthesis API 本身是命令式的:你需要手动创建SpeechSynthesisUtterance实例、监听start/end/pause/resume/boundary/error等一系列事件,再手动维护"当前是否在朗读"、"当前状态"等标志位,极易产生样板代码与状态不同步问题。
useSpeechSynthesis将这些细节全部收敛:
- 自动检测
window.speechSynthesis是否存在(SSR 场景下同样安全),通过isSupported暴露支持情况; - 内部创建并持有
SpeechSynthesisUtterance,通过utterance以ComputedRef形式暴露; - 将朗读状态收敛为
status("init" | "play" | "pause" | "end")与isPlaying两个响应式信号; - 提供
speak()、toggle()、stop()三个控制函数,组件内无需再与底层 API 直接打交道。
在 .agents/skills/vueuse-functions/SKILL.md 中,useSpeechSynthesis被归入 Sensors 类别,Invocation 规则为AUTO——即"在适用场景下应自动优先使用",而非要求显式请求或额外安装依赖。这与该 skill 的核心主张一致:优先用 VueUse 组合式函数替代手写逻辑,以保持代码简洁、可维护且性能良好。
基础用法
从@vueuse/core引入并在组件中调用:
import { useSpeechSynthesis } from '@vueuse/core' const { isSupported, isPlaying, status, voiceInfo, utterance, error, stop, toggle, speak, } = useSpeechSynthesis()调用后即可直接使用这些返回值。一个最小可用的 Vue 组件示例如下:
<script setup lang="ts"> import { ref } from 'vue' import { useSpeechSynthesis } from '@vueuse/core' const text = ref('你好,我是 A.I.R.I,很高兴认识你。') const { isSupported, isPlaying, status, speak, stop, toggle } = useSpeechSynthesis(text) </script> <template> <div v-if="isSupported"> <textarea v-model="text" /> <button @click="speak()">朗读</button> <button @click="toggle()">{{ isPlaying ? '暂停' : '继续' }}</button> <button @click="stop()">停止</button> <span>当前状态:{{ status }}</span> </div> <p v-else>当前环境不支持 Speech Synthesis API</p> </template>注意useSpeechSynthesis的第一个参数是文本内容,类型为MaybeRefOrGetter<string>,即可以直接传字符串、ref或 getter 函数。传入ref时,朗读文本会随响应式数据自动更新,这正是"响应式 SpeechSynthesis"的含义。
Options:语音参数与默认值
useSpeechSynthesis(text, options)的第二个参数UseSpeechSynthesisOptions直接透传给底层的SpeechSynthesisUtterance,默认值如下:
import { useSpeechSynthesis } from '@vueuse/core' useSpeechSynthesis('朗读内容', { lang: 'en-US', // 语言,默认 en-US pitch: 1, // 音高,默认 1 rate: 1, // 语速,默认 1 volume: 1, // 音量,默认 1 })各选项的作用与取值范围说明如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
lang | MaybeRefOrGetter<string> | 'en-US' | 朗读使用的语言(BCP 47 语言标签,如'zh-CN'、'ja-JP')。应与所选voice匹配,否则可能使用系统默认语音 |
pitch | MaybeRefOrGetter<SpeechSynthesisUtterance["pitch"]> | 1 | 音高,数值范围 0~2(典型取值 0~2,越接近 2 声音越尖) |
rate | MaybeRefOrGetter<SpeechSynthesisUtterance["rate"]> | 1 | 语速,数值范围 0.1~10(1 为正常语速) |
voice | MaybeRef<SpeechSynthesisVoice> | 无默认值 | 指定使用的语音对象,可通过speechSynthesis.getVoices()获取候选列表,不传则由系统选择 |
volume | MaybeRefOrGetter<SpeechSynthesisUtterance["volume"]> | 1 | 音量,数值范围 0~1 |
onBoundary | (event: SpeechSynthesisEvent) => void | 无 | 触发boundary事件时的回调,可用于实现逐词高亮、字幕同步等效果 |
关键点:
lang、pitch、rate、volume均接受MaybeRefOrGetter,意味着这些参数可以绑定到响应式数据上,用户在界面上拖动滑块调节语速、音高时,下一次speak()自动使用新值;voice只接受MaybeRef(ref 或普通值),因为语音对象是浏览器提供的实例,不适合用 getter 动态推导;- 由于
SpeechSynthesisUtterance的属性(如pitch、rate)在不同浏览器上校验严格程度不同,超出范围的值可能被钳制或忽略,建议在 UI 层做好范围约束。
返回值详解
useSpeechSynthesis返回UseSpeechSynthesisReturn,字段说明如下:
| 返回值 | 类型 | 说明 |
|---|---|---|
isSupported | boolean(来自Supportable) | 当前环境是否支持 Speech Synthesis API,用于条件渲染或功能降级 |
isPlaying | ShallowRef<boolean> | 当前是否处于播放状态 |
status | ShallowRef<UseSpeechSynthesisStatus> | 朗读状态机:"init" \| "play" \| "pause" \| "end" |
voiceInfo | 语音信息 | 当前使用的语音信息(用于展示或调试) |
utterance | ComputedRef<SpeechSynthesisUtterance> | 当前朗读的SpeechSynthesisUtterance实例,文本或参数变化时会重新生成 |
error | ShallowRef<SpeechSynthesisErrorEvent \| undefined> | 最近一次朗读错误事件,无错误时为undefined |
stop | () => void | 立即停止朗读 |
toggle | (value?: boolean) => void | 切换播放 / 暂停;传入value时可强制指定目标状态 |
speak | () => void | 使用当前文本与参数开始朗读 |
典型的使用模式:
const { isPlaying, status, error, stop, toggle, speak } = useSpeechSynthesis(text) // 在朗读结束时做清理或触发下一步 watch(status, (s) => { if (s === 'end') console.log('朗读结束') }) // 朗读出错时回退到文本提示 watch(error, (e) => { if (e) console.error('语音朗读失败', e) })toggle()不带参数时自动切换播放与暂停:播放中调用则暂停,暂停中调用则恢复。这在"语音播放/暂停"按钮的实现中尤其好用,无需手动判断当前状态。
类型声明:一次看懂完整接口
文档给出了完整的 TypeScript 类型声明,它是理解该函数能力边界的最佳入口:
export type UseSpeechSynthesisStatus = "init" | "play" | "pause" | "end" export interface UseSpeechSynthesisOptions extends ConfigurableWindow { lang?: MaybeRefOrGetter<string> pitch?: MaybeRefOrGetter<SpeechSynthesisUtterance["pitch"]> rate?: MaybeRefOrGetter<SpeechSynthesisUtterance["rate"]> voice?: MaybeRef<SpeechSynthesisVoice> volume?: MaybeRefOrGetter<SpeechSynthesisUtterance["volume"]> onBoundary?: (event: SpeechSynthesisEvent) => void } export interface UseSpeechSynthesisReturn extends Supportable { isPlaying: ShallowRef<boolean> status: ShallowRef<UseSpeechSynthesisStatus> utterance: ComputedRef<SpeechSynthesisUtterance> error: ShallowRef<SpeechSynthesisErrorEvent | undefined> stop: () => void toggle: (value?: boolean) => void speak: () => void } export declare function useSpeechSynthesis( text: MaybeRefOrGetter<string>, options?: UseSpeechSynthesisOptions, ): UseSpeechSynthesisReturn值得注意的类型细节:
UseSpeechSynthesisStatus是一个四态字符串联合类型,"init"表示尚未开始,"play"/"pause"/"end"对应朗读的三种终态与中间态,用status === 'end'即可可靠判断一轮朗读是否结束;UseSpeechSynthesisOptions extends ConfigurableWindow:与 VueUse 中大多数浏览器相关组合式函数一致,允许通过window选项传入自定义窗口对象(主要用于测试或特殊宿主环境,如 Electron 渲染进程);- 返回值
extends Supportable:保证isSupported字段的存在,且isSupported在 SSR 环境下为false,避免服务端渲染时误调用浏览器 API; utterance是ComputedRef,当text或lang/pitch/rate/volume等响应式参数变化时,内部会重新构建SpeechSynthesisUtterance实例,保证每次speak()都使用最新配置。
在 airi 仓库中的接入实践
airi 仓库是一个多应用、多包的大型 Vue 3 工程,多个应用与包都声明了对@vueuse/core的依赖,包括 apps/stage-web/package.json、apps/stage-tamagotchi/package.json、apps/stage-pocket/package.json、packages/stage-ui/package.json、packages/stage-shared/package.json 等,因此useSpeechSynthesis可直接在这些工程中开箱即用,无需额外安装依赖(这与 SKILL.md 中该函数的AUTO调用规则一致)。
一个贴近实际场景的完整示例——带语速 / 音量控制与状态展示的朗读面板:
<script setup lang="ts"> import { ref } from 'vue' import { useSpeechSynthesis } from '@vueuse/core' const message = ref('A.I.R.I 正在为你朗读这条消息。') const rate = ref(1) const volume = ref(1) const { isSupported, isPlaying, status, error, speak, toggle, stop } = useSpeechSynthesis(message, { lang: 'zh-CN', rate, volume, }) </script> <template> <section v-if="isSupported"> <textarea v-model="message" rows="2" /> <label>语速 <input v-model.number="rate" type="range" min="0.5" max="2" step="0.1" /></label> <label>音量 <input v-model.number="volume" type="range" min="0" max="1" step="0.1" /></label> <button @click="speak()">开始朗读</button> <button @click="toggle()">{{ isPlaying ? '暂停' : '继续' }}</button> <button @click="stop()">停止</button> <p>状态:{{ status }}<span v-if="error">(错误:{{ error.error }})</span></p> </section> </template>注意事项与限制
- 浏览器支持差异:Speech Synthesis API 的支持程度与语音质量因浏览器、操作系统而异(可通过
isSupported检测基本可用性)。部分浏览器要求页面交互后才能触发朗读,首次调用speak()前建议先由用户手势触发。在 Chromium 系浏览器上,还存在已知的长时间朗读后静默停止的问题,可监听status变为'end'但实际未播完的情况做兜底。 - SSR 安全:由于继承
Supportable且基于window能力检测,isSupported在服务端渲染时为false,配合v-if="isSupported"可以避免在 SSR 阶段调用浏览器 API。 - 语音与语言匹配:
lang只声明语言,实际使用哪个语音由系统决定;若需要精确控制,请通过speechSynthesis.getVoices()获取SpeechSynthesisVoice列表,用voice选项显式指定。 - 范围校验:
pitch(0~2)、rate(0.1~10)、volume(0~1)超出浏览器合法范围时行为不确定,建议在 UI 上限制滑块范围。 - 语音切换异步性:
getVoices()在部分浏览器上首次调用可能返回空数组,需要在voiceschanged事件后再读取,接入时注意时序。
小结
useSpeechSynthesis以最小的 API 面覆盖了 Web Speech Synthesis 的核心能力:isSupported做能力检测、status/isPlaying做状态追踪、speak()/toggle()/stop()做播放控制,配合lang/pitch/rate/volume等响应式参数,可以在 Vue 3 应用中快速构建出健壮的 TTS 朗读功能。对于 airi 这类大量使用 Vue 3 与@vueuse/core的工程,它正是 SKILL.md 所倡导的"用组合式函数替代手写逻辑"的典型实践。若需要完整的类型约束与参数语义,可直接查阅参考文档 useSpeechSynthesis.md。
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考