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

@cf-platform/chat

v1.1.0

Published

聊天相关库

Downloads

32

Readme

@cf-platform/chat

可扩展音视频会议功能库,支持语音通话、视频通话、多点会议、屏幕共享。

架构

┌──────────────────────────────────────┐
│         ConferenceClient             │  ← 用户侧主入口(统一 API)
├──────────────────────────────────────┤
│         LiveKitEngine                │  ← 引擎层(可扩展为其他引擎)
├──────────────────────────────────────┤
│         livekit-client               │  ← 底层 SDK
└──────────────────────────────────────┘
  • ConferenceClient — 面向用户的统一 API,封装引擎、设备管理、本地轨道生命周期
  • LiveKitEngine — 基于 livekit-client v1.15.13 的引擎实现,提供房间连接、轨道管理等功能
  • MediaDeviceManager — 来自 @mm-custom/method,提供设备枚举、热插拔监听等能力

快速开始

安装

npm install @cf-platform/chat

前置依赖@mm-custom/method >= 2.2.11(peerDependency)

基础用法

import { ConferenceClient, LiveKitEngine } from '@cf-platform/chat'
import { MediaDeviceManager } from '@mm-custom/method'

// 1. 创建引擎和设备管理器
const engine = new LiveKitEngine({ url: 'wss://your-server.com' })
const deviceManager = new MediaDeviceManager()

// 2. 创建会议客户端(deviceManager 可选,不传则内部自动创建)
const client = new ConferenceClient({ engine, deviceManager })

// 3. 监听事件
client.on('participantJoined', (p) => console.log(p.id, '加入会议'))
client.on('participantLeft', (p) => console.log(p.id, '离开会议'))
client.on('remoteTrackAdded', (info) => {
  const el = document.getElementById(`video-${info.participantId}`)
  if (el) client.attachTrack(info.track, el as HTMLVideoElement)
})
client.on('remoteTrackRemoved', (info) => {
  const el = document.getElementById(`video-${info.participantId}`)
  if (el) client.detachTrack(el as HTMLVideoElement)
})

// 4. 初始化引擎并加入房间
await engine.init()
try {
  await client.join('room-name', 'access-token')
} catch (e) {
  console.error('加入房间失败', e)
}

// 5. 开启音视频
await client.startAudio()   // 麦克风
await client.startVideo()   // 摄像头

// 6. 离开
await client.leave()

// 7. 销毁(异步,需 await)
await client.destroy()

更多示例

语音通话(纯音频)

const engine = new LiveKitEngine({ url: 'wss://your-server.com' })
const client = new ConferenceClient({ engine })

await engine.init()
await client.join('voice-room', 'token')
await client.startAudio()

// 静音/取消静音
await client.muteAudio()
await client.unmuteAudio()

屏幕共享

await client.startScreenShare()

// 用户通过浏览器 UI 停止共享时,会自动触发 stopScreenShare
// 也可以手动停止
await client.stopScreenShare()

设备切换

// 获取可用设备
const cameras = client.getCameras()
const microphones = client.getMicrophones()

// 切换到指定设备
if (cameras.length > 1) {
  await client.switchCamera(cameras[1].deviceId)
}

E2EE 端到端加密

const engine = new LiveKitEngine({
  url: 'wss://your-server.com',
  e2ee: true,
  cryptoKey: 'shared-secret-key',
})
const client = new ConferenceClient({ engine })

await engine.init()
await client.join('secure-room', 'token')

[!WARNING] E2EE 依赖 new Worker(new URL(..., import.meta.url)) 加载加密 worker,仅在 ESM 消费方式下可用。 以 UMD(<script>)方式引入时 import.meta 会被替换为空对象,worker 路径失效。 此时若配置了 e2ee: true,引擎会在初始化时抛出明确错误(提示改用 ESM 引入),而非静默失败。 如需 E2EE,请使用 import 方式引入本库。

强制 TURN 中继

// 适用于限制性网络环境(如仅允许 HTTP 出站的防火墙)
const engine = new LiveKitEngine({
  url: 'wss://your-server.com',
  forceUseTURN: true,
})

自定义房间配置

import { VideoPresets } from 'livekit-client'

const engine = new LiveKitEngine({
  url: 'wss://your-server.com',
  roomOptions: {
    adaptiveStream: false,
    dynacast: false,
    publishDefaults: {
      videoSimulcastLayers: [VideoPresets.h180, VideoPresets.h360],
    },
  },
})

开启日志

const engine = new LiveKitEngine({
  url: 'wss://your-server.com',
  enableLog: true,
})

// 或通过 ConferenceClient 开启(同时会覆盖引擎的 log 方法)
const client = new ConferenceClient({ engine, enableLog: true })

错误处理

client.on('error', (err) => {
  console.error('[Conference]', err)
})

try {
  await client.join('room', 'token')
} catch (e) {
  // join 失败会抛出异常
  console.error('连接失败', e)
}

完整的 React 组件示例

import { useEffect, useRef, useState } from 'react'
import { ConferenceClient, LiveKitEngine } from '@cf-platform/chat'

function VideoCall({ roomName, token }: { roomName: string; token: string }) {
  const clientRef = useRef<ConferenceClient | null>(null)
  const localVideoRef = useRef<HTMLVideoElement>(null)
  const remoteVideoRef = useRef<HTMLVideoElement>(null)
  const [connected, setConnected] = useState(false)

  useEffect(() => {
    const engine = new LiveKitEngine({ url: 'wss://your-server.com' })
    const client = new ConferenceClient({ engine })
    clientRef.current = client

    client.on('connected', () => setConnected(true))
    client.on('disconnected', () => setConnected(false))
    client.on('remoteTrackAdded', (info) => {
      if (info.kind === 'video' && remoteVideoRef.current) {
        client.attachTrack(info.track, remoteVideoRef.current)
      }
    })

    engine.init()
    client.join(roomName, token).then(() => {
      client.startAudio()
      client.startVideo().then(() => {
        // 将本地视频渲染到预览元素
        const track = (client as any)._localVideoTrack
        if (track && localVideoRef.current) {
          const stream = new MediaStream([track])
          localVideoRef.current.srcObject = stream
        }
      })
    })

    return () => { client.destroy() }
  }, [roomName, token])

  return (
    <div>
      <video ref={localVideoRef} autoPlay playsInline muted />
      <video ref={remoteVideoRef} autoPlay playsInline />
      <p>{connected ? '已连接' : '未连接'}</p>
    </div>
  )
}

API

ConferenceClient

连接

| 方法 | 说明 | |---|---| | join(roomName: string, token: string): Promise<void> | 加入房间(连接失败会抛出异常) | | leave(): Promise<void> | 离开房间,自动关闭所有本地轨道(任一 stop 失败不影响其他清理和断开) |

语音通话

| 方法 | 说明 | |---|---| | startAudio(deviceId?: string): Promise<void> | 开启麦克风。采集失败或未拿到音频轨道时抛出错误 | | stopAudio(): Promise<void> | 关闭麦克风 | | muteAudio(): Promise<void> | 静音(保持发布,发送静音数据,触发 localAudioChanged(false)) | | unmuteAudio(): Promise<void> | 取消静音(触发 localAudioChanged(true)) |

视频通话

| 方法 | 说明 | |---|---| | startVideo(deviceId?: string): Promise<void> | 开启摄像头。采集失败或未拿到视频轨道时抛出错误 | | stopVideo(): Promise<void> | 关闭摄像头 | | muteVideo(): Promise<void> | 静画(保持发布,发送黑屏,触发 localVideoChanged(false)) | | unmuteVideo(): Promise<void> | 取消静画(触发 localVideoChanged(true)) |

屏幕共享

| 方法 | 说明 | |---|---| | startScreenShare(): Promise<void> | 开始屏幕共享。采集失败或未拿到屏幕轨道时抛出错误 | | stopScreenShare(): Promise<void> | 停止屏幕共享(同时解绑 ended 监听器) |

设备管理

| 方法 | 说明 | |---|---| | switchCamera(deviceId: string): Promise<void> | 切换摄像头。先采集新轨道成功再停旧轨道,采集失败时旧轨道保持不变并抛出错误 | | switchMicrophone(deviceId: string): Promise<void> | 切换麦克风。同上,失败时旧轨道保持不变并抛出错误 | | getDevices(): GroupedDevices | 获取当前设备列表(按类别分组) | | getCameras(): DeviceInfo[] | 获取摄像头列表 | | getMicrophones(): DeviceInfo[] | 获取麦克风列表 | | getSpeakers(): DeviceInfo[] | 获取扬声器列表 |

设备热插拔时自动迁移到可用设备,无可用设备时自动停止轨道。

轨道渲染

| 方法 | 说明 | |---|---| | attachTrack(track: MediaStreamTrack, element: HTMLMediaElement): void | 将轨道附加到 DOM 元素(创建 MediaStream 并设置 srcObject) | | detachTrack(element: HTMLMediaElement): void | 从 DOM 元素移除轨道绑定(仅清空 srcObject,不停止轨道本身,轨道生命周期由引擎管理) |

参与者

| 方法 | 说明 | |---|---| | getParticipants(): ParticipantInfo[] | 获取当前房间所有远程参与者 |

状态

| 属性 | 类型 | 说明 | |---|---|---| | connectionState | ConnectionState | 当前连接状态 | | isAudioEnabled | boolean | 麦克风是否已开启 | | isVideoEnabled | boolean | 摄像头是否已开启 | | isScreenSharing | boolean | 是否正在屏幕共享 | | isAudioMuted | boolean | 音频是否被静音 | | isVideoMuted | boolean | 视频是否被静音 |

事件

| 事件 | 参数 | 说明 | |---|---|---| | connected | room: any | 连接成功(透传) | | disconnected | reason?: DisconnectReason | 连接断开(透传) | | connectionStateChanged | state: ConnectionState | 连接状态变更(透传) | | participantJoined | ParticipantInfo | 参与者加入(透传) | | participantLeft | { id: string } | 参与者离开(透传) | | remoteTrackAdded | RemoteTrackInfo | 远程轨道添加(透传),调用 attachTrack 渲染。kindscreen 时表示对方屏幕共享轨道 | | remoteTrackRemoved | RemoteTrackInfo | 远程轨道移除(透传),调用 detachTrack 清理 | | trackMuted | TrackMuteInfo | 轨道静音(透传) | | trackUnmuted | TrackMuteInfo | 轨道取消静音(透传) | | activeSpeakersChanged | ParticipantInfo[] | 活跃发言人变更(透传) | | localAudioChanged | enabled: boolean | 本地音频状态变更(自产),true=开启/取消静音,false=关闭/静音 | | localVideoChanged | enabled: boolean | 本地视频状态变更(自产),true=开启/取消静音,false=关闭/静音 | | screenShareChanged | enabled: boolean | 屏幕共享状态变更(自产) | | mediaDevicesChanged | room: any | 设备列表变更(透传) | | activeDeviceChanged | ActiveDeviceChange | 活跃设备切换(透传) | | error | Error | 错误(透传) |

生命周期

| 方法 | 说明 | |---|---| | destroy(): Promise<void> | 销毁客户端,释放所有资源(异步方法,需 await) |

LiveKitEngine

基于 livekit-client v1.15.13 的引擎实现。通常作为 ConferenceClient 的底层引擎使用,不要直接调用其 @internal 方法

配置

interface LiveKitEngineConfig {
  url: string                          // 服务器地址(必填,格式如 wss://your-server.com)
  enableLog?: boolean                  // 是否开启日志(默认 false)
  e2ee?: boolean                       // 是否启用端到端加密(需同时提供 cryptoKey)
  cryptoKey?: string                   // E2EE 加密密钥(e2ee 为 true 时必填)
  forceUseTURN?: boolean               // 是否强制使用 TURN 中继(默认 false)
  roomOptions?: RoomOptions            // 房间配置选项(深度合并到默认配置)
  roomConnectOptions?: RoomConnectOptions  // 连接配置选项(深度合并到默认配置)
}

默认房间选项

| 选项 | 默认值 | 说明 | |---|---|---| | adaptiveStream | true | 自动管理订阅视频质量 | | dynacast | true | 动态暂停无人消费的视频层 | | publishDefaults.videoSimulcastLayers | [h90, h216] | 视频联播层 | | publishDefaults.screenShareEncoding | h1080fps30 | 屏幕共享编码 | | disconnectOnPageLeave | false | 离开页面不自动断开 |

公开方法

| 方法 | 说明 | |---|---| | init(config?) | 初始化引擎,创建 Room 实例并绑定事件。可在构造后再次调用以更新配置 | | initConfig(config) | 更新引擎配置(唯一的配置入口,构造函数内部也调用此方法) | | setVideoQuality(quality) | 设置远程视频质量(LOW/MEDIUM/HIGH),需关闭 adaptiveStream 才生效 | | setVideoFPS(fps) | 设置远程视频帧率 | | getRemoteParticipants() | 获取当前房间所有远程参与者 | | log(...logs) | 输出日志(仅在 enableLog 为 true 时生效) | | ~~handleTrackSubscribed(participant, track, elementId)~~ | 已废弃 — 请使用 ConferenceClient.attachTrack() 替代 |

[!NOTE] 以下引擎层方法标记为 @internal,由 ConferenceClient 内部调度,请勿直接调用

  • connect() / disconnect() → 通过 client.join() / client.leave()
  • publishTrack() / unpublishTrack() → 通过 client.startAudio/Video() 等方法

直接调用会绕过本地轨道生命周期管理(如离开时自动停止轨道)。

扩展引擎

当前 ConferenceClient 直接依赖 LiveKitEngine。如需接入其他媒体引擎(如 Agora、TRTC), 可参考 LiveKitEngine 的实现,创建一个继承 EventManager 并提供相同公开方法的新引擎类:

import { EventManager } from '@mm-custom/method'
import type { ConnectionState, LocalTrackInfo } from '@cf-platform/chat'

class AgoraEngine extends EventManager {
  // 必须实现以下方法(与 LiveKitEngine 保持一致的接口)
  get connectionState(): ConnectionState { /* ... */ }
  init(config?: any): void { /* ... */ }
  async connect(roomName: string, token: string): Promise<void> { /* ... */ }   // @internal
  async disconnect(): Promise<void> { /* ... */ }                                 // @internal
  async publishTrack(track: LocalTrackInfo): Promise<void> { /* ... */ }         // @internal
  async unpublishTrack(track: LocalTrackInfo): Promise<void> { /* ... */ }       // @internal
  getRemoteParticipants(): Record<string, any> { /* ... */ }
  destroy(): void { /* ... */ }
}

// 使用方式与 LiveKit 完全一致
const client = new ConferenceClient({ engine: new AgoraEngine() as any })

[!NOTE] connectdisconnectpublishTrackunpublishTrack 标记为 @internal, 由 ConferenceClient 统一调用,外部不应直接使用。

类型参考

// 连接状态
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'

// 轨道来源
type TrackSource = 'camera' | 'microphone' | 'screen' | 'unknown'

// 本地轨道信息
interface LocalTrackInfo {
  track: MediaStreamTrack
  kind: 'audio' | 'video' | 'screen'
  source?: TrackSource
  deviceId?: string
  label?: string
}

// 远程轨道信息
interface RemoteTrackInfo {
  participantId: string
  participantName?: string
  track: MediaStreamTrack
  kind: 'audio' | 'video' | 'screen'
  isLocal: false
}

// 参与者信息
interface ParticipantInfo {
  id: string
  name?: string
  isLocal?: boolean
}

// 轨道静音状态
interface TrackMuteInfo {
  participantId: string
  kind: 'audio' | 'video' | 'screen'
  isMuted: boolean
}

// 设备信息
interface DeviceInfo {
  deviceId: string
  groupId: string
  kind: MediaDeviceKind
  label: string
}

// 分组设备列表
interface GroupedDevices {
  audioinput: DeviceInfo[]
  audiooutput: DeviceInfo[]
  videoinput: DeviceInfo[]
}

// 活跃设备变更
interface ActiveDeviceChange {
  kind: MediaDeviceKind
  deviceId: string
}

// 会议客户端选项
interface ConferenceOptions {
  engine: LiveKitEngine
  deviceManager?: MediaDeviceManager  // 可选,不传则内部自动创建
  enableLog?: boolean
}

// LiveKit 引擎配置
interface LiveKitEngineConfig {
  url: string
  enableLog?: boolean
  e2ee?: boolean
  cryptoKey?: string
  forceUseTURN?: boolean
  roomOptions?: RoomOptions
  roomConnectOptions?: RoomConnectOptions
}

依赖

| 包 | 版本 | 类型 | 说明 | |---|---|---|---| | livekit-client | ^1.15.13 | dependency | LiveKit Web SDK | | @mm-custom/method | ^2.2.11 | peerDependency | 事件管理器 & 设备管理器 |

版本

当前版本:1.1.0

更新日志

v1.1.0 (修复 + 打包对齐)

打包配置(对齐 cim/mq)

  • 🔧 统一打包配置vite.config.ts 按 cim/mq 模板重写:显式 formats: ['es', 'umd']、补 rollupOptions.inputsourcemap: false,新增 cleanup(去注释)+ terser(压缩、mangle _ 前缀属性)两个 rollup 插件
  • 🔧 dts 配置补齐dts 插件补 include/exclude/insertTypesEntry/removeComments,与 cim/mq 一致
  • ⚠️ 产物文件名变更chat.lib.es.js / chat.lib.umd.jschat.es.js / chat.umd.js(与 cim/mq 的 <pkg>.<format>.js 命名统一)。package.jsonmain/module/exports 同步更新,按包名引入的消费方不受影响;filesREADME.md

逻辑修复

  • 🐛 修复 unpublishTrack 永远无法取消发布 — 原先用 trackInfo.track.id(MediaStreamTrack 的 UUID)当作 Track.Source 枚举调 getTrack,永远返回 undefined,导致本地轨道在服务端永不取消发布。改为直接用 MediaStreamTrackLocalParticipant.unpublishTrack(track)(livekit 原生支持)
  • 🐛 修复屏幕共享远程轨道 kind 错标为 video — livekit 的 track.kind 只有 audio/video/unknown,屏幕共享视频轨道 kind 仍为 video,消费者按 kind === 'screen' 判断永远拿不到。新增 _mapTrackKind,通过 track.source === ScreenShare 映射为 'screen',订阅/退订/静音三处统一使用
  • 🛡️ 本地轨道采集判空startAudio/startVideo/startScreenShare 采集后检查是否拿到轨道,失败时释放已申请轨道并抛出错误(原先会把 undefined 传给引擎)
  • 🛡️ 设备切换失败回滚switchCamera/switchMicrophone 改为"先采集新轨道成功再停旧轨道",采集失败时旧轨道保持不变并抛出错误(原先是"先停旧再采新",失败会导致新旧轨道同时丢失)
  • 🛡️ 屏幕共享 ended 监听器不再泄漏 — 改用具名 handler 存为字段,stopScreenShareremoveEventListener(原先用匿名函数无法解绑,重复 start/stop 会累积监听器)
  • 🛡️ E2EE worker 加载失败保护 — UMD 引入下 import.meta.url 失效,new Worker(new URL(...)) 会抛错。现用 try/catch 捕获并抛出明确错误(提示改用 ESM),而非静默失败

代码质量

  • 🧹 unmuteAudio 去掉多余的 await this.emit(...),与其他 emit 调用一致
  • 🧹 setVideoFPS 去掉 (trackPub as any) 强转(RemoteTrackPublication.setVideoFPS 本就是公开方法)
  • 🧹 log 方法去掉非对象日志里多余的 符号

文档

  • 📝 方法表补充 @throws 说明、switchCamera/Microphone 的新行为、remoteTrackAdded 屏幕共享识别说明
  • 📝 E2EE 章节增加 WARNING:UMD 下 E2EE 不可用,会抛明确错误

v1.0.1

  • 完善会议库,补全注释与文档

v1.0.0

  • 基于 livekit-client 的可扩展音视频会议库
  • ConferenceClient 统一 API(语音/视频/多点会议/屏幕共享)
  • LiveKitEngine 引擎层(房间连接、轨道管理、设备管理、E2EE、TURN)
  • 设备热插拔自动迁移轨道
  • 支持自定义房间配置深度合并

License

ISC