跪拜 Guibai
← Back to the summary

Zero-Cost Voice I/O for Vue Apps Using the Browser's Web Speech API


theme: channing-cyan highlight: a11y-light

In daily development, many backend systems, intelligent customer service, and AI conversation projects use Text-to-Speech (TTS) and Speech-to-Text (STT) functions.

Many developers' first reaction is to introduce a third-party paid SDK. In fact, browsers natively provide a mature Web Speech API, which is zero-cost, requires no backend, and works out of the box, perfectly meeting most business scenarios.

Based on Vue2/Vue3 universal syntax, this article implements bidirectional speech functions step by step, including: core principles, complete code, start/stop control, exception handling, compatibility adaptation, and common pitfalls. You can copy it directly into your project.


1. Core Technology Prerequisites

The Web Speech API is a browser-native speech interface, mainly divided into two modules, corresponding to our two core functions:

1. Text-to-Speech: SpeechSynthesis

Converts text content into spoken audio. It belongs to browser local synthesis, requires no internet connection, no API requests, and has extremely fast response speed.

Supports configuration: reading speed, pitch, volume, voice selection, start/stop/pause/resume operations.

2. Speech-to-Text: SpeechRecognition

Listens to microphone audio in real time and recognizes speech as text in real time. It relies on the browser's speech recognition service and requires an internet connection + HTTPS environment (localhost is exempt during local development).

Supports configuration: recognition language, real-time return of fragments, return of complete text upon ending, monitoring recognition status, etc.

✅ Compatibility Notes


2. Overall Function Implementation Approach

  1. Encapsulate the native speech interface to solve browser prefix compatibility issues.
  2. Implement Text-to-Speech: Input text → Configure parameters → Trigger reading → Support pause/stop.
  3. Implement Speech-to-Text: Request microphone permission → Listen to speech in real time → Output recognized text.
  4. Add state management: Status prompts for loading, recognizing, reading, no permission, etc.
  5. Exception catching: Permission denied, browser not supported, network exception, recognition timeout.

3. Complete Code Implementation (Vue Universal)

The component code does not distinguish between Vue2/Vue3. Only the Composition/Options API writing style can be fine-tuned as needed. The core logic is completely universal.

<template>
  <div class="speech-box">
    <h3>Vue Speech Bidirectional Conversion Function Demo</h3>

    <!-- Text-to-Speech Area -->
    <div class="tts-box">
      <p>【Text-to-Speech】Enter the content to be read aloud</p>
      <textarea v-model="ttsText" placeholder="Please enter the text to read aloud"></textarea>
      <div class="btn-group">
        <button @click="startSpeak" :disabled="speaking">Start Reading</button>
        <button @click="stopSpeak" :disabled="!speaking">Stop Reading</button>
      </div>
    </div>

    <!-- Speech-to-Text Area -->
    <div class="stt-box">
      <p>【Speech-to-Text】Click Start Recognition and speak into the microphone</p>
      <div class="result-text">Recognition Result: {{ sttResult || 'No content yet' }}</div>
      <div class="btn-group">
        <button @click="startRecognition" :disabled="recognitioning">Start Recognition</button>
        <button @click="stopRecognition" :disabled="!recognitioning">End Recognition</button>
        <button @click="clearResult">Clear Result</button>
      </div>
      <p class="tip-text">{{ statusText }}</p>
    </div>
  </div>
</template>

<script>
export default {
  name: 'SpeechTranslate',
  data() {
    return {
      // Text-to-Speech
      ttsText: 'Hello everyone, this is a demo of the Vue native text-to-speech function',
      speaking: false, // Whether currently reading aloud
      speech: null, // Speech instance

      // Speech-to-Text
      sttResult: '', // Recognition result
      recognitioning: false, // Whether currently recognizing
      recognition: null, // Recognition instance
      statusText: 'Waiting for operation...'
    }
  },
  mounted() {
    // Initialize speech instance
    this.initSpeech()
    // Initialize recognition instance
    this.initRecognition()
  },
  beforeDestroy() {
    // Terminate all speech processes when the component is destroyed to prevent memory leaks
    this.stopSpeak()
    this.stopRecognition()
  },
  methods: {
    // ===================== Text-to-Speech Core Methods =====================
    initSpeech() {
      if (!window.speechSynthesis) {
        this.$message && this.$message.error('Current browser does not support text-to-speech')
        return false
      }
      return true
    },
    startSpeak() {
      if (!this.initSpeech() || !this.ttsText.trim()) {
        this.$message && this.$message.warning('Please enter text to read aloud')
        return
      }
      // Stop the previous reading first
      window.speechSynthesis.cancel()

      // Create a reading instance
      this.speech = new SpeechSynthesisUtterance()
      this.speech.text = this.ttsText // Text to read
      this.speech.lang = 'zh-CN' // Read in Chinese
      this.speech.rate = 1 // Speed 0.1-10
      this.speech.pitch = 1 // Pitch 0-2
      this.speech.volume = 1 // Volume 0-1

      // Reading start
      this.speech.onstart = () => {
        this.speaking = true
      }
      // Reading end
      this.speech.onend = () => {
        this.speaking = false
      }
      // Reading error
      this.speech.onerror = () => {
        this.speaking = false
        this.$message && this.$message.error('Speech reading failed')
      }

      // Execute reading
      window.speechSynthesis.speak(this.speech)
    },
    stopSpeak() {
      window.speechSynthesis.cancel()
      this.speaking = false
    },

    // ===================== Speech-to-Text Core Methods =====================
    initRecognition() {
      // Compatible with different browser prefixes
      window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
      if (!window.SpeechRecognition) {
        this.statusText = 'Current browser does not support speech recognition'
        return false
      }
      // Create recognition instance
      this.recognition = new SpeechRecognition()
      this.recognition.lang = 'zh-CN' // Recognize Chinese
      this.recognition.continuous = true // Continuous recognition
      this.recognition.interimResults = true // Return interim recognition results

      // Recognition result callback
      this.recognition.onresult = (e) => {
        let result = ''
        // Iterate through recognition fragments
        for (let i = e.resultIndex; i < e.results.length; i++) {
          result += e.results[i][0].transcript
        }
        this.sttResult = result
      }

      // Recognition start
      this.recognition.onstart = () => {
        this.recognitioning = true
        this.statusText = 'Listening, please speak...'
      }

      // Recognition end
      this.recognition.onend = () => {
        // Keep recognition active continuously
        if (this.recognitioning) {
          this.recognition.start()
        } else {
          this.statusText = 'Recognition has ended'
        }
      }

      // Recognition error
      this.recognition.onerror = (err) => {
        this.recognitioning = false
        this.statusText = `Recognition failed: ${err.error}`
        if (err.error === 'not-allowed') {
          this.$message && this.$message.error('Microphone permission denied, please manually enable permission')
        }
      }
      return true
    },

    startRecognition() {
      if (!this.recognition) {
        if (!this.initRecognition()) return
      }
      this.recognition.start()
    },

    stopRecognition() {
      if (this.recognition) {
        this.recognition.stop()
      }
      this.recognitioning = false
      this.statusText = 'Recognition stopped'
    },

    clearResult() {
      this.sttResult = ''
      this.statusText = 'Waiting for operation...'
    }
  }
}
</script>

<style scoped>
.speech-box {
  width: 90%;
  max-width: 600px;
  margin: 30px auto;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
}
.tts-box, .stt-box {
  margin: 20px 0;
  padding: 20px;
  background: #f8f9fa;
  border-radius: 6px;
}
textarea {
  width: 100%;
  height: 80px;
  padding: 10px;
  margin: 10px 0;
  border: 1px solid #ddd;
  border-radius: 4px;
  resize: none;
}
.btn-group {
  margin-top: 10px;
}
button {
  padding: 6px 16px;
  margin-right: 10px;
  border: none;
  border-radius: 4px;
  background: #409eff;
  color: #fff;
  cursor: pointer;
}
button:disabled {
  background: #99bcf7;
  cursor: not-allowed;
}
.result-text {
  padding: 10px;
  background: #fff;
  border-radius: 4px;
  margin: 10px 0;
  min-height: 20px;
}
.tip-text {
  color: #666;
  font-size: 14px;
  margin: 5px 0 0;
}
</style>

4. Core Parameter Details (Customizable Configuration)

1. Text-to-Speech Parameters

2. Speech-to-Text Parameters


5. Common Project Pitfalls & Solutions

Pitfall 1: Function works locally but fails in the online environment

Reason: The speech recognition function must require the HTTPS protocol. The HTTP plaintext protocol will directly disable the microphone permission.

Solution: Deploy an HTTPS certificate for the online project. Localhost and 127.0.0.1 are browser-exempt addresses and can be used normally.

Pitfall 2: Clicking read aloud multiple times causes overlapping speech

Solution: Execute window.speechSynthesis.cancel() before each reading to terminate the previous speech process and avoid overlap.

Pitfall 3: Cannot trigger again after microphone permission is denied

Solution: Catch the not-allowed error and prompt the user to manually enable the microphone permission in the browser address bar.

Pitfall 4: Speech continues playing in the background after the component is destroyed

Solution: Forcefully terminate reading and recognition in the beforeDestroy lifecycle hook to prevent memory leaks and background execution.

Pitfall 5: Some browsers do not support it

Solution: Perform a compatibility check in advance. Provide a friendly prompt when not supported, without blocking page functionality.


6. Summary of Advantages and Disadvantages

✅ Advantages

❌ Disadvantages


7. Expansion and Upgrade Directions

If the business requires higher precision, private deployment, or offline recognition, you can upgrade based on the foundation of this article:

  1. Integrate Baidu AI, Alibaba Cloud, iFlytek speech SDKs to improve recognition accuracy.
  2. Add speech pause, resume, and segmented reading functions.
  3. Record history of recognition records and save them in local cache.
  4. Adapt for mobile devices, optimizing touch and click experience.
  5. Add multi-language switching and dialect recognition functions.

Finally

The browser's native Web Speech API can fully meet the speech needs of the vast majority of ToB backends, simple AI conversations, and auxiliary input scenarios. Compared to third-party SDKs, it has the absolute advantages of zero cost, lightweight nature, and rapid deployment.

The code in this article works out of the box. Copy it into a Vue project to run it directly without additional configuration. Hope it helps those in need!