npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kookapp/voice-web-sdk

v0.1.0

Published

Browser voice SDK using KOOK QuickJoin and initSpeaking signaling

Downloads

0

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 策略,如 allrelay | | 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,播放音量范围为 0100

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:demo

Demo 支持粘贴 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 packs

dev 会持续构建 ESM、CommonJS,并同步类型声明和 AI 降噪 WASM。修改 RTC Core 时还需要同时运行根目录的 pnpm dev:rtc-core