@cf-platform/chat
v1.2.2
Published
聊天相关库
Readme
@cf-platform/chat
可扩展音视频会议功能库,支持语音通话、视频通话、多点会议、屏幕共享。
架构
┌──────────────────────────────────────┐
│ ConferenceManager │ ← 高层门面(一键 init → join)
├──────────────────────────────────────┤
│ ConferenceClient │ ← 用户侧主入口(统一 API)
├──────────────────────────────────────┤
│ LiveKitEngine │ ← 引擎层(可扩展为其他引擎)
├──────────────────────────────────────┤
│ livekit-client │ ← 底层 SDK
└──────────────────────────────────────┘- ConferenceManager — 高层门面,封装引擎 + 客户端的完整生命周期,将 init → join → leave → destroy 简化为几个方法调用
- ConferenceClient — 面向用户的统一 API,封装引擎、设备管理、本地轨道生命周期
- LiveKitEngine — 基于 livekit-client v1.15.13 的引擎实现,提供房间连接、轨道管理等功能
- MediaDeviceManager — 来自
@mm-custom/method,提供设备枚举、热插拔监听等能力
快速开始
安装
npm install @cf-platform/chat前置依赖:
@mm-custom/method >= 2.2.11(peerDependency)
推荐用法(ConferenceManager)
ConferenceManager 封装了引擎创建、客户端初始化、事件注册的完整生命周期,适合大多数场景:
import { ConferenceManager } from '@cf-platform/chat'
import { MediaDeviceManager } from '@mm-custom/method'
const manager = new ConferenceManager({
engine: { url: 'wss://your-server.com' },
client: { deviceManager: new MediaDeviceManager() }
})
// 1. 初始化(创建引擎 + 客户端)
await manager.init()
// 2. 注册事件(按需监听)
const client = manager.getClient()!
client.on('participantJoined', (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)
})
// 3. 加入房间(自动开启音视频)
await manager.join('room-name', 'access-token')
// 4. 离开
await manager.leave()
// 5. 销毁
await manager.destroy()基础用法(ConferenceClient)
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)
}Vue 3 组件示例 — 多人音视频通话
重点:远程轨道的获取、控制及与 DOM 节点的交互。
<template>
<div class="video-call">
<!-- 连接状态 -->
<div class="toolbar">
<span :class="{ on: connected }">{{ connected ? '已连接' : '未连接' }}</span>
<button @click="toggleAudio">{{ audioMuted ? '开麦' : '静音' }}</button>
<button @click="toggleVideo">{{ videoMuted ? '开摄像头' : '关摄像头' }}</button>
<button @click="leave">离开</button>
</div>
<!-- 视频网格 -->
<div class="video-grid">
<!-- 本地预览 -->
<div class="video-cell local">
<video ref="localVideoRef" autoplay playsinline muted />
<span class="label">我</span>
</div>
<!-- 远程参与者 -->
<div v-for="p in participants" :key="p.id" class="video-cell">
<!--
重点:DOM 节点需要预先渲染。id 用于 remoteTrackAdded 时精确绑定。
remoteTrackAdded 事件到达时,通过 document.getElementById 找到对应元素,
然后调用 client.attachTrack(track, element) 将轨道绑定到 DOM。
-->
<video :id="`video-${p.id}`" autoplay playsinline />
<!-- 纯音频参与者的 audio 元素 -->
<audio :id="`audio-${p.id}`" autoplay />
<span class="label">
{{ p.name || p.id }}
<i v-if="p.audioMuted">🔇</i>
<i v-if="p.videoMuted">📷</i>
</span>
</div>
<div v-if="!participants.length" class="empty">等待其他参与者加入…</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onBeforeUnmount } from 'vue'
import { ConferenceManager } from '@cf-platform/chat'
import type { ParticipantInfo, RemoteTrackInfo, TrackMuteInfo, ConnectionState } from '@cf-platform/chat'
const props = defineProps<{ roomName: string; token: string }>()
const manager = new ConferenceManager({
engine: { url: 'wss://your-server.com' }
})
// ── 状态 ──
const connected = ref(false)
const audioMuted = ref(false)
const videoMuted = ref(false)
const localVideoRef = ref<HTMLVideoElement>()
interface ParticipantState {
id: string
name?: string
audio: boolean // 是否有音频轨道
video: boolean // 是否有视频轨道
screen: boolean // 是否有屏幕共享轨道
audioMuted: boolean
videoMuted: boolean
}
const participants = reactive<ParticipantState[]>([])
function findOrCreate(id: string, name?: string): ParticipantState {
let p = participants.find(x => x.id === id)
if (!p) {
p = { id, name, audio: false, video: false, screen: false, audioMuted: false, videoMuted: false }
participants.push(p)
} else if (name) {
p.name = name
}
return p
}
// ── 初始化 & 加入 ──
manager.init()
const client = manager.getClient()!
// 重点1:监听 remoteTrackAdded,将远程轨道绑定到 DOM 节点
client.on('remoteTrackAdded', (track: RemoteTrackInfo) => {
const p = findOrCreate(track.participantId, track.participantName)
// 根据 track.kind 更新轨道存在状态
// 注意:kind 只有 'audio' | 'video' | 'screen',屏幕共享的 kind 也是 'video'(由引擎层映射为 'screen')
if (track.kind === 'audio') p.audio = true
else if (track.kind === 'video') p.video = true
else if (track.kind === 'screen') p.screen = true
// 重点2:DOM 绑定 —— 通过约定的 id 找到对应元素
// video 轨道绑定到 <video id="video-{participantId}">
// audio 轨道绑定到 <audio id="audio-{participantId}">
const prefix = track.kind === 'audio' ? 'audio' : 'video'
const el = document.getElementById(`${prefix}-${track.participantId}`)
if (el) {
client.attachTrack(track.track, el as HTMLMediaElement)
}
})
// 重点3:监听 remoteTrackRemoved,解绑 DOM 并更新状态
client.on('remoteTrackRemoved', (track: RemoteTrackInfo) => {
const p = participants.find(x => x.id === track.participantId)
if (p) {
if (track.kind === 'audio') { p.audio = false; p.audioMuted = false }
else if (track.kind === 'video') { p.video = false; p.videoMuted = false }
else if (track.kind === 'screen') p.screen = false
}
// 解绑 DOM
const prefix = track.kind === 'audio' ? 'audio' : 'video'
const el = document.getElementById(`${prefix}-${track.participantId}`)
if (el) client.detachTrack(el as HTMLMediaElement)
})
// 重点4:监听静音/取消静音,更新 UI 状态
client.on('trackMuted', (info: TrackMuteInfo) => {
const p = participants.find(x => x.id === info.participantId)
if (p) {
if (info.kind === 'audio') p.audioMuted = true
else if (info.kind === 'video') p.videoMuted = true
}
})
client.on('trackUnmuted', (info: TrackMuteInfo) => {
const p = participants.find(x => x.id === info.participantId)
if (p) {
if (info.kind === 'audio') p.audioMuted = false
else if (info.kind === 'video') p.videoMuted = false
}
})
// 参与者进出
client.on('participantJoined', (p: ParticipantInfo) => {
findOrCreate(p.id, p.name)
})
client.on('participantLeft', (p) => {
const idx = participants.findIndex(x => x.id === p.id)
if (idx !== -1) participants.splice(idx, 1)
})
// 连接状态
client.on('connectionStateChanged', (state: ConnectionState) => {
connected.value = state === 'connected'
})
client.on('error', (err) => console.error('[Conference]', err))
// 加入房间
manager.join(props.roomName, props.token).then(() => {
// 本地预览:通过 getLocalVideoTrack() 拿到正在推流的视频轨道并绑定到预览元素
// (返回的是同一轨道,仅用于渲染,不应 stop/修改)
const track = client.getLocalVideoTrack()
if (track && localVideoRef.value) {
localVideoRef.value.srcObject = new MediaStream([track])
}
})
// ── 控制方法 ──
async function toggleAudio() {
if (audioMuted.value) {
await client.unmuteAudio()
} else {
await client.muteAudio()
}
audioMuted.value = !audioMuted.value
}
async function toggleVideo() {
if (videoMuted.value) {
await client.unmuteVideo() // 恢复画面
} else {
await client.muteVideo() // 静画面(保持轨道发布,发送黑屏)
}
videoMuted.value = !videoMuted.value
}
async function leave() {
await manager.destroy()
connected.value = false
participants.splice(0)
}
onBeforeUnmount(() => {
manager.destroy()
})
</script>[!TIP] DOM 绑定的关键模式:
- 模板中预先渲染
<video>/<audio>元素,设置约定好的id(如video-{participantId})remoteTrackAdded事件携带track(MediaStreamTrack)和participantId- 通过
document.getElementById找到对应 DOM 元素- 调用
client.attachTrack(track, element)将轨道渲染到该元素remoteTrackRemoved时调用client.detachTrack(element)解绑- 注意:
track.kind为'screen'时表示屏幕共享轨道(由引擎层将 livekit 的Track.Source.ScreenShare映射),也需要用<video>渲染
API
ConferenceManager
高层门面,封装 LiveKitEngine + ConferenceClient + MediaDeviceManager 的完整生命周期。
配置
interface ConferenceManagerConfig {
engine?: Partial<LiveKitEngineConfig> // 引擎配置(url 必填)
client?: Partial<Omit<ConferenceOptions, 'engine'>> & { deviceManager?: MediaDeviceManager }
}方法
| 方法 | 说明 |
|---|---|
| init(config?) | 初始化引擎和客户端。可传入 config 覆盖/合并构造时的配置 |
| join(roomName, token, opts?) | 加入房间并自动开启音视频。opts 可指定 { audioDeviceId?, videoDeviceId? }。返回 boolean |
| leave() | 离开房间 |
| destroy() | 销毁管理器,释放所有资源 |
| getClient() | 获取底层 ConferenceClient 实例,供外部注册事件监听器 |
[!NOTE]
join/leave/destroy在init()之前调用会安全返回(join 返回 false,leave/destroy 为 no-op)。
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,不停止轨道本身,轨道生命周期由引擎管理) |
本地轨道访问
| 方法 | 说明 |
|---|---|
| getLocalVideoTrack(): MediaStreamTrack \| undefined | 获取本地视频轨道(用于本地预览渲染)。摄像头未开启时为 undefined,返回的是正在推流的同一轨道,仅用于渲染不应停止/修改 |
| getLocalScreenShareTrack(): MediaStreamTrack \| undefined | 获取本地屏幕共享轨道(用于本地预览渲染)。未共享时为 undefined,同上仅用于渲染 |
参与者
| 方法 | 说明 |
|---|---|
| getParticipants(): ParticipantInfo[] | 获取当前房间所有远程参与者 |
状态
| 属性 | 类型 | 说明 |
|---|---|---|
| connectionState | ConnectionState | 当前连接状态 |
| isAudioEnabled | boolean | 麦克风是否已开启 |
| isVideoEnabled | boolean | 摄像头是否已开启 |
| isScreenSharing | boolean | 是否正在屏幕共享 |
| isAudioMuted | boolean | 音频是否被静音 |
| isVideoMuted | boolean | 视频是否被静音 |
元数据(PTT 频道 / 会议高级控制)
| 方法 | 说明 |
|---|---|
| updateLocalMetadata(meta: Record<string, any>): Promise<boolean> | 更新本地参与者的元数据,其他参与者收到 participantMetadataChanged 事件 |
事件
| 事件 | 参数 | 说明 |
|---|---|---|
| connected | room: any | 连接成功(透传) |
| disconnected | reason?: DisconnectReason | 连接断开(透传) |
| connectionStateChanged | state: ConnectionState | 连接状态变更(透传) |
| participantJoined | ParticipantInfo | 参与者加入(透传) |
| participantLeft | { id: string } | 参与者离开(透传) |
| remoteTrackAdded | RemoteTrackInfo | 远程轨道添加(透传) |
| remoteTrackRemoved | RemoteTrackInfo | 远程轨道移除(透传) |
| trackMuted | TrackMuteInfo | 轨道静音(透传) |
| trackUnmuted | TrackMuteInfo | 轨道取消静音(透传) |
| activeSpeakersChanged | ParticipantInfo[] | 活跃发言人变更(透传) |
| participantMetadataChanged | ParticipantMetadataChanged | 参与者元数据变更(含 metadata + oldMetadata) |
| roomMetadataChanged | metadata: any | 房间元数据变更 |
| localAudioChanged | enabled: boolean | 本地音频状态变更(自产) |
| localVideoChanged | enabled: boolean | 本地视频状态变更(自产) |
| 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]
connect、disconnect、publishTrack、unpublishTrack标记为@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
identity?: string
isSpeaking?: boolean
micEnabled?: boolean
cameraEnabled?: boolean
shareEnabled?: boolean
metadata?: any // 自定义元数据(PTT 频道 / 会议控制)
}
// 参与者元数据变更
interface ParticipantMetadataChanged {
participant: ParticipantInfo
oldMetadata?: any
}
// 轨道静音状态
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.2.2
更新日志
v1.2.2 (本地轨道访问 + 类型补全)
- 🆕 新增
getLocalVideoTrack()/getLocalScreenShareTrack()- 暴露本地视频/屏幕共享轨道供消费方做本地预览渲染,避免直接读取私有字段_localVideoTrack/_screenTrack(耦合内部实现,引擎重构即碎) - 🐛
ConferenceEvents补全participantMetadataChanged/roomMetadataChanged-ConferenceClient实际转发这两个事件,但公开事件类型此前漏声明,消费方无类型提示(EngineEvents已有) - 📝
updateLocalMetadataJSDoc 去掉残留@param roomId- 签名已无该参数(实现用合并语义,roomId 由单房间隐含),文档与签名对齐
v1.2.1 (屏幕共享发布 source 修复)
- 🐛 修复屏幕共享发布未指定
Track.Source-startScreenShare原先调publishTrack不传source,屏幕共享轨道的Track.Source退回 LiveKit 自动判定,远端按_mapTrackKind判断kind === 'screen'不稳定。现startScreenShare显式传source: 'screen',引擎层新增_mapTrackSource将会议层'screen'映射为 LiveKitTrack.Source.ScreenShare('screen_share'),音频/摄像头 source 同步改走映射,发布后远端可被稳定识别为屏幕共享 - 🐛 修复
_doPublishTrack的as Track.Source强转 - 会议层TrackSource的'screen'与 LiveKitTrack.Source.ScreenShare('screen_share')字符串不一致,原强转会让非法值透传。改用显式_mapTrackSource映射替代强转
v1.2.0 (新增 ConferenceManager)
- 🆕 新增
ConferenceManager— 高层门面类,封装LiveKitEngine+ConferenceClient+MediaDeviceManager的完整生命周期,将init → join → leave → destroy简化为几个方法调用。支持构造函数 +init()两阶段配置合并(通过deepReplace) - 🆕 类型导出
ConferenceManagerConfig— 配置接口,engine和client均为可选,client的engine字段由管理器内部提供 - 🛡️ 生命周期守卫 —
join/leave/destroy在init()之前调用会安全返回(join 返回 false,其余 no-op),避免_client为 undefined 导致崩溃
v1.1.0 (修复 + 打包对齐)
打包配置(对齐 cim/mq)
- 🔧 统一打包配置 —
vite.config.ts按 cim/mq 模板重写:显式formats: ['es', 'umd']、补rollupOptions.input、sourcemap: false,新增cleanup(去注释)+terser(压缩、mangle_前缀属性)两个 rollup 插件 - 🔧 dts 配置补齐 —
dts插件补include/exclude/insertTypesEntry/removeComments,与 cim/mq 一致 - ⚠️ 产物文件名变更 —
chat.lib.es.js/chat.lib.umd.js→chat.es.js/chat.umd.js(与 cim/mq 的<pkg>.<format>.js命名统一)。package.json的main/module/exports同步更新,按包名引入的消费方不受影响;files补README.md
逻辑修复
- 🐛 修复
unpublishTrack永远无法取消发布 — 原先用trackInfo.track.id(MediaStreamTrack 的 UUID)当作Track.Source枚举调getTrack,永远返回undefined,导致本地轨道在服务端永不取消发布。改为直接用MediaStreamTrack调LocalParticipant.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 存为字段,stopScreenShare中removeEventListener(原先用匿名函数无法解绑,重复 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
