AB
AiBoss站
教程

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 还是电话线路。