JavaScript SDK

Scribe:在 JavaScript 中进行实时语音转文本

如需了解 Scribe 及其功能概览,请参阅语音转文本 概览。如需分步使用指南,请参阅客户端 流式传输。

安装

npm install @elevenlabs/client
# or
yarn add @elevenlabs/client
# or
pnpm install @elevenlabs/client

使用 ElevenLabs 语音转文本技能,通过 AI 编程助手转写音频:

npx skills add elevenlabs/skills --skill speech-to-text

此库可用于任何基于 JavaScript 的项目。如果使用 React,建议使用 useScribe hook,它提供 内置状态管理和生命周期处理。

使用方法

以下是连接 Scribe 并记录转写结果的最小可运行示例:

import { Scribe, RealtimeEvents } from "@elevenlabs/client";
const token = await fetchTokenFromServer();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
microphone: {
echoCancellation: true,
noiseSuppression: true,
},
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
console.log("Partial:", data.text);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Committed:", data.text);
});
// Later, close the connection
connection.close();

获取令牌

Scribe 需要一次性令牌进行身份验证。在服务器上创建一个 API 端点:

// Node.js server
app.get("/scribe-token", yourAuthMiddleware, async (req, res) => {
const response = await fetch("https://api.elevenlabs.io/v1/single-use-token/realtime_scribe", {
method: "POST",
headers: {
"xi-api-key": process.env.ELEVENLABS_API_KEY,
},
});
const data = await response.json();
res.json({ token: data.token });
});

ElevenLabs API 密钥十分敏感。切勿将其暴露给客户端。始终在 服务器上生成令牌。

// Client
const fetchToken = async () => {
const response = await fetch("/scribe-token");
const { token } = await response.json();
return token;
};

连接选项

Scribe.connect() 接受麦克风选项或手动音频选项。两者共用一组基础选项。

基础选项

属性类型默认值描述
tokenstring用于 WebSocket 身份验证的一次性令牌。
modelIdstring模型 ID(例如 "scribe_v2_realtime")。
baseUristring"wss://api.elevenlabs.io"自定义 WebSocket 基础 URI。
commitStrategyCommitStrategy"manual""manual" 或 "vad"。
vadSilenceThresholdSecsnumber1.5VAD 提交前的静音秒数(0.3-3.0)。
vadThresholdnumber0.4VAD 灵敏度(0.1-0.9,数值越低越灵敏)。
minSpeechDurationMsnumber100最短语音时长,单位为 ms(50-2000)。
minSilenceDurationMsnumber100最短静音时长,单位为 ms(50-2000)。
languageCodestringISO-639-1 或 ISO-639-3 语言代码。留空则自动检测。
includeTimestampsbooleanfalse通过 COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS 事件接收词级时间戳。

麦克风选项

传入 microphone 对象,可直接从用户麦克风流式传输音频。连接会自动处理 getUserMedia 和音频编码。

const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
microphone: {
deviceId: "optional-device-id",
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
属性类型描述
deviceIdstring指定麦克风设备 ID。
echoCancellationboolean启用回声消除。
noiseSuppressionboolean启用降噪。
autoGainControlboolean启用自动增益控制。

手动音频选项

传入 audioFormat 和 sampleRate,通过 connection.send() 手动发送音频数据。

import { AudioFormat } from "@elevenlabs/client";
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
});
属性类型描述
audioFormatAudioFormat音频编码格式(例如 AudioFormat.PCM_16000)。
sampleRatenumber采样率,单位为 Hz。必须与 audioFormat 匹配。

AudioFormat 枚举

enum AudioFormat {
PCM_8000 = "pcm_8000",
PCM_16000 = "pcm_16000",
PCM_22050 = "pcm_22050",
PCM_24000 = "pcm_24000",
PCM_44100 = "pcm_44100",
PCM_48000 = "pcm_48000",
ULAW_8000 = "ulaw_8000",
}

麦克风模式

直接从用户麦克风流式传输音频:

import { Scribe, RealtimeEvents } from "@elevenlabs/client";
async function transcribeFromMicrophone() {
const token = await fetchToken();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
microphone: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
document.getElementById("live").textContent = data.text;
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
const el = document.createElement("p");
el.textContent = data.text;
document.getElementById("transcripts").appendChild(el);
document.getElementById("live").textContent = "";
});
document.getElementById("stop").addEventListener("click", () => {
connection.close();
});
}

手动音频模式(文件转写)

通过手动发送音频数据来转写预录音频文件:

import { Scribe, RealtimeEvents, AudioFormat } from "@elevenlabs/client";
async function transcribeFile(file) {
const token = await fetchToken();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Transcript:", data.text);
});
// Decode audio file
const arrayBuffer = await file.arrayBuffer();
const audioContext = new AudioContext({ sampleRate: 16000 });
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// Convert to PCM16
const channelData = audioBuffer.getChannelData(0);
const pcmData = new Int16Array(channelData.length);
for (let i = 0; i < channelData.length; i++) {
const sample = Math.max(-1, Math.min(1, channelData[i]));
pcmData[i] = sample < 0 ? sample * 32768 : sample * 32767;
}
// Send in chunks
const chunkSize = 4096;
for (let offset = 0; offset < pcmData.length; offset += chunkSize) {
const chunk = pcmData.slice(offset, offset + chunkSize);
const bytes = new Uint8Array(chunk.buffer);
const base64 = btoa(String.fromCharCode(...bytes));
connection.send({ audioBase64: base64 });
await new Promise((resolve) => setTimeout(resolve, 50));
}
// Commit and close
connection.commit();
}

RealtimeConnection

Scribe.connect() 返回一个 RealtimeConnection 实例,包含以下方法。

on(event, listener)

注册事件监听器。可用事件类型请参阅事件。

connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Committed:", data.text);
});

off(event, listener)

移除之前注册的事件监听器。

const handler = (data) => console.log(data.text);
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, handler);
// Later
connection.off(RealtimeEvents.COMMITTED_TRANSCRIPT, handler);

send(data)

向 Scribe 发送音频数据(仅限手动音频模式)。

connection.send({
audioBase64: base64AudioChunk,
commit: false, // Optional: commit immediately
sampleRate: 16000, // Optional: override sample rate
previousText: "Previous transcription text", // Optional: context from a previous transcription
});

previousText 字段只能在会话的第一个音频块中发送。在后续 音频块中发送会导致错误。

commit()

手动提交当前转写结果。仅在使用 CommitStrategy.MANUAL 时需要。

connection.commit();

close()

关闭 WebSocket 连接并清理资源(麦克风流、音频上下文)。

connection.close();

事件

使用 connection.on(event, listener) 注册事件监听器。所有事件均可作为 RealtimeEvents 枚举中的常量使用。

转写事件

事件数据描述
SESSION_STARTED{ session_id: string }Scribe 会话已开始。
PARTIAL_TRANSCRIPT{ text: string }临时转写结果。
COMMITTED_TRANSCRIPT{ text: string }已完成的转写结果。
COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS{ text: string; language_code?: string; words?: WordsItem[] }包含词级时间信息的已完成结果。

WordsItem 类型包含词级时间信息:

interface WordsItem {
text?: string; // Word text
start?: number; // Start time in seconds
end?: number; // End time in seconds
type?: "word" | "spacing"; // Token type
speaker_id?: string; // Speaker identifier
}

连接事件

事件数据描述
OPENEventWebSocket 连接已打开。
CLOSEEventWebSocket 连接已关闭。
ERRORError | Event通用错误。

错误事件

所有错误事件均会接收 { error: string }。

事件描述
AUTH_ERROR身份验证错误。
QUOTA_EXCEEDED已超出使用配额。
COMMIT_THROTTLED提交请求受到限流。
TRANSCRIBER_ERROR转写引擎错误。
UNACCEPTED_TERMS尚未接受服务条款。
RATE_LIMITED已被限流。
INPUT_ERROR输入格式无效。
QUEUE_OVERFLOW处理队列已满。
RESOURCE_EXHAUSTED服务器资源已满。
SESSION_TIME_LIMIT_EXCEEDED已达到最长会话时长。
CHUNK_SIZE_EXCEEDED音频块过大。
INSUFFICIENT_AUDIO_ACTIVITY音频活动不足,无法维持连接。

提交策略

控制何时提交转写结果:

import { Scribe, CommitStrategy } from '@elevenlabs/client';
// Manual (default): you control when to commit
const connection = Scribe.connect({
token,
modelId: 'scribe_v2_realtime',
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
commitStrategy: CommitStrategy.MANUAL,
});
// Send audio, then commit when ready
connection.send({ audioBase64: chunk });
connection.commit();
// Voice Activity Detection: Scribe detects silences and commits automatically
const connection = Scribe.connect({
token,
modelId: 'scribe_v2_realtime',
microphone: { echoCancellation: true },
commitStrategy: CommitStrategy.VAD,
});

更多详情请参阅转写文本和提交策略。

完整示例

以下完整示例使用基于 VAD 的提交策略转写麦克风音频:

import { Scribe, RealtimeEvents, CommitStrategy } from "@elevenlabs/client";
async function startTranscription() {
const token = await fetchToken();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
commitStrategy: CommitStrategy.VAD,
microphone: {
echoCancellation: true,
noiseSuppression: true,
},
});
connection.on(RealtimeEvents.SESSION_STARTED, (data) => {
console.log("Session started:", data.session_id);
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
document.getElementById("live").textContent = data.text;
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
const el = document.createElement("p");
el.textContent = data.text;
document.getElementById("transcripts").appendChild(el);
document.getElementById("live").textContent = "";
});
connection.on(RealtimeEvents.ERROR, (error) => {
console.error("Scribe error:", error);
});
// Stop button
document.getElementById("stop").addEventListener("click", () => {
connection.close();
});
}
document.getElementById("start").addEventListener("click", startTranscription);