WebRTC 接入(实验)
实验性的浏览器 Opus 接入,含 ICE/TURN 与控制数据通道;默认生产路径为 WSS + PCM
⚠️ 状态:实验:WebRTC(实验)尚不是默认生产接入方式。优先使用 WebSocket 的 WSS + 16 kHz 单声道 PCM 链路。
WebRTC(实验)面向浏览器:浏览器可使用回声消除、降噪、抖动缓冲和拥塞控制,音频走 Opus(48 kHz,服务端统一转成 16 kHz PCM 进房间)。信令是一次性的(非 trickle):客户端把 SDP offer POST 给 gateway,拿回 answer,无需额外的 WebSocket 信令通道。
ℹ️ 本部署的网关地址:https://hearo-gw.apps.aisp24.com(下方示例已代入,可直接复制使用)。
1. 获取 ICE 服务器
GET https://hearo-gw.apps.aisp24.com/v1/ice
返回 { "iceServers": [...] }(标准 RTCIceServer 数组):STUN 加上(若平台已配置)TURN 中继凭据。配了 TURN 时 gateway 自己会强制 relay 策略以穿透对称型 NAT。
const { iceServers } = await fetch("https://hearo-gw.apps.aisp24.com/v1/ice")
.then((r) => r.json());
const pc = new RTCPeerConnection({ iceServers });ℹ️ 自部署调优:STUN 服务器用 HEARO_STUN_URL 配置(默认 Google STUN;国内网络访问不到它只会拖慢 ICE 收集,建议换成可达的 STUN 或设 off 关闭);TURN 用 HEARO_TURN_URL/HEARO_TURN_USERNAME/HEARO_TURN_CREDENTIAL,或 Cloudflare Realtime(HEARO_CF_TURN_KEY_ID/HEARO_CF_TURN_API_TOKEN,由 gateway 代签短期凭据)。
2. 建立 PeerConnection 并交换 SDP
POST https://hearo-gw.apps.aisp24.com/v1/webrtc/offer?name=<昵称>&lang=<语言>
| 项 | 说明 |
|---|---|
| Authorization | 请求头,必填:Bearer <CLIENT_TOKEN> |
| name / lang | 查询参数,可选。房间昵称 / 你说的语言(供字幕) |
| 请求体 | JSON:{"type":"offer","sdp":"<你的 offer SDP>"} |
| 返回 | JSON:{"type":"answer","sdp":"<answer SDP>"},ICE candidate 已内联(非 trickle) |
offer 里需要包含一条音频轨(sendrecv)和一个名为 control 的数据通道(名字必须是 control,其他名字会被忽略)。
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
});
stream.getTracks().forEach((t) => pc.addTrack(t, stream));
// 控制通道:与 WebSocket 文本控制平面等价(speech_start / interrupt / roster ...)
const dc = pc.createDataChannel("control");
dc.onmessage = (e) => handleEvent(JSON.parse(e.data));
// 播放下行音频
pc.ontrack = (e) => { audioEl.srcObject = e.streams[0]; };
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 非 trickle:给 ICE 收集留一小段时间(无需等到 complete——
// STUN 不可达时 complete 永远不来;host candidate 是即时的)
await new Promise((r) => {
pc.onicegatheringstatechange = () => pc.iceGatheringState === "complete" && r();
setTimeout(r, 2000);
});
const offerUrl = new URL("https://hearo-gw.apps.aisp24.com/v1/webrtc/offer");
offerUrl.searchParams.set("name", "浏览器访客");
offerUrl.searchParams.set("lang", "zh-CN");
const resp = await fetch(offerUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ type: "offer", sdp: pc.localDescription.sdp }),
});
const answer = await resp.json();
await pc.setRemoteDescription({ type: "answer", sdp: answer.sdp });服务端同样以“有界等待”收集自己的 ICE candidate 后返回 answer。
⚠️ WebRTC(实验)的客户端 Token 只能放在 Authorization: Bearer 请求头中。任何 token query 参数都会被拒绝,即使请求同时携带了有效的 Bearer 头。
3. 控制与事件:走数据通道
WebRTC(实验)路径下,control 数据通道镜像了 WebSocket 的文本控制平面:同样的 speech_start/interrupt/config/caption_start 控制消息和 roster/transcript/turn_*/caption 事件(完整列表见 WebSocket 第 3、4 节)。音频走媒体轨(Opus),无需手动分帧,也没有 audio 头加二进制帧的配对。
进房时房间广播的初始 roster 快照会由 gateway 缓存,待数据通道打开后立即补发——所以通道一打开你就能拿到当前的完整成员列表。
ℹ️ WebRTC(实验)与 WebSocket 的对应关系:把「二进制音频帧」换成「媒体轨」,把「文本帧」换成「数据通道消息」,其余协议(控制/事件、交互模式、邀请 AI)与 WebSocket 一致。