How to Use the TTS Audio Playback Component in TypeWords for Word Pronunciation
TypeWords provides a built-in useTTsPlayAudio hook that converts any text to speech using the browser's native speechSynthesis API, with automatic voice selection, configurable rate/volume/pitch, and seamless fallback when pronunciation files are unavailable.
The TypeWords typing application includes a robust Text-to-Speech (TTS) system for word and sentence pronunciation. This article explains how to integrate the TTS audio playback component into your own Vue components, based on the actual implementation in the zyronon/TypeWords open-source repository.
Architecture of the TTS System
The TTS implementation spans four layers that work together to provide a seamless pronunciation experience.
| Layer | Purpose | Key Source File |
|---|---|---|
| TTS Hook | Exposes play(text, options) function using speechSynthesis |
app/core/hooks/sound.ts (line 55) |
| Setting Store | Persists user voice preferences per browser/device | app/core/stores/setting.ts |
| Settings UI | Voice selector with preview functionality | app/components/setting/SoundSetting.vue (line 32) |
| Audio Fallback | TTS activates when word-audio API calls fail | app/core/hooks/sound.ts (line 22) |
The hook uses getBrowserKey() to generate stable identifiers like mac+chrome or win+edge, ensuring voice preferences survive across sessions on the same device.
The useTTsPlayAudio Hook Explained
Located at [app/core/hooks/sound.ts](https://github.com/zyronon/TypeWords/blob/master/app/core/hooks/sound.ts#L55), the hook returns a play function with this signature:
function play(text: string, options?: {
rate?: number // 0.1 to 10, defaults to store speed
volume?: number // 0 to 1, defaults to store volume/100
pitch?: number // 0 to 2, defaults to 1
lang?: string // defaults to 'en-US'
onEnd?: () => void // callback when speech finishes
}): void
The implementation handles several critical concerns:
- Race condition prevention: A
generationcounter cancels stale requests when new ones arrive - Voice selection hierarchy: User-saved voice → best-match English voice → first available English voice
- Graceful degradation: Returns early if
speechSynthesisis undefined (server-side rendering or unsupported browsers)
// Core excerpt from sound.ts lines 55-95
export function useTTsPlayAudio() {
const settingStore = useSettingStore()
function play(text: string, options: TTsPlayOptions = {}) {
if (!text || typeof speechSynthesis === 'undefined') return
const generation = ++ttsPlaybackGeneration
speechSynthesis.cancel()
const msg = new SpeechSynthesisUtterance(text)
msg.rate = options.rate ?? settingStore.wordSoundSpeed
msg.volume = options.volume ?? settingStore.wordSoundVolume / 100
msg.pitch = options.pitch ?? 1
msg.lang = options.lang ?? 'en-US'
getVoicesAsync().then(voices => {
if (generation !== ttsPlaybackGeneration) return
const saved = settingStore?.ttsVoiceMap?.find(
v => v.key === getBrowserKey()
)?.voice
// Voice selection logic...
speechSynthesis.speak(msg)
})
}
return play
}
Basic Usage in Vue Components
Follow these three steps to add TTS audio playback to any TypeWords component.
Step 1: Import the Hook
import { useTTsPlayAudio } from '@/core/hooks/sound.ts'
Step 2: Initialize the Player
Inside your component's <script setup>:
const ttsPlay = useTTsPlayAudio()
Step 3: Call with Text and Options
// Minimal call with store defaults
ttsPlay('pronunciation')
// Full options for customized playback
ttsPlay('example sentence', {
rate: 1.2, // 20% faster than normal
volume: 0.8, // 80% of configured volume
pitch: 0.9, // Slightly deeper
onEnd: () => {
console.log('Playback complete')
// Trigger next word, update UI, etc.
}
})
Complete Working Example
Here's a minimal component that demonstrates word pronunciation with user input:
<script setup lang="ts">
import { ref } from 'vue'
import { useTTsPlayAudio } from '@/core/hooks/sound.ts'
const word = ref('accessibility')
const ttsPlay = useTTsPlayAudio()
function pronounce() {
ttsPlay(word.value, {
rate: settingStore.wordSoundSpeed,
volume: 0.9,
onEnd: () => console.log(`Spoke: ${word.value}`)
})
}
</script>
<template>
<div class="flex gap-2">
<input
v-model="word"
@keyup.enter="pronounce"
class="border rounded px-3 py-1"
/>
<button
@click="pronounce"
class="bg-blue-600 text-white px-4 py-1 rounded"
>
🔊 Speak
</button>
</div>
</template>
TTS as Fallback for Failed Word Audio
TypeWords prioritizes high-quality pronunciation files from its API. When those fail, the system automatically falls back to TTS without requiring additional code in your component.
In app/core/hooks/sound.ts (line 22), the usePlayWordAudio function implements this behavior:
wordAudio.onerror = () => {
if (generation !== wordPlaybackGeneration) return
const ttsPlay = useTTsPlayAudio()
ttsPlay(word, {
rate: playbackRate,
onEnd: onended
})
}
This pattern appears in app/core/composables/useWordPracticeAudio.ts, where practice sessions remain uninterrupted even when network pronunciation files are unavailable.
Integrating with the Settings UI
The TTS system reads user preferences from settingStore.ttsVoiceMap, which app/components/setting/SoundSetting.vue populates. When building settings interfaces, mirror this pattern for voice preview:
// From SoundSetting.vue implementation
function previewTtsVoice(voiceName: string) {
if (typeof speechSynthesis === 'undefined') return
speechSynthesis.cancel()
const msg = new SpeechSynthesisUtterance('How are you? I am fine.')
msg.lang = 'en-US'
msg.volume = settingStore.sentenceSoundVolume / 100
msg.rate = settingStore.sentenceSoundSpeed
const voice = ttsVoiceList.value.find(v => v.name === voiceName)
if (voice) msg.voice = voice
speechSynthesis.speak(msg)
}
Detecting Missing Voice Configuration
Production components should warn users when no TTS voice is configured, as implemented in app/components/word/WordMetaPanel.vue:
import { getBrowserKey } from '@/core/hooks/sound.ts'
import { Toast } from '@/base'
function playSentence(sentence: string) {
const hasVoice = settingStore.ttsVoiceMap?.some(
v => v.key === getBrowserKey() && v.voice
)
if (!hasVoice) {
Toast.warning(
'No TTS voice set — open Settings → Sound → TTS Voice to choose one'
)
}
ttsPlay(sentence, {
rate: settingStore.sentenceSoundSpeed,
volume: settingStore.sentenceSoundVolume / 100
})
}
Key Source Files Reference
| File | Lines | Responsibility |
|---|---|---|
app/core/hooks/sound.ts |
1-120 | useTTsPlayAudio, usePlayWordAudio, getBrowserKey, voice utilities |
app/components/setting/SoundSetting.vue |
32-85 | Voice selector UI, preview, preference storage |
app/components/word/WordMetaPanel.vue |
45-80 | Sentence playback with missing-voice detection |
app/core/stores/setting.ts |
20-50 | ttsVoiceMap, wordSoundSpeed, wordSoundVolume |
app/core/composables/useWordPracticeAudio.ts |
30-60 | TTS fallback integration |
Summary
useTTsPlayAudioreturns aplay(text, options)function for browser-native speech synthesis- Import from
app/core/hooks/sound.tsand call within any Vue component - Options include
rate,volume,pitch,lang, andonEndcallback - Voice selection prefers user-configured voices, falls back to English defaults
- The hook automatically cancels overlapping requests using a generation counter
- TTS activates automatically when
usePlayWordAudiocannot fetch pronunciation files - Warn users about missing voice configuration by checking
settingStore.ttsVoiceMapagainstgetBrowserKey()
Frequently Asked Questions
How does TypeWords handle browsers without speech synthesis support?
The useTTsPlayAudio hook checks typeof speechSynthesis !== 'undefined' at the start of every play() call and returns silently if the API is unavailable. This prevents runtime errors during server-side rendering or on unsupported browsers like certain mobile WebViews.
Can I use different TTS voices for words versus sentences?
Yes. The setting store maintains separate speed and volume fields (wordSoundSpeed/sentenceSoundSpeed, wordSoundVolume/sentenceSoundVolume). Pass these explicitly in the options object, or modify the hook to accept a context parameter that selects appropriate defaults automatically.
Why does my TTS voice preference disappear on different devices?
Voice preferences are keyed by getBrowserKey(), which combines platform and browser identifiers (e.g., mac+safari, win+chrome). Since available voices vary by operating system and browser, TypeWords stores separate preferences per environment rather than attempting to synchronize incompatible voice selections.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →