@watertreestar/voip-sdk
v0.1.3
Published
Browser SDK for voip-edge device sessions
Readme
voip-edge 浏览器 SDK
@watertreestar/voip-sdk 用于在浏览器中控制一个受管 SIP 设备:建立信令和媒体通道、监听设备/通话状态、播放音频、申请讲话权、控制录音。一个 CallClient 只能对应一个 deviceId;多个设备必须使用多个实例。
文档
| 文档 | 说明 | | --- | --- | | 快速开始 | 安装、创建客户端、连接和退出。 | | 状态与讲话权 | 设备、SIP、媒体和 Talk Lease 状态;何时可申请讲话权。 | | API 与事件 | 配置项、方法、事件与多设备管理。 | | 指标指南 | 指标获取方式、字段含义、Prometheus 输出及 WS PCM / WebRTC 的适用范围。 |
安装
npm install @watertreestar/voip-sdkSDK 使用浏览器原生 WebSocket、RTCPeerConnection 和媒体 API。认证应通过 WebSocket 握手、Cookie、短期 token 或反向代理完成;不要将用户身份或任意 SIP target 当作 SDK 参数发送。
最小示例
import { CallClient } from "@watertreestar/voip-sdk";
const client = new CallClient({
wsUrl: "wss://edge.example.com/ws",
deviceId: "gate-01",
transport: "webrtc",
});
client.on("device_state", (state) => console.info("设备状态", state));
client.on("call_state", (state) => console.info("通话状态", state));
client.on("talk_state", (state) => console.info("讲话权状态", state));
client.on("error", (error) => console.error(error));
await client.join();
// 建议确认 SIP 已接通后再允许用户点击“讲话”。
if (client.deviceState?.callState === "connected") {
await client.acquireTalk();
}WS 媒体反向代理路径
服务端返回的 WS 媒体地址通常是相对路径(如 /media?ticket=...)。当它需要经过带路径前缀的公开反向代理时,传入 mediaBaseUrl。SDK 保留该路径前缀,并将 HTTP(S) 协议转换为浏览器可连接的 WS(S) 协议:
const client = new CallClient({
wsUrl: "ws://edge.internal/socket",
mediaBaseUrl: "https://a.com/a/b",
deviceId: "gate-01",
transport: "ws",
});
// 服务端的 /media?ticket=... 会连接到
// wss://a.com/a/b/media?ticket=...录音浏览器预览
管理 API 创建的播放地址是短期签名的相对路径,不需要把管理认证信息放到 <audio> 请求中。用 SDK 将其与信令地址拼接为浏览器可访问的 HTTP(S) URL:
const previewURL = client.resolvePlaybackURL(playback.url);
audio.src = previewURL;也可以在尚未创建 CallClient 时使用 resolvePlaybackURL(wsUrl, playback.url)。签名 URL 会在服务端指定的到期时间后失效。
若浏览器访问的公开地址与 SDK 信令地址不同(例如信令使用内网地址),创建客户端时传入 publicBaseURL;client.resolvePlaybackURL() 将优先使用它:
const client = new CallClient({
wsUrl: "ws://edge.internal:8080/ws",
publicBaseURL: "https://preview.example.com",
deviceId: "gate-01",
});join() 只有在服务端确认 session 且媒体通道就绪后才 resolve。检查 selectedTransport 获取实际建立的传输;Edge 当前不会在首选 WebRTC 失败时自动降级到 WS PCM。
浏览器啸叫抑制
默认 BrowserPCMAudioAdapter 在 WS PCM 上行链路启用窄带啸叫抑制。它在浏览器 AudioWorklet 内检测麦克风中的持续单频反馈,并在音频发送给 Edge 前应用 notch filter。它只用于近距离外放的保护层,不能替代耳机、降低设备扬声器音量或正确摆放麦克风。
const client = new CallClient({
wsUrl: "wss://edge.example.com/ws",
deviceId: "gate-01",
transport: "ws",
feedbackSuppression: {
enabled: true,
mode: "balanced",
},
});
// 发生持续啸叫时,切换为更宽频、更强的抑制与上行 ducking。
client.setFeedbackSuppressionMode("aggressive");
// 可按产品 UI 开关控制。
client.setFeedbackSuppression(false);balanced:默认值,使用较窄的 notch,优先保留自然人声。aggressive:使用较宽的 notch;检测到持续反馈时,临时将浏览器上行衰减约18 dB,反馈停止后平滑恢复。近距离扬声器外放时推荐此预设。getPCMAdapterMetrics()返回feedbackSuppressionEnabled、feedbackSuppressionMode、feedbackNotchFrequencies、feedbackDetections和feedbackUplinkAttenuationDb,可用于 UI 与现场诊断。
setPlaybackActive() 与 setPlaybackOutputDevice() 同时适用于 WS PCM 和 WebRTC;传输层只在创建客户端时指定。播放输出设备为 default 或空值时,SDK 不调用 setSinkId(),直接使用系统默认扬声器。若指定的输出设备在运行期间断开并触发 NotFoundError,SDK 会自动回退到系统默认输出。
若浏览器拒绝即时选择扬声器的权限,setPlaybackOutputDevice() 会 reject,并同步触发明确的回调事件。媒体尚未就绪时,设备选择会延后应用;该阶段的所有异常都通过 playback_error 通知:
client.on("playback_output_permission_denied", ({ outputDeviceId, error }) => {
console.warn(`没有权限选择输出设备 ${outputDeviceId}`, error);
// 提示用户授权,或改用 client.setPlaybackOutputDevice("default")。
});
client.on("playback_error", ({ operation, outputDeviceId, error }) => {
console.warn(`播放${operation}失败,设备:${outputDeviceId}`, error);
});
try {
await client.setPlaybackActive(true);
await client.setPlaybackOutputDevice("speaker-01");
} catch (error) {
// 初始化或设备选择失败;对应事件也会同步触发。
}当前公开 API 仅支持 enabled 和两种 mode 预设;检测阈值、扫描频段、notch Q 值和 ducking 幅度是 SDK 内部实现,不支持逐项配置。这些参数互相影响,单独暴露会使误配置导致误抑制人声或无法停止反馈。需要新的现场预设时,请基于设备、麦克风、扬声器位置和状态指标评估后扩展 SDK。
保留 Talk Lease 的本地静音
申请 Talk Lease 后,可停止本地麦克风上行但不释放 lease;这适用于暂时不说话、仍需保留讲话权的场景。WS PCM 与 WebRTC 使用相同 API:
await client.acquireTalk();
await client.muteMicrophone();
// Talk Lease 仍有效,不会把讲话权交给其他会话。
await client.unmuteMicrophone();microphone_muted 事件携带 { muted, leaseId },可直接驱动静音按钮。取消静音时,SDK 仅在 lease 未过期且仍由当前客户端持有时恢复上行。
关键约束
connect()仅连接/ws;join()才创建设备 session 和媒体。- 设备离线时
join()仍可能成功,随后等待device_state与call_state更新。 - 协议允许已加入的会话调用
acquireTalk(),但产品 UI 应仅在deviceState.callState === "connected"时开放讲话按钮。未接通时申请没有有效的 SIP 上行媒体路径。 - 获得
TalkLease后才可开始采集或发送上行音频;lease 失效、释放或被抢占后必须立即停止。 - 讲话权由设备维度独占。
talk_busy表示其他 session 正在讲话;管理员才可以使用acquireTalk(true)抢占。
详见 状态与讲话权。
