引言
在当今的人工智能时代,语音交互成为了一种极具潜力的交互方式。本项目致力于构建一个前端语音交互系统,实现文字到语音的转换。以下将结合代码片段,详细阐述项目中的各个关键部分。
项目准备
敏感信息保护与环境变量配置
在实际开发中,我们通过 .gitignore 文件来控制哪些文件不提交到远程仓库,像 node_modules/ 目录,由于其体积大且可通过 npm i 重新安装,所以无需提交。
而本地环境变量存储在 .env.local 文件中,该文件也被添加到 .gitignore 里,防止敏感信息泄露。在代码中,我们使用 import.meta.env 来获取这些环境变量:
const { VITE_TOKEN, VITE_APP_ID, VITE_CLUSTER_ID } = import.meta.env;
这里的 VITE_TOKEN、VITE_APP_ID 和 VITE_CLUSTER_ID 是调用火山接口所需的关键信息,通过环境变量的方式存储,提高了代码的安全性。
核心功能实现
状态管理
在代码中,我们使用 React 的 useState 钩子来管理组件的状态。
const [prompt, setPrompt] = useState('大家好,我是蔡徐坤');
const [status, setStatus] = useState('ready');
-
prompt状态用于存储用户输入的文字,初始值设置为 “大家好,我是蔡徐坤”。当用户在输入框中输入内容时,通过onChange事件更新prompt的值:
<textarea
className="input"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
status状态用于表示当前的处理状态,初始值为'ready',表示系统就绪。处理过程中,状态会变为'loading'(加载中),处理完成后变为'done'。界面会根据这个状态进行相应的显示,体现了单向数据流的思想,即数据状态驱动界面更新。
生成语音功能
当用户点击 “Generate & Play” 按钮时,会触发 generateAudio 函数。以下是该函数的详细解释:
const generateAudio = () => {
const voiceName = "zh_female_tianmeixiaoyuan_moon_bigtts"; // 语音角色
const endpoint = "/tts/api/v1/tts"; // API地址
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer;${VITE_TOKEN}`
};
// POST请求体
const payload = {
app: {
appid: VITE_APP_ID,
token: VITE_TOKEN,
cluster: VITE_CLUSTER_ID
},
user: {
uid: 'bearbobo'
},
audio: {
voice_type: voiceName,
encoding: 'ogg_opus', // 编码
compression_rate: 1, // 压缩比例
rate: 24000,
speed_ratio: 1.0,
volume_ratio: 1.0,
pitch_ratio: 1.0,
emotion: 'happy' // 情绪
},
request: {
reqid: Math.random().toString(36).substring(7),
text: prompt,
text_type: 'plain',
operation: 'query',
silence_duration: '125',
with_frontend: '1',
frontend_type: 'unitTson',
pure_english_opt: '1',
}
};
setStatus('loading'); // 将状态设置为加载中
fetch(
endpoint,
{
method: 'POST',
headers: headers,
body: JSON.stringify(payload)
}
)
.then(res => res.json())
.then(data => {
const url = createBlobURL(data.data); // 返回一个可以播放声音的URL
audioRef.current.src = url;
audioRef.current.play();
setStatus('done'); // 将状态设置为完成
});
};
- 语音角色和 API 地址:
voiceName指定了语音的角色,endpoint是调用的 API 地址。 - 请求头:设置了请求的内容类型为
application/json,并在Authorization字段中使用之前获取的VITE_TOKEN进行身份验证。 - 请求体:包含了应用信息、用户信息、音频配置和请求信息等。其中,
text字段的值为prompt,即用户输入的文字。 - 状态更新:在发送请求前,将
status状态设置为'loading',表示系统正在处理请求。当请求成功返回并完成语音播放后,将状态设置为'done'。
Base64 编码处理
接口返回的音频数据是 Base64 编码的,我们需要将其转换为可播放的 URL。通过 createBlobURL 函数实现了这一转换:
function createBlobURL(base64AudioData) {
var byteArrays = [];
var byteCharacters = atob(base64AudioData);
for (var offset = 0; offset < byteCharacters.length; offset++) {
var byteArray = byteCharacters.charCodeAt(offset);
byteArrays.push(byteArray);
}
var blob = new Blob([new Uint8Array(byteArrays)], { type: 'audio/mp3' });
return URL.createObjectURL(blob);
}
atob函数用于解码 Base64 字符串,将其转换为二进制数据。- 遍历解码后的字符,将每个字符的 Unicode 编码存储在
byteArrays中。 - 使用
Blob对象将二进制数据封装为音频文件,指定文件类型为'audio/mp3'。 - 最后,使用
URL.createObjectURL方法创建一个可以播放声音的 URL。
界面展示
在最后的返回部分,构建了一个简单的界面:
return (
<div className="container">
<div>
<label>Prompt</label>
<button onClick={generateAudio}>Generate & Play</button>
<textarea
className="input"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
</div>
<div className="out">
<div>{status}</div>
<audio ref={audioRef}/>
</div>
</div>
);
- 输入框:用户可以在输入框中输入要转换为语音的文字,输入框的值与
prompt状态绑定,通过onChange事件更新prompt。 - 按钮:点击 “Generate & Play” 按钮会触发
generateAudio函数,开始调用火山接口生成语音并播放。 - 状态显示区域:显示当前的处理状态,如
ready、loading或done。 - 隐藏的
audio元素:通过audioRef引用该元素,将生成的音频 URL 赋值给其src属性,然后调用play方法播放语音。
总结
通过这个项目,我们成功实现了一个基于前端的语音交互系统,将文字转换为语音。在开发过程中,我们深入了解了敏感信息保护、环境变量配置、状态管理、API 调用和 Base64 编码处理等关键知识点。这些知识不仅适用于本项目,还可以应用到其他前端开发项目中。希望这篇文章能对你有所帮助,让你在 AI 时代的前端开发中迈出坚实的一步👏!