> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# JavaScript SDK

> **Info**
>
> 如需了解 Scribe 及其功能概览，请参阅[语音转文本 概览](/docs/zh/capabilities/speech-to-text)。如需分步使用指南，请参阅[客户端 流式传输](/docs/zh/eleven-api/guides/how-to/speech-to-text/realtime/client-side-streaming)。

## 安装

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

> **Tip**
>
> 使用 [ElevenLabs 语音转文本技能](https://github.com/elevenlabs/skills/tree/main/speech-to-text)，通过 AI 编程助手转写音频：
>
> ```bash
> npx skills add elevenlabs/skills --skill speech-to-text
> ```

> **Note**
>
> 此库可用于任何基于 JavaScript 的项目。如果使用 React，建议使用
> [`useScribe` hook](/docs/zh/eleven-api/resources/libraries/scribe-stt/react-scribe)，它提供
> 内置状态管理和生命周期处理。

## 使用方法

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

```js
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 端点：

```js
// 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 });
});
```

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

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

## 连接选项

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

### 基础选项

| 属性                          | 类型               | 默认值                         | 描述                                                   |
| --------------------------- | ---------------- | --------------------------- | ---------------------------------------------------- |
| **token**                   | `string`         |                             | 用于 WebSocket 身份验证的一次性令牌。                             |
| **modelId**                 | `string`         |                             | 模型 ID（例如 `"scribe_v2_realtime"`）。                    |
| **baseUri**                 | `string`         | `"wss://api.elevenlabs.io"` | 自定义 WebSocket 基础 URI。                                |
| **commitStrategy**          | `CommitStrategy` | `"manual"`                  | `"manual"` 或 `"vad"`。                                |
| **vadSilenceThresholdSecs** | `number`         | `1.5`                       | VAD 提交前的静音秒数（0.3-3.0）。                               |
| **vadThreshold**            | `number`         | `0.4`                       | VAD 灵敏度（0.1-0.9，数值越低越灵敏）。                            |
| **minSpeechDurationMs**     | `number`         | `100`                       | 最短语音时长，单位为 ms（50-2000）。                              |
| **minSilenceDurationMs**    | `number`         | `100`                       | 最短静音时长，单位为 ms（50-2000）。                              |
| **languageCode**            | `string`         |                             | ISO-639-1 或 ISO-639-3 语言代码。留空则自动检测。                  |
| **includeTimestamps**       | `boolean`        | `false`                     | 通过 `COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS` 事件接收词级时间戳。 |

### 麦克风选项

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

```js
const connection = Scribe.connect({
  token,
  modelId: "scribe_v2_realtime",
  microphone: {
    deviceId: "optional-device-id",
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
  },
});
```

| 属性                   | 类型        | 描述          |
| -------------------- | --------- | ----------- |
| **deviceId**         | `string`  | 指定麦克风设备 ID。 |
| **echoCancellation** | `boolean` | 启用回声消除。     |
| **noiseSuppression** | `boolean` | 启用降噪。       |
| **autoGainControl**  | `boolean` | 启用自动增益控制。   |

### 手动音频选项

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

```js
import { AudioFormat } from "@elevenlabs/client";

const connection = Scribe.connect({
  token,
  modelId: "scribe_v2_realtime",
  audioFormat: AudioFormat.PCM_16000,
  sampleRate: 16000,
});
```

| 属性              | 类型            | 描述                                  |
| --------------- | ------------- | ----------------------------------- |
| **audioFormat** | `AudioFormat` | 音频编码格式（例如 `AudioFormat.PCM_16000`）。 |
| **sampleRate**  | `number`      | 采样率，单位为 Hz。必须与 `audioFormat` 匹配。    |

#### AudioFormat 枚举

```typescript
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",
}
```

## 麦克风模式

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

```js
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();
  });
}
```

## 手动音频模式（文件转写）

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

```js
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)

注册事件监听器。可用事件类型请参阅[事件](#events)。

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

### off(event, listener)

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

```js
const handler = (data) => console.log(data.text);
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, handler);

// Later
connection.off(RealtimeEvents.COMMITTED_TRANSCRIPT, handler);
```

### send(data)

向 Scribe 发送音频数据（仅限手动音频模式）。

```js
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
});
```

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

### commit()

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

```js
connection.commit();
```

### close()

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

```js
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` 类型包含词级时间信息：

```typescript
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
}
```

### 连接事件

| 事件        | 数据               | 描述               |
| --------- | ---------------- | ---------------- |
| **OPEN**  | `Event`          | WebSocket 连接已打开。 |
| **CLOSE** | `Event`          | WebSocket 连接已关闭。 |
| **ERROR** | `Error \| 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** | 音频活动不足，无法维持连接。 |

## 提交策略

控制何时提交转写结果：

```js
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,
});
```

更多详情请参阅[转写文本和提交策略](/docs/zh/eleven-api/guides/how-to/speech-to-text/realtime/transcripts-and-commit-strategies)。

## 完整示例

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

```js
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);
```