@kookapp/voice-web-sdk
v0.1.0
Published
Browser voice SDK using KOOK QuickJoin and initSpeaking signaling
Downloads
0
Keywords
Readme
@kookapp/voice-web-sdk
voice-web-sdk 是 KOOK 浏览器语音底层 SDK,在 @kookapp/voice-rtc-core 之上提供麦克风采集、远端播放、设备切换、音量检测和 AI 降噪。
当前入口使用 BrowserVoiceSdk,建链流程为 quickJoin,第一次发布音轨时通过 initSpeaking 完成发送侧协商。
功能
- 浏览器麦克风采集和本地音频流管理。
- QuickJoin、InitSpeaking、发送/接收 Transport 生命周期。
- 只收听加入后升级为真实麦克风 Producer。
- 远端音轨包装、播放、静音和音量控制。
- 输入设备、输出设备和麦克风灵敏度切换。
- WebAINS AI 降噪及浏览器原生降噪回退。
- ESM、CommonJS 和 TypeScript 类型声明。
安装
pnpm add @kookapp/voice-web-sdk浏览器要求
- 支持
RTCPeerConnection。 - 支持
navigator.mediaDevices.getUserMedia()。 - 页面运行在 HTTPS 或 localhost。
- 麦克风申请和远端自动播放应由用户手势触发,以满足浏览器权限策略。
- 输出设备切换依赖浏览器对
HTMLMediaElement.setSinkId()的支持。
可以在创建客户端前检测:
import VoiceSdk from '@kookapp/voice-web-sdk'
const sdk = new VoiceSdk()
if (!sdk.checkSystemRequirements()) {
throw new Error('当前浏览器不支持 WebRTC 语音')
}主要导出
| 导出 | 说明 |
| --- | --- |
| 默认导出 / BrowserVoiceSdk | SDK 工厂,创建客户端和音频流 |
| BrowserVoiceClient | 浏览器 RTC 客户端 |
| createBrowserSilentAudioSource | 创建只收听模式使用的静音音轨 |
| normalizeVoiceTokenConfig | 归一化 KOOK Token 响应 |
| setSdkDeps / getSdkDeps | 注入宿主能力和事件回调 |
Token 配置
SDK 不负责请求业务 Token。调用方应从自己的后端取得 KOOK 频道 Token,再传给 normalizeVoiceTokenConfig()。
import { normalizeVoiceTokenConfig } from '@kookapp/voice-web-sdk'
const response = await fetch(`/api/voice/token?channelId=${channelId}`)
if (!response.ok) throw new Error('获取语音 Token 失败')
const tokenResponse = await response.json()
const token = normalizeVoiceTokenConfig(tokenResponse, {
ipDiscovery: [],
})常用 Token 字段:
| 字段 | 说明 |
| --- | --- |
| gateway_url / wsUrl | Protoo WebSocket 地址 |
| iceServers | TURN/STUN 配置 |
| iceTransportPolicy | ICE 策略,如 all 或 relay |
| rtc_config / rtcConfig | KOOK RTC 扩展配置 |
| call_id | 服务端呼叫标识 |
| join_voice | 加入语音相关标识 |
| startAins | 服务端建议的 AI 降噪初始状态 |
gateway_url 通常包含敏感临时凭证,不要完整写入日志或错误上报。
完整发言示例
import VoiceSdk, { normalizeVoiceTokenConfig, setSdkDeps } from '@kookapp/voice-web-sdk'
const channelId = 'channel-123'
const uid = 'user-456'
const token = normalizeVoiceTokenConfig(tokenResponse)
setSdkDeps({
getInitVolume: () => ({ outputVolume: 100, usersVolume: {} }),
getInputMuted: () => false,
onUserTalk: (peerId) => console.log('正在说话', peerId),
onLocalVolume: (level) => console.log('本地音量', level),
onAuthing: (active) => console.log('鉴权状态', active),
})
const sdk = new VoiceSdk()
let localStream = sdk.createStream({
userID: uid,
audio: true,
video: false,
microphoneId: 'default',
autoSens: false,
sensValue: -55,
aiNoiseSuppression: false,
})
const client = sdk.createClient({
uid,
joinId: `${Date.now()}-${uid}-${channelId}`,
handlers: {
getLocalStream: () => localStream,
restartTrack: (_oldTrack, replace) => {
const nextTrack = localStream.streamObj?.getAudioTracks()[0]
if (nextTrack) replace(nextTrack, localStream)
},
},
})
client.tokenConfig = token
client.on('stream-added', ({ stream, peerInfo }) => {
client.subscribe(stream)
stream.play(undefined, (error) => {
if (error) console.warn('远端音频播放失败', peerInfo, error)
})
})
client.on('peer-join', (peer) => console.log('成员加入', peer))
client.on('peer-leave', (peer) => console.log('成员离开', peer))
client.on('error', (error) => console.error('语音错误', error))
await new Promise<void>((resolve, reject) => {
localStream.init(resolve, reject)
})
await client.join(channelId, {
gateway_url: token.gateway_url,
ip_discovery: token.ipDiscovery,
forceRelay: token.forceRelay,
rtcConfig: token.rtcConfig,
iceServers: token.iceServers,
iceTransportPolicy: token.iceTransportPolicy,
quickJoinListenOnly: false,
joinMuted: false,
joinDeafened: false,
})
await client.publish(localStream)推荐调用顺序:
createStream()
-> stream.init()
-> createClient()
-> client.join()
-> client.publish()client.join() 返回 Promise,同时保留可选的成功/失败回调参数以兼容旧调用方式。
只收听模式
只收听时不需要创建本地 Stream:
const sdk = new VoiceSdk()
const client = sdk.createClient({
uid,
joinId: `${Date.now()}-${uid}-${channelId}`,
})
client.tokenConfig = token
await client.join(channelId, {
gateway_url: token.gateway_url,
iceServers: token.iceServers,
iceTransportPolicy: token.iceTransportPolicy,
quickJoinListenOnly: true,
joinMuted: true,
})SDK 会创建静音占位音轨完成 InitSpeaking。后续创建真实 Stream 并调用 publish() 时,会把占位 Producer 升级为真实麦克风音轨。
麦克风与耳机控制
client.pauseMic()
client.resumeMic()
client.pauseHeadset()
client.resumeHeadset()
localStream.muteAudio()
localStream.unmuteAudio()pauseHeadset() 会通知 RTC Core;宿主如果还有额外的本地播放节点,也应同步暂停或静音。
切换输入和输出设备
// 切换麦克风,并在已发布时替换 Producer 音轨
await localStream.switchDevice(client, microphoneDeviceId)
// 设置远端 Stream 的播放设备
remoteStream.setAudioOutput(
speakerDeviceId,
() => console.log('输出设备切换成功'),
(error) => console.warn('输出设备切换失败', error),
)设备列表由宿主通过 navigator.mediaDevices.enumerateDevices() 获取。首次读取设备名称前通常需要先请求麦克风权限。
灵敏度和音量
localStream.setSens(-55)
localStream.setAutoSens(true)
localStream.setInputVolume(0.8)
remoteStream.setAudioVolume(80)
remoteStream.setAudioGlobalVolume(100)具体数值范围应由宿主 UI 做约束。当前 Runtime 使用的灵敏度范围为 -90 dB 到 -10 dB,播放音量范围为 0 到 100。
AI 降噪
发布包在 dist/assets/external/ 中包含:
ai_denoiser_module.wasm
ai_denoiser_module_simd.wasm宿主需要把两个文件部署到同一静态目录,并注入目录地址和 License 获取函数:
import { setSdkDeps } from '@kookapp/voice-web-sdk'
setSdkDeps({
supportAiNoiseSuppression: () =>
typeof AudioWorkletNode !== 'undefined' && typeof WebAssembly !== 'undefined',
getAiNoiseAssetsPath: () => '/assets/kook-ains',
getAiNoiseLicense: async ({ refresh }) => {
const response = await fetch('/api/voice/ai-noise-license', {
cache: refresh ? 'reload' : 'default',
})
if (!response.ok) throw new Error('获取 AI 降噪 License 失败')
return response.json()
},
onAiNoiseSuppressionError: (error) => {
console.warn('AI 降噪不可用,已回退浏览器原生降噪', error)
},
})
const stream = sdk.createStream({
userID: uid,
audio: true,
video: false,
aiNoiseSuppression: true,
})getAiNoiseAssetsPath() 返回目录,不要返回具体 WASM 文件名。SDK 会根据 SIMD 支持情况选择文件。WASM、License 或兼容性检查失败时会尝试恢复浏览器原生噪声抑制。
运行时切换:
await localStream.updateAiConfig({ aiNoiseSuppression: true }, client)
await localStream.updateAiConfig({ aiNoiseSuppression: false }, client)退出和资源释放
await client.unpublish(localStream).catch(() => {})
await client.leave().catch(() => {})
client.close()
localStream.close()leave() 后客户端会关闭。重新加入频道时应创建新的客户端实例。
本地 Demo
在仓库根目录执行:
pnpm --filter @kookapp/voice-web-sdk build
pnpm --filter @kookapp/voice-web-sdk dev:demoDemo 支持粘贴 Token 响应,并验证加入、发布、只收听、静音、开麦和离房流程。
测试、构建和打包
pnpm --filter @kookapp/voice-web-sdk test
pnpm --filter @kookapp/voice-web-sdk typecheck
pnpm --filter @kookapp/voice-web-sdk build
pnpm --filter @kookapp/voice-web-sdk dev
pnpm --dir packages/voice-web-sdk pack --pack-destination packsdev 会持续构建 ESM、CommonJS,并同步类型声明和 AI 降噪 WASM。修改 RTC Core 时还需要同时运行根目录的 pnpm dev:rtc-core。
