开发工具/音视频/基于 WebSocket 实现音频通话/集成语音合成 SDK
集成语音合成 SDK
更新于: 2026-06-25 19:29:14
语音合成 SDK(简称 WsSpeech SDK)基于双向流式语音合成 WebSocket OpenAPI 封装,提供了完整且高效的语音交互解决方案。它具备实时语音合成、文本播放、灵活的播放控制和丰富的音色选择等功能,适用于智能对话系统、语音导航和有声内容生成等多种应用场景,帮助开发者快速实现高质量的语音交互功能。更多接口详情请参考双向流式语音合成。
扣子提供语音合成 Demo 和 TypeScript 格式的语音合成示例源码,帮助你快速体验语音合成的功能,并根据示例源码快速实现语音合成。
Demo 的主要功能包括:
配置参数。
单击右上角的 Settings,配置个人访问令牌和音色 ID,具体如下表所示。
在文本框中输入待合成的文本,单击整句播放或流式播放。
以下是基于 React 和 Antd 框架开发的语音合成示例代码,具备文本输入、播放控制和状态显示等核心功能,能帮助你快速了解语音合成的实现流程。
使用前需配置个人访问令牌(ACCESS_TOKEN),并安装@coze/api、antd、react等依赖,建议开发时开启调试模式。
import React, { useEffect, useRef, useState } from 'react';
import {
Button,
Layout,
Space,
Typography,
message,
Row,
Col,
Card,
Input,
} from 'antd';
import { WsSpeechClient } from '@coze/api/ws-tools';
import {
SoundOutlined,
PauseOutlined,
PlayCircleOutlined,
StopOutlined,
} from '@ant-design/icons';
const { Title, Paragraph, Text } = Typography;
const { TextArea } = Input;
// 个人访问令牌
const ACCESS_TOKEN ='pat_Qm47PKJR5dvMOP53v6DyzwCbTtvEZHQc2TVINEveg9v1T3iSYlTdScJ8***';
// 音色ID
const VOICE_ID = ''; // 可选
const SpeechDemo: React.FC = () => {
const clientRef = useRef<WsSpeechClient>();
const [transcriptionText, setTranscriptionText] =
useState('你好,这是一个文本转语音测试。');
const [isPlaying, setIsPlaying] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const [disabled, setDisabled] = useState(false);
const [hasToken, setHasToken] = useState<boolean>(false);
const [status, setStatus] = useState<string>('未开始');
// 检查令牌
useEffect(() => {
const checkRequirements = async () => {
// 检查是否配置了PAT令牌
const hasConfiguredToken = !!ACCESS_TOKEN;
setHasToken(hasConfiguredToken);
};
checkRequirements();
}, []);
// 初始化客户端
const initClient = () => {
if (!ACCESS_TOKEN) {
throw new Error('请先配置个人访问令牌');
}
const client = new WsSpeechClient({
token: ACCESS_TOKEN,
allowPersonalAccessTokenInBrowser: true,
debug: false,
});
// 语音合成完成事件(含中断)
client.on('completed', () => {
setIsPlaying(false);
setIsPaused(false);
setStatus('已完成');
console.log('语音合成完成');
});
// 注册所有事件
client.on('data', event => {
console.log('收到事件', event);
});
clientRef.current = client;
};
// 整句播放
const handleAppendAndComplete = async () => {
try {
setDisabled(true); // 操作过程中禁用按钮,防止重复点击
if (!clientRef.current) {
initClient();
}
await clientRef.current?.connect({
voiceId: VOICE_ID, // 可以从配置中获取音色ID
});
setIsPlaying(true);
setIsPaused(false);
setStatus('播放中');
// 开始语音合成
clientRef.current?.appendAndComplete(transcriptionText);
} catch (error) {
message.error(`操作失败:${error}`);
console.error(error);
setStatus('发生错误');
} finally {
setDisabled(false);
}
};
// 流式播放
const handleAppend = async () => {
try {
setDisabled(true);
if (!clientRef.current) {
initClient();
}
await clientRef.current?.connect({
voiceId: VOICE_ID,
});
setIsPlaying(true);
setIsPaused(false);
setStatus('流式播放中');
// 逐字符发送文本
for (let i = 0; i < transcriptionText.length; i++) {
clientRef.current?.append(transcriptionText[i]);
await new Promise(resolve => setTimeout(resolve, 100));
}
// 完成文本输入
clientRef.current?.complete();
} catch (error) {
message.error(`操作失败:${error}`);
console.error(error);
setStatus('发生错误');
} finally {
setDisabled(false);
}
};
// 中断播放
const handleInterrupt = async () => {
try {
await clientRef.current?.interrupt();
setIsPlaying(false);
setIsPaused(false);
setStatus('已中断');
} catch (error) {
message.error(`中断失败:${error}`);
}
};
// 暂停播放
const handlePause = async () => {
try {
await clientRef.current?.pause();
setIsPaused(true);
setStatus('已暂停');
} catch (error) {
message.error(`暂停失败:${error}`);
}
};
// 恢复播放
const handleResume = async () => {
try {
await clientRef.current?.resume();
setIsPaused(false);
setStatus('播放中');
} catch (error) {
message.error(`恢复失败:${error}`);
}
};
// 设置变更处理
const handleSettingsChange = () => {
window.location.reload();
};
// 组件卸载时清理资源
useEffect(() => {
return () => {
clientRef.current?.disconnect();
};
}, []);
return (
<Layout style={{ height: '100%' }}>
<Layout.Content style={{ background: '#fff', padding: '20px' }}>
<Title level={2}>语音合成 (TTS) 演示</Title>
{/* 前置条件检查 */}
<Card title="前置条件检查" style={{ marginBottom: '20px' }}>
<Row gutter={[0, 16]}>
<Col span={24}>
<Space>
<Text strong>个人访问令牌 (PAT):</Text>
{hasToken ? (
<Text type="success">已配置</Text>
) : (
<Text type="danger">未配置 - 请在右上角 Settings 中设置</Text>
)}
</Space>
</Col>
<Col span={24}>
<Space>
<Text strong>音色配置:</Text>
{VOICE_ID ? (
<Text type="success">已配置 (ID: {VOICE_ID})</Text>
) : (
<Text type="warning">未配置 - 将使用默认音色</Text>
)}
</Space>
</Col>
</Row>
</Card>
{/* 文本输入 */}
<Card title="文本输入" style={{ marginBottom: '20px' }}>
<TextArea
rows={4}
value={transcriptionText}
onChange={e => setTranscriptionText(e.target.value)}
placeholder="请输入要转换为语音的文本"
style={{ marginBottom: '16px' }}
/>
</Card>
{/* 播放控制 */}
<Card title="播放控制" style={{ marginBottom: '20px' }}>
<Row justify="start" style={{ marginBottom: '16px' }}>
<Col>
<Text>
当前状态: <Text strong>{status}</Text>
</Text>
</Col>
</Row>
<Row gutter={16}>
<Col>
<Button
type="primary"
icon={<SoundOutlined />}
disabled={disabled || !hasToken || (isPlaying && !isPaused)}
onClick={handleAppendAndComplete}
size="large"
>
整句播放
</Button>
</Col>
<Col>
<Button
type="primary"
icon={<SoundOutlined />}
disabled={disabled || !hasToken || (isPlaying && !isPaused)}
onClick={handleAppend}
size="large"
>
流式播放
</Button>
</Col>
{isPlaying && (
<>
<Col>
<Button
icon={<StopOutlined />}
onClick={handleInterrupt}
size="large"
disabled={!isPlaying}
>
中断
</Button>
</Col>
<Col>
<Button
icon={isPaused ? <PlayCircleOutlined /> : <PauseOutlined />}
onClick={isPaused ? handleResume : handlePause}
size="large"
disabled={!isPlaying}
>
{isPaused ? '恢复' : '暂停'}
</Button>
</Col>
</>
)}
</Row>
</Card>
{/* 使用说明 */}
<Card title="使用说明" style={{ marginTop: '20px' }}>
<Paragraph>
<ol>
<li>在代码中配置个人访问令牌 (PAT)</li>
<li>可选:在代码中配置音色 ID</li>
<li>在"文本输入"区域输入要转换为语音的文本</li>
<li>
选择播放方式:
<ul>
<li>
<strong>整句播放</strong>:一次性将全部文本转换为语音
</li>
<li>
<strong>流式播放</strong>:逐字符转换,模拟实时生成效果
</li>
</ul>
</li>
<li>播放过程中可以使用"暂停"、"恢复"和"中断"按钮控制播放</li>
</ol>
</Paragraph>
</Card>
</Layout.Content>
</Layout>
);
};
export default SpeechDemo;
运行以下命令安装 WsSpeech SDK 及其依赖项。
npm install @coze/api
在项目中导入 WsSpeechClient 类。
import { WsSpeechClient } from '@coze/api/ws-tools';
创建 WsSpeechClient 实例,并配置 token 等参数以初始化客户端。
// 创建 WsSpeechClient 实例
const client = new WsSpeechClient({
token: 'pat_Qm47PKJR5dvMOP53v6DyzwCbTtvEZHQc2TVINEveg9v1T3iSYlTdScJ8***', // 替换为你的个人访问令牌
allowPersonalAccessTokenInBrowser: true, // 在浏览器环境中必需
debug: true, // 可选,启用调试日志
});
参数说明:
说明
扣子 SDK 封装了多种鉴权方式,能够有效简化鉴权流程,你可以参考鉴权示例代码实现不同方式的 OAuth 认证,以获取和管理访问扣子 API 所需的令牌
在初始化客户端后,通过 client.on 方法注册各种事件监听器。详细的事件说明请参见双向流式语音合成事件。
// 注册事件监听器
client.on('completed', () => {
console.log('语音合成完成');
// 处理完成事件(例如,启用按钮,更新 UI)
});
// 可选:记录所有事件用于调试
client.on('data', (event) => {
console.log('收到事件:', event);
});
调用 client.connect 方法建立客户端和服务端之间的连接,并配置音色。
扣子提供一系列系统音色,你可以在系统音色列表查看音色 ID,或通过查看音色列表 API 获取可用的音色列表。如果不指定 voiceId 或值为空,将使用默认的柔美女友音色,音色 ID 为 7426720361733046281。
// 可以选择指定音色 ID
await client.connect({ voiceId: '7426720361733046281' });
WsSpeech SDK 提供了两种语音的播放模式:
两种模式的示例代码如下:
整句播放
// 方法 1:一次性播放整段文本
client.appendAndComplete('你好,这是一个文本转语音测试。');
流式播放(逐字符)
// 方法 2:逐字符流式播放文本
const text = '你好,这是一个流式文本转语音测试。';
// 首先建立连接
await client.connect();
// 逐字符发送文本
for (let i = 0; i < text.length; i++) {
client.append(text[i]);
// 字符之间添加延迟(可选)
await new Promise(resolve => setTimeout(resolve, 100));
}
// 通知文本输入完成
client.complete();
在播放过程中,你可以中断播放、暂停播放、恢复播放。
// 立即中断播放
await client.interrupt();
// 暂停播放
await client.pause();
// 恢复播放
await client.resume();
// 切换播放/暂停
await client.togglePlay();
// 检查是否正在播放
const isPlaying = client.isPlaying();
说明
isPlaying 状态更新用户界面,为用户提供更直观的反馈。在组件卸载或不再需要语音合成功能时,调用 disconnect 方法断开连接,清理资源,以避免潜在的内存泄漏或其他资源占用问题。
// 完成后断开连接以清理资源
await client.disconnect();