在这篇文章中,我们将介绍如何使用 Vue.js 和 SpeechSynthesis API 开发一个基础的有声书编辑器。这个编辑器允许用户在段落中插入语音角色、设置语气(如开心、伤心等),并支持段落试听功能。以下是功能的详细实现步骤。
功能概述
- 段落编辑:支持用户动态添加和编辑段落内容。
- 语音角色选择:通过 SpeechSynthesis API 动态加载系统可用的语音角色。
- 语气设置:为每个段落设置语气(如开心、平静、伤心等)。
- 段落试听:支持段落内容的语音播放,语音参数(语速、音调、音量)根据用户选择的语气动态调整。
- 快捷键交互:通过双击回车键快速添加新段落。
代码解析
基础结构
HTML 部分使用 v-for
遍历渲染段落列表,每个段落包含语音角色、语气选择、可编辑的内容区域,以及试听按钮。
<template>
<div class="book-editor-wrap">
<div v-for="(item, paragraphIndex) in paragraphList" :key="item.id" class="paragraph-wrap">
<div class="paragraph-info">
<!-- 语音角色选择 -->
<a-popover title="添加角色" placement="bottom" overlayClassName="role-list" trigger="click">
<template #content>
<p v-for="roleItem in roleList" :key="roleItem.voiceURI" class="role-item" :class="roleItem.voiceURI === item.roleId && 'active'" @click="changeParmTone(paragraphIndex, roleItem, 'roleId')">
{{ roleItem.voiceURI }}
</p>
</template>
<div class="role-dom">{{ item.roleId.voiceURI || '暂无角色' }}</div>
</a-popover>
<!-- 语气选择 -->
<a-popover title="添加语气" placement="bottom" overlayClassName="tone-list" trigger="click">
<template #content>
<p v-for="toneItem in toneList" :key="toneItem.id" class="tone-item" :class="toneItem.id === item.toneId && 'active'" @click="changeParmTone(paragraphIndex, toneItem, 'toneId')">
{{ toneItem.tone }}
</p>
</template>
<div class="tone-dom">
<img src="https://p5.ssl.qhimg.com/t110b9a93016ab227550b4aa226.png"/>
{{ item.toneId.tone || '心情' }}
</div>
</a-popover>
<!-- 可编辑内容 -->
<div contenteditable @keydown="handleKeyDown" :ref="`editor_${item.id}`" class="editor-dom" v-html="item.content"></div>
</div>
<!-- 段落试听 -->
<div v-if="auditionID === item.id" class="audition-play" @click="onAudition(item, false)">
正在试听...<img src="https://p3.ssl.qhimg.com/t110b9a9301808402d167204131.png"/>
</div>
<div v-else class="audition-play" @click="onAudition(item, true)">
段落试听<img src="https://p4.ssl.qhimg.com/t110b9a9301293dc726c49e7a0f.png"/>
</div>
</div>
</div>
</template>
数据结构设计
段落数据结构
每个段落保存内容、角色 ID 和语气 ID:
const defaultParagraph = {
content: '暂无段落',
roleId: '',
toneId: '',
id: 1
};
语气列表
定义不同语气的参数(语速、音调、音量):
const toneList = [
{ id: 'happy', tone: '开心', rate: 1.5, pitch: 1.8, volume: 1 },
{ id: 'sad', tone: '伤心', rate: 0.8, pitch: 0.8, volume: 0.6 },
{ id: 'angry', tone: '生气', rate: 1.2, pitch: 1.2, volume: 1 },
{ id: 'calm', tone: '平静', rate: 1, pitch: 1, volume: 0.8 },
{ id: 'default', tone: '默认', rate: 1, pitch: 1, volume: 1 }
];
核心方法
加载语音角色
通过 SpeechSynthesis API 获取系统支持的语音角色:
methods: {
getRoleList() {
const loadVoices = () => {
const voices = window.speechSynthesis.getVoices();
if (voices.length > 0) {
this.roleList = voices;
}
};
if ('speechSynthesis' in window) {
window.speechSynthesis.onvoiceschanged = loadVoices;
if (window.speechSynthesis.getVoices().length > 0) {
loadVoices();
}
} else {
console.error('当前浏览器不支持 SpeechSynthesis API。');
}
}
}
试听功能
根据段落内容、角色和语气动态调整语音播放参数:
onAudition(item, isPlay) {
if (isPlay) {
this.auditionID = item.id
} else {
this.auditionID = undefined
return
}
let content = this.$refs[`editor_${item.id}`][0].innerHTML
item.content = content
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance();
utterance.voice = item.roleId || this.roleList[0];
const toneInfo = item.toneId || this.toneList.find(t => t.id === 'default');
utterance.pitch = toneInfo.pitch;
utterance.rate = toneInfo.rate;
utterance.volume = toneInfo.volume;
window.speechSynthesis.speak(utterance);
}
段落快捷添加
通过双击回车键快速添加新段落:
handleKeyDown(event) {
if (event.key === 'Enter') {
this.enterCount++;
clearTimeout(this.timer);
if (this.enterCount === 1) {
this.timer = setTimeout(() => this.enterCount = 0, 300);
} else if (this.enterCount === 2) {
this.enterCount = 0;
this.paragraphList.push({ ...defaultParagraph, id: this.paragraphList.length + 1 });
}
}
}
总结与优化方向
通过本文的实现,我们完成了一个基础的有声书编辑器,它能动态设置段落内容、语气和语音角色,并支持段落试听。未来的优化方向包括:
- 语音播放进度:支持显示播放进度条。
- 批量试听:一次性播放多个段落。
- 多设备兼容性:解决不同浏览器对 SpeechSynthesis API 的支持差异。