基于 LiveKit 实现群聊视频通话:从选人振铃到部分超时的完整实践 适合读者已了解 WebRTC / IM或读过「一对一 LiveKit」实践准备做多方群通话的前后端同学。技术栈示例Spring Boot Redis IMuni-appH5 / App LiveKit ClientSFU。一、写在前面一对一通话可以勉强用「两端连一下」理解群通话则必须回答这些问题多人同时振铃谁接了、谁拒了、谁超时部分人已进房时未接听的人超时了整场要不要拆掉某人挂断时若还有人在振铃邀请人要不要继续等App / H5 如何共用同一套会话与房间本文基于真实业务代码介绍如何用 IM 群信令 /webrtc/group/*REST 管会话状态用 LiveKit SFU 管媒体落地完整的群聊视频通话。与一对一的核心差异维度一对一群聊会话 Keycaller:callee双向兼容callerId:groupId以邀请人为准发起信令RTC_CALL_*RTC_GROUP_SETUP进房时机常等 ACCEPT 再进邀请人 call 后即可进房被叫 accept 后进超时主叫 30s failed邀请人前端 30s 服务端定时扫描 inviteAt结束判定任一方挂断即结束有人振铃则不结束需isEndCall二、总体架构原则不变信令决定“谁该在通话里”LiveKit 只负责“在房间里怎么传音视频”。三、会话模型以邀请人为中心3.1 Redis KeyTTL 默认 24hheartbeat续期。所有后续接口都带inviterId用它定位会话。3.2 会话关键字段字段含义callerId/callerTerminal邀请人webrtcUser已进房成员列表含邀请人acceptorIds当前仍有效的被叫拒接/挂断/超时会收缩copyAcceptorIds发起时被叫快照写聊天记录用不收缩roomNameLiveKit 房间名inviteAt发起时间戳毫秒服务端 30s 超时依据mode/mediaMode如video/sfu3.3 房间命名同群多次发起不会撞房。3.4 两个核心集合邀请人在call时已写入webrtcUser且不在acceptorIds因此邀请人永远不算“振铃中”。这是理解「有人挂断是否结束整场」的前提。四、信令消息类型群聊Code常量典型方向content200RTC_GROUP_SETUP邀请人 → 被叫{姓名}邀请你群视频通话201RTC_GROUP_ACCEPT进房者 → 已进房成员逗号分隔已进房 userId对自己端可sendToSelf关其他端振铃202RTC_GROUP_REJECT拒接者 → 邀请人true/false是否整场结束203RTC_GROUP_FAILED见超时逻辑未接者null已进房partial JSON204RTC_GROUP_CANCEL邀请人挂断 → 仍振铃者取消振铃205RTC_GROUP_QUIT挂断者 → 剩余已进房true/false endCall212RTC_CHANGE_GROUP_VIDEO_AUDIO操作者 → 其他已进房{changeType:1|2,video|audio:bool}通话结束后落库展示ACT_RT_VIDEO/VOICE文案如「未接通」「通话结束」。五、REST API 一览前缀/api/v1/webrtc/group接口作用POST /call建会话、邀请人进房、推 SETUP、返回 LiveKit 凭证POST /accept被叫进会话、推 ACCEPT、返回凭证POST /token按会话 roomName 重签 JWTPOST /reject拒接POST /failed邀请超时全员未接 / 部分未接POST /handup挂断邀请人走 cancel被叫走 QUITPOST /heartbeat续会话 忙线POST /findUserInSession已进房成员含音视频状态POST /findGroupMember按 id 查展示信息POST /joinGroupUser振铃页「还有谁」排除邀请人与自己POST /changeVideoAudio同步麦/摄像头状态忙线im:user:state:{userId}TTL 约 60s前端约 20s 心跳续期。六、前端页面流转6.1 邀请人发起群聊页点视频 → 校验权限 → 选人组件校验至少 1 个被叫总人数建议 ≤ 9redirectTo媒体页媒体页过滤掉邀请人调用/call拿凭证进 LiveKit启动 30s 邀请超时定时器有人接听后不清除用于踢掉仍未接听者启动心跳6.2 被叫振铃App.vue收到RTC_GROUP_SETUP自己发的忽略joinGroupUser拉「参与通话的还有」仅邀请人自己时不展示该区块拒接 →/reject接听 → 进媒体页isCallerfalse再/accept/token6.3 平台分流平台媒体实现H5gvideocallh5.vuecommon/livekit/room.jsAppgvideocall.vue桥接hybrid/html/gvideocall.htmlLiveKit CDNApp 信令桥App.vue→uni.$emit(*App)→ 壳页evalJS→ HTML 全局函数。七、最重要的业务规则isEndCall伪代码若 listRingingUsers 非空 → 不结束否则若 已进房成员里除邀请人外无人 → 结束否则 → 不结束对应产品场景场景结果李翔已进房后挂断夏雨仍在振铃不结束张三继续等夏雨超时被踢掉后李翔再挂断且房里只剩张三结束通知张三离开张三邀请人挂断cancel已进房收 QUIT(true)振铃中收 CANCEL八、30 秒超时前端 服务端双保险8.1 前端邀请人注意onHide不要清掉这个定时器切后台仍要超时真正离开通话页再卸监听。8.2 后端failed情况行为除邀请人外无人进房通知全部被叫 FAILED删会话落库「未接通」endCalltrue部分人未进房未接者 FAILED(null 关振铃)已进房成员 FAILED(partial JSON 同步 UI)收缩acceptorIdsendCallfalse全员已进房无操作partial content 示例已进房端含非邀请人必须监听 FAILED否则会出现「邀请人已去掉夏雨李翔界面还在振铃占位」的不同步。8.3 服务端定时任务约每 10s 扫描会话inviteAt超 30s 且仍有振铃 → 以邀请人身份执行failed。覆盖邀请人进程被杀、前端定时器未触发等场景。九、完整时序假设邀请人 A被叫 B、CB 接听C 超时未接。「B 挂断但 C 仍振铃」时QUIT的 content 为falseA 不退出。十、H5 媒体进房要点10.1 进房封装与一对一共用connectLiveKitRoom10.2 音画分离群聊必做多人宫格若把音轨混进同一个MediaStream再塞给video且未静音移动浏览器极易拦自动播放 → 黑屏 原生控件。推荐预览MediaStream只含视频轨画面元素 始终 muted远端音频用track.attach()挂隐藏audio10.3 宫格 UI本地 1 格 远端 N 格布局1 人全宽 / ≤4 人 50% / 更多 33%状态等待接入 / 已进房关摄像头 / 开摄像头画面 静音角标10.4 离开页面防串扰媒体页若用navigateTo回聊天往往只有onHide没有onUnload监听器还在稍后别人挂断会再弹「通话已结束」。建议挂断时立刻卸掉uni.$on回聊天用redirectTo卸掉通话页hanging后忽略后续信令十一、App壳页 Hybrid HTML壳页注入事件示例uni 事件HTML 全局函数userJoinApphandleUserJoinhandleGroupRejectApphandleGroupRejecthandleGroupHandupApphandleGroupHanduphandleGroupCallFailedApphandleGroupCallFailed页面卸载handlePageUnloadCDNlivekit-client版本与 H5 npm 对齐如 2.21.x。十二、后端关键逻辑摘要12.1call校验 LiveKit 已启用写会话acceptorIds、copyAcceptorIds、inviteAt、roomName邀请人addUsersetBusyACCEPT 同步邀请人本端SETUP 推被叫返回邀请人凭证12.2accept被叫写入webrtcUsersetBusy向每个已进房用户推 ACCEPT带完整进房 id 列表12.3handup邀请人 →cancelQUIT true CANCEL 删会话被叫 → 移出自身 →isEndCall决定 QUIT content → 必要时删会话12.4heartbeat续会话 24h续/重建已进房成员忙线。十三、和一对一对照迁移时怎么复用可复用需新建LiveKit Token 签发方式群会话结构与inviterId维度connectLiveKitRoomSETUP / 振铃页 / 部分超时音画分离与自动播放策略isEndCall/listRingingUsers忙线 TTL 心跳思路服务端 inviteAt 扫描任务建议顺序先跑通一对一 token 进房再上群会话与振铃矩阵最后补部分超时与挂断不误杀。十四、踩坑清单有人接听就清 30s 定时器 → 未接听者永远超时不了部分超时只通知未接者 → 已进房其他人 UI 不同步LiveKit 离开直接当整场结束 → 误弹「通话已结束」endCalltrue时邀请人本地还有振铃占位仍强制退出 → 刚发起就结束需前端防护 后端判定正确回聊天不卸监听 → 过一会又 toastuni-video MediaStream → 黑屏改用原生 video /track.attachSDK 版本 App CDN ≠ H5 npm → 双端行为漂移十五、总结群聊 LiveKit 视频通话可以收敛成四句话会话以邀请人为 key被叫一律带inviterId访问。振铃 acceptorIds − 已进房结束条件看「还有没有振铃 还有没有其他已进房被叫」。30s 超时双保险邀请人前端 服务端inviteAt部分未接不拆房。媒体进房与一对一同源Room Track差异主要在信令状态机与宫格 UI。把「业务状态」留在 IM/REST「媒体转发」交给 SFU群通话才能从 Demo 变成可维护的产品能力。