
Play.ht
用 Play.ht API 流式合成语音并保存为 MP3
从获取 User ID 与 API Key 开始,用 Play.ht 的 HTTP 流式接口把文本实时转成语音:安装 Node.js 或 Python SDK、调用 /v2/tts/stream、选择 PlayDialog 与 Play3.0 语音引擎、设置输出格式与采样率,并把返回的音频流写入本地文件或转发给浏览器与电话系统。
Play.ht 提供了一套 AI 文本转语音(TTS)接口,核心能力是把一段文字变成听起来自然的语音,并且支持边生成边返回音频流。对于需要低延迟发声的场景——实时对话机器人、语音助手、电话客服、浏览器内朗读——流式接口比「等整段音频生成完再下载」更合适。这篇教程讲的是如何用 Play.ht 的 HTTP 流式接口完成一次完整的语音合成:拿到凭证、发出请求、把返回的音频流落成 MP3 文件,并了解 SDK 与 REST 两种调用方式之间的差别。如果你只是想先看看这个工具在站内的条目,可以访问 Play.ht。
适合阅读这篇教程的读者:已经会用命令行或 Node.js 发 HTTP 请求,准备把 TTS 接进自己产品的开发者;或者想先用 cURL 验证接口是否可用、再决定是否引入 SDK 的工程师。文中所有请求都基于 Play.ht 的 v2 接口,路径为 https://api.play.ht/api/v2/tts/stream。
准备工作
账号与凭证
调用 Play.ht 的 API 需要两样东西:一个 User ID 和一个 API Key。这两项都在 Play.ht 的控制台(dashboard)里生成。生成之后请妥善保存,后续每一个请求都要在请求头里带上它们:
X-USER-ID:填你的 User ID。AUTHORIZATION:填你的 API Key。
注意请求头的写法:授权信息放在名为 AUTHORIZATION 的头里,值直接就是 API Key 本身,素材中的示例没有在它前面加 Bearer 之类的前缀。照示例原样填写即可,不要自行改造格式。
运行环境
如果你打算用 cURL 验证,只需要一个能发 HTTPS 请求的终端。如果你想用 SDK,需要先装好 Node.js 或 Python 环境。SDK 是可选的——不用 SDK 也能通过 REST 接口完成全部工作,SDK 只是把请求封装得更顺手一些。
安装 SDK(可选)
Node.js 侧可以用 npm、pnpm 或 yarn 安装,包名是 playht:
npm install playht
pnpm install playht
yarn add playht
Python 侧用 pip 安装,包名是 pyht:
pip install pyht
三种包管理器任选其一即可,不需要同时安装。Python 的包名和 Node.js 的包名不一样,注意不要写错。
操作步骤
第一步:用 cURL 发出第一个流式请求
最直接的验证方式是用 cURL 直接 POST 到流式接口。下面这条命令把一段英文文本合成语音,并把返回的音频写入 result.mp3:
curl --request POST \
--url https://api.play.ht/api/v2/tts/stream \
--header 'X-USER-ID: <YOUR_USER_ID>' \
--header 'AUTHORIZATION: <YOUR_API_KEY>' \
--header 'accept: audio/mpeg' \
--header 'content-type: application/json' \
--data '
{
"text": "Hello from a realistic voice.",
"voice_engine": "PlayDialog",
"voice": "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json",
"output_format": "mp3"
}
' \
--output result.mp3
把 <YOUR_USER_ID> 和 <YOUR_API_KEY> 换成你自己的凭证。命令里的 --output result.mp3 让 cURL 把响应体直接写进文件,因为响应体就是音频数据本身,不是 JSON。
这条请求里有几个关键点值得拆开看:
- accept 头:
accept: audio/mpeg告诉服务端你要的是 MP3 音频。响应体是二进制音频流,所以不能像调普通 JSON 接口那样直接看返回内容。 - content-type 头:
content-type: application/json,因为请求体是 JSON。 - voice_engine:示例里用的是
PlayDialog,这是 Play.ht 的一个语音引擎。另一个在示例中出现的引擎是Play3.0,两者在同一个接口上通过这个字段切换。 - voice:音色标识,示例中是一个
s3://开头的 manifest 路径,指向某个预置或克隆音色。要换成别的音色,需要拿到对应的 manifest 地址。 - output_format:输出格式,示例里是
mp3。
第二步:用 Node.js SDK 流式合成
如果不想手写 HTTP 请求,可以用官方 SDK。先初始化客户端,再调用 PlayHT.stream:
import * as PlayHT from 'playht';
import fs from 'fs';
// Initialize client
PlayHT.init({
userId: '<YOUR_USER_ID>',
apiKey: '<YOUR_API_KEY>',
});
async function streamAudio(text) {
const stream = await PlayHT.stream('All human wisdom is summed up in these two words: Wait and hope.', {
voiceEngine: 'PlayDialog',
});
stream.on('data', (chunk) => {
// Do whatever you want with the stream, you could save it to a file, stream it in realtime to the browser or app, or to a telephony system
fs.appendFileSync('output.mp3', chunk);
});
return stream;
}
这段代码的结构是:PlayHT.init 传入凭证完成初始化;PlayHT.stream 接收文本和选项对象,返回一个可读流;监听 data 事件,每收到一块音频数据就追加写入 output.mp3。注意 SDK 里的选项字段名是驼峰式的 voiceEngine,而 REST 请求体里是下划线式的 voice_engine,两种写法不要混用。
示例注释里点明了这个流的用途:可以存成文件,也可以实时推给浏览器、App 或电话系统。这正是流式接口相对批量接口的价值所在——音频在生成过程中就陆续到达,不必等整段合成结束。
第三步:用 fetch 手动处理流并落盘
如果不使用 SDK,但又在 Node.js 环境里,可以用 fetch 直接请求接口,然后把响应的可读流接到文件写入流上。这个版本比 SDK 版本多暴露了几个参数:
import fs from "fs";
import fetch from "node-fetch";
const url = "https://api.play.ht/api/v2/tts/stream";
const options = {
method: "POST",
headers: {
accept: "audio/mpeg",
"content-type": "application/json",
"X-USER-ID": "<YOUR_USER_ID>",
AUTHORIZATION: "<YOUR_API_KEY>",
},
body: JSON.stringify({
voice_engine: 'Play3.0',
text: "Hey, this is Jennifer from Play. Please hold on a moment, let me just um pull up your details real quick.",
voice: "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json",
output_format: "mp3",
sample_rate: "44100",
speed: 1,
}),
};
const response = await fetch(url, options);
const readableStream = response.body;
// Pipe the readable stream to a writable stream, this can be a local file or any other writable stream
readableStream.pipe(fs.createWriteStream("./audio.mp3"));
与第一步的 cURL 相比,这个请求体多了两个字段:
- sample_rate:采样率,这里写的是
44100。在另一处 cURL 示例中,同样的字段被设为48000,说明这个值是可配置的,按你的播放端或下游系统要求来定。 - speed:语速,示例值为
1,即正常语速。
另外注意 voice_engine 这里换成了 Play3.0,与前面 SDK 示例的 PlayDialog 不同。同一个接口支持多个引擎,切换引擎只需要改这一个字段。
代码最后一行 readableStream.pipe(fs.createWriteStream("./audio.mp3")) 是流式处理的核心:响应体本身就是一个可读流,直接管道到文件写入流即可,不需要先把整个响应缓冲到内存里。如果目标不是文件而是 HTTP 响应或 WebSocket,把 createWriteStream 换成对应的可写流即可。
一个完整示例
下面把前面的步骤串成一个可以照着跑的最小流程。假设你已经拿到 User ID 和 API Key,并且本机有 Node.js 环境。
第一步,确认凭证可用。 先用 cURL 发一次请求,把音频写到本地文件:
curl --request POST \
--url https://api.play.ht/api/v2/tts/stream \
--header 'X-USER-ID: <YOUR_USER_ID>' \
--header 'AUTHORIZATION: <YOUR_API_KEY>' \
--header 'accept: audio/mpeg' \
--header 'content-type: application/json' \
--output output.mp3 \
--data '
{
"text": "Hey, this is Jennifer from Play. Please hold on a moment, let me just um pull up your details real quick.",
"voice": "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json",
"output_format": "mp3",
"speed": 1,
"sample_rate": 48000,
"voice_engine": "Play3.0"
}
'
执行完检查当前目录下是否出现了 output.mp3,并且文件大小不为零。如果文件为空,优先检查两个请求头是否填对、凭证是否有多余的空格。
第二步,换成代码调用。 新建一个 index.mjs,内容如下:
import fs from "fs";
import fetch from "node-fetch";
const url = "https://api.play.ht/api/v2/tts/stream";
const options = {
method: "POST",
headers: {
accept: "audio/mpeg",
"content-type": "application/json",
"X-USER-ID": "<YOUR_USER_ID>",
AUTHORIZATION: "<YOUR_API_KEY>",
},
body: JSON.stringify({
voice_engine: "Play3.0",
text: "All human wisdom is summed up in these two words: Wait and hope.",
voice: "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json",
output_format: "mp3",
sample_rate: "44100",
speed: 1,
}),
};
const response = await fetch(url, options);
const readableStream = response.body;
readableStream.pipe(fs.createWriteStream("./audio.mp3"));
运行它,然后播放生成的 audio.mp3。到这里,一条从文本到音频文件的完整链路就跑通了。
第三步,把流接到别处。 把最后一行替换成你要的目标即可。素材明确列出了几种典型去向:保存到本地文件、实时推送到浏览器或 App、推送到电话系统。接口返回的是音频缓冲区流,下游怎么消费由你决定。
接口与参数速查
把散落在各个示例里的字段汇总一下,方便对照填写。以下字段均出现在 POST https://api.play.ht/api/v2/tts/stream 的请求体或请求头中:
| 字段 | 位置 | 示例值 | 说明 |
|---|---|---|---|
X-USER-ID | 请求头 | 你的 User ID | 控制台生成 |
AUTHORIZATION | 请求头 | 你的 API Key | 控制台生成 |
accept | 请求头 | audio/mpeg | 声明需要 MP3 音频 |
content-type | 请求头 | application/json | 请求体为 JSON |
text | 请求体 | 任意待合成文本 | 要朗读的内容 |
voice | 请求体 | s3://voice-cloning-zero-shot/.../manifest.json | 音色标识 |
voice_engine | 请求体 | PlayDialog / Play3.0 | 语音引擎 |
output_format | 请求体 | mp3 | 输出音频格式 |
sample_rate | 请求体 | 44100 / 48000 | 采样率 |
speed | 请求体 | 1 | 语速 |
SDK 调用时字段名会转成驼峰形式,例如 voiceEngine。REST 调用时用下划线形式。这是两套写法,不要交叉使用。
相关能力与延伸方向
流式合成只是 Play.ht 接口能力的一部分。素材中提到的其他方向,可以根据你的场景继续深入:
与 LLM 的输入流式配合
接口支持输入流式(input streaming),目的是让它和 ChatGPT 这类大语言模型无缝衔接。典型做法是:模型一边生成文字,TTS 一边把已经生成的片段转成语音,而不是等模型把整段回答写完再开始合成。这样首字延迟会明显降低。素材中有一篇专门讲这个用法的指南。
降低延迟的技巧
如果对延迟敏感,素材里另有一篇专门讨论保持最低延迟的技术要点。这类内容通常涉及请求参数选择、流式消费方式、下游缓冲策略等,建议在跑通基础流程之后再去看。
接入 Twilio 做电话语音
要把实时音频流接到电话通话上,素材提供了与 Twilio 集成的指南。前面代码注释里提到的「推送到电话系统」,对应的就是这类场景。
即时声音克隆
Play.ht 支持即时声音克隆:只需 30 秒的语音样本,就能跨语言克隆一个声音。素材说明可以在 API Playground 里试用,也可以通过 API 完成。如果你需要用自己的音色或特定说话人的音色,这是对应的入口。
批量合成
除了流式接口,API 还提供批量 TTS 能力,包括创建批量任务、查询任务详情、按自定义 ID 或任务 ID 查询子任务等接口。适合不需要实时返回、但要一次处理大量文本的场景。流式接口和批量接口解决的是两类不同问题,选型时按是否需要边生成边消费来判断。
预置音色列表
接口提供查询预置音色的能力。如果你不想自己克隆声音,可以先拉取预置音色列表,从中挑一个 manifest 地址填到 voice 字段里。
WebSocket 接口
除了 HTTP 流式接口,Play.ht 还提供 WebSocket 接口。双向长连接的场景(例如持续对话)可能更适合走 WebSocket,具体用法参见对应文档。
注意事项
- 响应体是音频,不是 JSON。 用 cURL 调试时务必加
--output把结果写到文件,否则终端会刷出乱码。用代码调用时,直接把response.body当可读流处理。 - 两个请求头缺一不可。
X-USER-ID和AUTHORIZATION必须同时提供,且值来自控制台生成的真实凭证。 - SDK 与 REST 的字段命名不同。 SDK 用
voiceEngine,REST 用voice_engine。混用会导致参数不生效。 - voice 字段需要完整的 manifest 路径。 示例中的
s3://地址是某个具体音色的标识,换成别的音色时要替换成对应的 manifest 地址,不能只写音色名称。 - sample_rate 在不同示例中取值不同。 素材里出现过
44100和48000,说明它是可配置项。请按下游播放端或传输系统的要求选择,不要假设存在某个固定默认值。 - 流式写入要注意追加与覆盖的区别。 SDK 示例用的是
fs.appendFileSync,多次调用会往同一个文件后面追加;fetch示例用的是createWriteStream,每次运行会覆盖目标文件。调试时留意这一点,避免误以为音频被截断。 - 速率限制与配额。 素材的文档目录中包含「Rate Limits」条目,说明接口存在调用频率方面的约束。具体数值请以控制台与官网当前信息为准,不要按固定数字做容量规划。
- 价格与可用模型。 各语音引擎的可用性、计费方式与配额会随时间调整,接入前请以官网当前信息为准。
把上面这些串起来,你已经可以用 Play.ht 的流式接口完成一次从文本到音频的合成,并且知道在需要更低延迟、更长连接或批量处理时该往哪个方向继续。下一步通常是确定自己的音色来源(预置还是克隆),以及决定音频流最终交给谁消费——文件、浏览器、App 还是电话线路。