跳转到内容

客服通话

客服通话插件(Customer Service)为 Monibuca V6 提供 1v1 实时音视频客服 能力。适用于在线客服、远程咨询、售后支持等场景。

  • 1v1 通话:每个会话固定 2 人(客服 + 用户),基于 Room 服务的 max_users: 2 限制
  • 免登录用户端:用户无需注册账号,通过链接直接进入通话
  • 客服坐席管理:五态状态机(Offline/Online/Ready/Ringing/Busy),支持签入签出、就绪示忙
  • 排队分配系统:访客排队 → FIFO 自动分配 → 坐席接听的完整工作台闭环
  • 来电振铃:坐席收到来电通知,30 秒倒计时,支持音频/视频接听或拒接
  • 通话转接:坐席可将通话转接给其他就绪坐席,用户不断线
  • 满意度评价:通话结束后用户可进行 1-5 星评价,后台可查看评分统计
  • 音频混音:客服端可将本地音频文件混入推流(如背景音乐、提示音)
  • Admin 管理后台:完整的会话管理界面,支持创建会话、生成用户链接/二维码
Cargo.toml
features = ["customer-service"]
# 客服通话依赖 Room 服务
# room feature 会自动启用
graph TB
subgraph CS["CustomerService Plugin"]
SM["SessionManager<br/>会话管理(DashMap + SQLite)"]
AP["AgentPool<br/>坐席池(Round-Robin)"]
VQ["VisitorQueue<br/>排队队列(FIFO)"]
RM["RingManager<br/>振铃管理"]
HTTP["HTTP API<br/>/customer-service/*"]
Signal["WebSocket 信令<br/>AcceptCall / EndCall / Transfer"]
end
subgraph RS["Room Service (引擎内置)"]
Room["房间管理<br/>max_users: 2"]
WS["WebSocket<br/>信令通信"]
end
subgraph Client["客户端"]
Agent["客服端<br/>账号登录"]
Customer["用户端<br/>免登录"]
end
CS -->|"register_callbacks<br/>RoomType::CustomerService"| RS
HTTP --> SM
HTTP --> AP
HTTP --> VQ
Agent -->|"WebRTC (WHIP)"| Room
Customer -->|"WebRTC (WHIP)"| Room
Agent <-->|"WebSocket"| WS
Customer <-->|"WebSocket"| WS
Waiting → Active → Ended
状态说明
Waiting会话已创建,等待客服/用户加入
Active双方均已加入,通话进行中
Ended通话已结束
Offline → Online → Ready → Ringing → Busy
↑ │
└──────────────────┘
状态说明
Offline未登录/已签出
Online已签入但未就绪(休息/小结中)
Ready就绪,等待分配来电(调度目标)
Ringing来电已推送,等待接听/拒接(默认 30 秒超时)
Busy通话中

状态转换

  • 坐席注册 → Ready
  • 分配来电 → Ringing(30s 超时自动返回 Ready,访客重新入队)
  • 接听 → Busy
  • 通话结束 → Ready
  • 拒接 → Ready(访客重新入队)
  • 下线 → Offline
  • FIFO 队列:访客按进入顺序排队,最大队列长度可配(默认 200)
  • 轮询分配:后台每 500ms 检查队列,使用 Round-Robin 算法选取就绪坐席
  • 两阶段分配:出队 → 坐席进入 Ringing → 接听后正式建立通话
  • 即时分配:访客加入时若有空闲坐席可跳过排队直接分配
  • 超时清理:排队超时(默认 10 分钟)自动移除

所有接口前缀为 /customer-service/

方法路径说明
GET/sessions获取会话列表
GET/sessions/{id}获取会话详情
POST/sessions创建新会话
DELETE/sessions/{id}关闭/删除会话
POST/sessions/{id}/assign手动分配客服坐席
POST/sessions/{id}/transfer转接通话到其他坐席
POST/sessions/{id}/rate提交满意度评价
GET/sessions/{id}/rating查询会话评价
方法路径说明
POST/agents/register注册坐席(上线就绪)
POST/agents/{id}/offline坐席下线
POST/agents/{id}/available坐席设为就绪
POST/agents/{id}/accept接听来电
POST/agents/{id}/reject拒接来电
GET/agents获取所有坐席列表
GET/agents/available获取就绪坐席列表
GET/agents/stats坐席池统计
方法路径说明
POST/queue/join访客加入排队(有空闲坐席时直接分配)
POST/queue/leave访客离开排队
GET/queue/status队列状态快照
GET/queue/position/{visitor_id}查询排队位置
方法路径说明
GET/stats综合统计(会话 + 坐席 + 队列 + 评分)
GET/stats/ratings评分统计(总数、均值、分布)
GET/stats/trend?days=7每日会话趋势

默认基地址:http://localhost:8180

POST /customer-service/sessions
Content-Type: application/json

请求体:

{
"title": "售后咨询",
"customer_id": "cust_001",
"customer_name": "张三"
}
字段类型必填说明
titlestring会话标题
customer_idstring用户 ID
customer_namestring用户展示名
GET /customer-service/sessions

响应字段:

字段类型说明
sessionsarray会话列表
countint会话总数
GET /customer-service/sessions/{id}
参数类型必填说明
idstring会话 ID

响应(示例):

{
"session_id": "cs_abc123",
"status": "waiting",
"created_at": "2026-05-07T19:05:00+08:00"
}
POST /customer-service/agents/register
Content-Type: application/json

请求体:

{
"agent_id": "agent_001",
"agent_name": "客服小王"
}
GET /customer-service/agents
GET /customer-service/agents/available
GET /customer-service/agents/stats
POST /customer-service/queue/join
Content-Type: application/json
字段类型必填说明
visitor_idstring访客 ID
visitor_namestring访客名称
preferred_skillstring指定技能组(可选)

排队响应可能是 assigned(直接分配)或 queued(进入队列)。

GET /customer-service/queue/status
GET /customer-service/queue/position/{visitor_id}
参数类型必填说明
visitor_idstring条件必填访客 ID(位置查询时必填)
POST /customer-service/sessions/{id}/transfer
Content-Type: application/json
字段类型必填说明
target_agent_idstring目标坐席 ID
POST /customer-service/sessions/{id}/rate
Content-Type: application/json

请求体:

{
"rating": 5,
"comment": "服务很好"
}
字段类型必填说明
ratingint1-5 分
commentstring文本反馈
GET /customer-service/stats
GET /customer-service/stats/ratings
GET /customer-service/stats/trend?days=7
参数类型必填说明
daysint趋势天数,默认 7
HTTP 状态码场景
400参数缺失或格式错误
404会话/坐席不存在
409状态冲突(如坐席非 Ready)
429队列超限或请求过频

详细字段和状态码以“详细入参与返回格式”章节为准。
最小示例:

Terminal window
curl -X POST http://localhost:8180/customer-service/sessions \
-H "Content-Type: application/json" \
-d '{"title":"售后咨询","customer_id":"cust_001"}'

客服通话使用专用的 WebSocket 信令,通过 Room 服务的 on_message 回调透传到插件处理。

Action方向说明
accept_callC→S客服接受通话
reject_callC→S客服拒接来电
end_callC→S任一方挂断通话
transfer_callC→S客服转接至其他坐席
mute_userC→S静音指定参与者
unmute_userC→S取消静音
incoming_callS→C来电通知(含 visitor_name, timeout_secs)
ring_timeoutS→C来电超时通知
call_acceptedS→C通知对方已接听
call_endedS→C通知通话已结束
call_transferredS→C通知通话已转接
transfer_failedS→C转接失败通知
agent_assignmentS→C坐席分配通知
{
"type": "control",
"data": {
"type": "AcceptCall"
}
}
{
"type": "control",
"data": {
"type": "TransferCall",
"data": {
"target_agent_id": "agent_002"
}
}
}
customer_service:
max_sessions: 1000 # 最大并发会话数
session_timeout_secs: 3600 # 会话超时时间(秒),默认 1 小时
max_queue_size: 200 # 最大排队人数
dispatch_interval_ms: 500 # 自动分配轮询间隔(毫秒)
queue_timeout_secs: 600 # 排队超时时间(秒),默认 10 分钟
accept_timeout_secs: 30 # 来电接听超时时间(秒),默认 30 秒
auto_record: false # 是否启用通话录制
recording_dir: "/tmp/cs-recordings" # 录制文件目录

客户端通过标准的 Room WebSocket 连接加入客服通话:

ws://host:port/room?type=customer_service

房间 ID 格式为 customer-service/{session_id},通过前缀区分不同的房间类型。


客服通话 Demo 基于 Monibuca Web-SDK 的 Web Components 构建:

<!-- 房间容器 -->
<mb-room id="room"></mb-room>
<!-- 本地推流 -->
<mb-publisher id="publisher"></mb-publisher>
<!-- 远端订阅 -->
<mb-subscriber id="subscriber"></mb-subscriber>
const room = document.getElementById('room') as MbRoom;
const publisher = document.getElementById('publisher') as MbPublisher;
// 设置 WebSocket 连接
room.setAttribute('ws-url', 'ws://localhost:8180/room?type=customer_service');
room.setAttribute('room-id', 'customer-service/session_001');
// 开始推流
await publisher.startCapture({ audio: true, video: true });
await publisher.startPublish();

客服端支持将本地音频文件混入推流,例如背景音乐、等待提示音:

const publisher = document.getElementById('publisher') as MbPublisher;
// 混入本地音频文件
publisher.mixAudio('bgm', '/audio/background-music.mp3', 0.3);
// 调整混音音量(0.0 ~ 1.0)
publisher.setMixVolume('bgm', 0.5);
// 移除混音源
publisher.unmixAudio('bgm');

混音原理

graph LR
Mic["麦克风<br/>MediaStream"] --> GainA["GainNode<br/>麦克风音量"]
BGM["音频文件<br/>MediaElement"] --> GainB["GainNode<br/>混音音量"]
GainA --> Dest["Destination<br/>合成输出"]
GainB --> Dest
Dest --> WHIP["WHIPClient<br/>WebRTC 推流"]

SDK 内部使用 Web Audio API 的 AudioContext 将多个音源(麦克风 + 混音文件)通过 GainNode 混合到同一个 MediaStreamAudioDestinationNode,最终输出到 WebRTC 推流。

推荐使用状态机管理通话流程:

type CallStage = 'waiting' | 'connecting' | 'connected' | 'ended';
// waiting → 等待对方加入(显示等待界面、脉冲动画)
// connecting → WebRTC 连接建立中
// connected → 通话进行中(显示视频画面、计时器、控制栏)
// ended → 通话结束(显示通话时长、满意度评价)

客服通话在 Admin 后台提供完整的管理功能。

  • 自动就绪:坐席进入工作台后自动注册为就绪状态
  • 实时仪表盘:排队人数、就绪坐席数、通话中坐席数实时展示
  • 自动分配:系统自动分配访客,坐席无需手动操作即可进入通话
  • 手动入口:保留手动输入会话 ID 的备用入口
  • 通话控制:麦克风/摄像头开关、屏幕共享
  • 通话转接:通话中点击”转接”按钮,选择就绪坐席执行转接
  • 文字聊天:通话中的实时文字聊天面板
  • 通话统计:码率、丢包率、RTT 实时展示
  • 统计卡片:总会话数、通话中、等待接入
  • 会话列表:支持状态筛选、搜索、批量操作、评价星级展示
  • 创建会话:填写标题和描述,自动分配坐席
  • 分配坐席:手动将会话分配给指定客服
  • 生成链接:为用户/客服生成专属入口链接和二维码
  • 平均评分:Dashboard 展示所有会话的平均满意度评分
  • 评分分布:1-5 星的分布柱状图
  • 会话列表评价列:每个已结束会话显示用户评价星级

客服通话的房间数据通过 Room 服务的上报系统采集,在 Admin 中按 customer-service/ 前缀自动筛选展示。


完整的客服通话 Demo 位于 web-sdk/packages/demo/customer-service/

customer-service/
├── src/
│ ├── pages/
│ │ ├── Lobby/index.tsx # 入口:角色选择
│ │ ├── CallRoom/index.tsx # 通话:1v1 视频、控制栏、聊天、评价
│ │ ├── AgentConsole/index.tsx # 坐席工作台:排队监控、来电弹窗、倒计时
│ │ └── QueueWaiting/index.tsx # 用户排队等待页
│ ├── services/api.ts # API 封装
│ └── types/index.ts # 类型定义
├── package.json # port: 5476
└── vite.config.ts
Terminal window
cd web-sdk/packages/demo/customer-service
pnpm install
pnpm dev
# 访问 http://localhost:5476

用户通过带参数的链接直接进入通话,无需登录:

http://localhost:5476?session=cs_abc123&role=customer

客服需要账号密码登录后进入坐席工作台:

http://localhost:5476?session=cs_abc123&role=agent

坐席登录后进入 AgentConsole 页面:

  • 点击”上线”注册为就绪状态
  • 实时显示排队人数和在线坐席数
  • 收到来电时弹出全屏振铃面板(30 秒倒计时)
  • 支持”视频接听”、“语音接听”、“拒接”三个操作
  • 接听后自动跳转到 CallRoom 通话页面

Demo 自动检测设备类型,移动端采用:

  • 上下分屏视频布局
  • 底部固定控制栏(适配安全区域)
  • 触摸优化的交互

联系我们

微信公众号:不卡科技 微信公众号二维码
腾讯频道:流媒体技术 腾讯频道二维码
QQ 频道:p0qq0crz08 QQ 频道二维码
QQ 群:751639168 QQ 群二维码