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 generation counter 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 speechSynthesis is 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

  • useTTsPlayAudio returns a play(text, options) function for browser-native speech synthesis
  • Import from app/core/hooks/sound.ts and call within any Vue component
  • Options include rate, volume, pitch, lang, and onEnd callback
  • 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 usePlayWordAudio cannot fetch pronunciation files
  • Warn users about missing voice configuration by checking settingStore.ttsVoiceMap against getBrowserKey()

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →