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
- Text-to-Speech: Perfectly compatible with mainstream browsers on all platforms.
- Speech-to-Text: Best supported by Chrome and Edge kernel browsers. Some versions of Safari have poor compatibility. Mobile WeChat browsers do not support it.
2. Overall Function Implementation Approach
- Encapsulate the native speech interface to solve browser prefix compatibility issues.
- Implement Text-to-Speech: Input text → Configure parameters → Trigger reading → Support pause/stop.
- Implement Speech-to-Text: Request microphone permission → Listen to speech in real time → Output recognized text.
- Add state management: Status prompts for loading, recognizing, reading, no permission, etc.
- 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
- lang: Reading language, zh-CN for Chinese, en-US for English.
- rate: Reading speed, value 0.1 ~ 10, default 1.
- pitch: Pitch, value 0 ~ 2, default 1. Higher values result in a sharper tone.
- volume: Volume, value 0 ~ 1, default 1.
2. Speech-to-Text Parameters
- lang: Recognition language, supports switching between Chinese and English.
- continuous: Whether to recognize continuously. true for continuous listening, false for single recognition.
- interimResults: Whether to return interim results. When enabled, it can display spoken content in real time without waiting for the end.
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
- Native API, zero dependencies, no need to install third-party packages, lightweight.
- Text-to-Speech executes locally, no API fees, no request latency.
- Extremely low development cost; complete functionality can be integrated in ten minutes.
❌ Disadvantages
- Speech-to-Text depends on the browser kernel; some niche browsers are incompatible.
- Recognition accuracy depends on the browser's official speech service; the model cannot be customized.
- Pure frontend implementation, cannot be deployed privately. High-precision scenarios require integrating third-party AI SDKs.
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:
- Integrate Baidu AI, Alibaba Cloud, iFlytek speech SDKs to improve recognition accuracy.
- Add speech pause, resume, and segmented reading functions.
- Record history of recognition records and save them in local cache.
- Adapt for mobile devices, optimizing touch and click experience.
- 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!