574 lines
12 KiB
Markdown
574 lines
12 KiB
Markdown
|
|
# 语音/视频通话模块说明
|
|||
|
|
|
|||
|
|
## 📋 模块概述
|
|||
|
|
|
|||
|
|
本模块实现了基于 WebRTC 的一对一语音/视频通话功能,支持通话邀请、接听、拒绝、取消、结束等完整流程,并提供通话记录管理功能。
|
|||
|
|
|
|||
|
|
**实现时间**: 已完成
|
|||
|
|
**技术方案**: WebRTC + WebSocket 信令交换
|
|||
|
|
**完成度**: 100%
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🏗️ 架构设计
|
|||
|
|
|
|||
|
|
### 核心组件
|
|||
|
|
|
|||
|
|
1. **CallRecord** - 通话记录实体类
|
|||
|
|
- 路径: `crmeb-common/src/main/java/com/zbkj/common/model/call/CallRecord.java`
|
|||
|
|
- 功能: 存储通话记录信息(通话双方、类型、状态、时长等)
|
|||
|
|
|
|||
|
|
2. **CallService** - 通话业务逻辑服务
|
|||
|
|
- 路径: `crmeb-service/src/main/java/com/zbkj/service/service/impl/CallServiceImpl.java`
|
|||
|
|
- 功能: 处理通话创建、接听、拒绝、取消、结束等业务逻辑
|
|||
|
|
|
|||
|
|
3. **CallController** - 通话接口控制器
|
|||
|
|
- 路径: `crmeb-front/src/main/java/com/zbkj/front/controller/CallController.java`
|
|||
|
|
- 功能: 提供 RESTful API 接口
|
|||
|
|
|
|||
|
|
4. **CallSignalingHandler** - WebRTC 信令处理器
|
|||
|
|
- 路径: `crmeb-front/src/main/java/com/zbkj/front/websocket/CallSignalingHandler.java`
|
|||
|
|
- 功能: 处理 WebSocket 信令交换(offer、answer、ice-candidate)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 数据库设计
|
|||
|
|
|
|||
|
|
### 表名: `eb_call_record`
|
|||
|
|
|
|||
|
|
| 字段名 | 类型 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| id | BIGINT | 主键ID |
|
|||
|
|
| call_id | VARCHAR(64) | 通话唯一标识(用于信令) |
|
|||
|
|
| caller_id | INT | 主叫用户ID |
|
|||
|
|
| callee_id | INT | 被叫用户ID |
|
|||
|
|
| call_type | VARCHAR(20) | 通话类型: voice-语音, video-视频 |
|
|||
|
|
| status | VARCHAR(20) | 通话状态 |
|
|||
|
|
| call_time | DATETIME | 通话发起时间 |
|
|||
|
|
| connect_time | DATETIME | 通话接通时间 |
|
|||
|
|
| end_time | DATETIME | 通话结束时间 |
|
|||
|
|
| duration | INT | 通话时长(秒) |
|
|||
|
|
| end_reason | VARCHAR(50) | 结束原因 |
|
|||
|
|
| caller_deleted | TINYINT(1) | 主叫是否删除记录 |
|
|||
|
|
| callee_deleted | TINYINT(1) | 被叫是否删除记录 |
|
|||
|
|
| create_time | DATETIME | 创建时间 |
|
|||
|
|
| update_time | DATETIME | 更新时间 |
|
|||
|
|
|
|||
|
|
### 通话状态说明
|
|||
|
|
|
|||
|
|
- `calling` - 呼叫中(主叫发起,等待被叫响应)
|
|||
|
|
- `ringing` - 响铃中(被叫收到邀请)
|
|||
|
|
- `connected` - 通话中(双方已接通)
|
|||
|
|
- `ended` - 已结束(正常结束)
|
|||
|
|
- `missed` - 未接(超时未接听)
|
|||
|
|
- `rejected` - 已拒绝(被叫拒绝)
|
|||
|
|
- `cancelled` - 已取消(主叫取消)
|
|||
|
|
- `busy` - 忙线(对方正在通话中)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔌 API 接口
|
|||
|
|
|
|||
|
|
### 1. 发起通话
|
|||
|
|
```http
|
|||
|
|
POST /api/front/call/initiate
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"calleeId": 123,
|
|||
|
|
"callType": "video" // voice 或 video
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"msg": "操作成功",
|
|||
|
|
"data": {
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"callType": "video",
|
|||
|
|
"calleeId": 123,
|
|||
|
|
"calleeName": "用户昵称",
|
|||
|
|
"calleeAvatar": "头像URL",
|
|||
|
|
"status": "calling",
|
|||
|
|
"signalingUrl": "/ws/call/call_abc123..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 接听通话
|
|||
|
|
```http
|
|||
|
|
POST /api/front/call/accept/{callId}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 拒绝通话
|
|||
|
|
```http
|
|||
|
|
POST /api/front/call/reject/{callId}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 取消通话
|
|||
|
|
```http
|
|||
|
|
POST /api/front/call/cancel/{callId}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 结束通话
|
|||
|
|
```http
|
|||
|
|
POST /api/front/call/end/{callId}?endReason=normal
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. 获取通话记录
|
|||
|
|
```http
|
|||
|
|
GET /api/front/call/history?page=1&limit=20
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7. 删除通话记录
|
|||
|
|
```http
|
|||
|
|
DELETE /api/front/call/record/{recordId}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8. 获取未接来电数量
|
|||
|
|
```http
|
|||
|
|
GET /api/front/call/missed/count
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9. 检查通话状态
|
|||
|
|
```http
|
|||
|
|
GET /api/front/call/status
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 10. 获取通话详情
|
|||
|
|
```http
|
|||
|
|
GET /api/front/call/detail/{callId}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔄 WebSocket 信令协议
|
|||
|
|
|
|||
|
|
### 连接地址
|
|||
|
|
```
|
|||
|
|
ws://your-domain/ws/call
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 客户端发送消息类型
|
|||
|
|
|
|||
|
|
#### 1. 注册用户
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "register",
|
|||
|
|
"userId": 123
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 发起通话请求
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_request",
|
|||
|
|
"calleeId": 456,
|
|||
|
|
"callType": "video"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 接听通话
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_accept",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. 拒绝通话
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_reject",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 5. 取消通话
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_cancel",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 6. 结束通话
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_end",
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"reason": "normal"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 7. WebRTC Offer
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "offer",
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"sdp": {
|
|||
|
|
"type": "offer",
|
|||
|
|
"sdp": "v=0\r\no=..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 8. WebRTC Answer
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "answer",
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"sdp": {
|
|||
|
|
"type": "answer",
|
|||
|
|
"sdp": "v=0\r\no=..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 9. ICE Candidate
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "ice-candidate",
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"candidate": {
|
|||
|
|
"candidate": "candidate:...",
|
|||
|
|
"sdpMLineIndex": 0,
|
|||
|
|
"sdpMid": "0"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 服务端推送消息类型
|
|||
|
|
|
|||
|
|
#### 1. 注册成功
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "registered",
|
|||
|
|
"userId": 123
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 来电通知
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "incoming_call",
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"callerId": 123,
|
|||
|
|
"callerName": "用户昵称",
|
|||
|
|
"callerAvatar": "头像URL",
|
|||
|
|
"callType": "video"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 通话已创建
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_created",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. 对方已接听
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_accepted",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 5. 对方已拒绝
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_rejected",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 6. 对方已取消
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_cancelled",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 7. 通话已结束
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_ended",
|
|||
|
|
"callId": "call_abc123...",
|
|||
|
|
"reason": "normal"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 8. 通话超时
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "call_timeout",
|
|||
|
|
"callId": "call_abc123..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 9. 错误消息
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "error",
|
|||
|
|
"message": "错误描述"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 通话流程
|
|||
|
|
|
|||
|
|
### 语音/视频通话完整流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
主叫方 服务器 被叫方
|
|||
|
|
| | |
|
|||
|
|
|--1. 发起通话----------->| |
|
|||
|
|
| POST /call/initiate | |
|
|||
|
|
| | |
|
|||
|
|
|<--返回callId-----------| |
|
|||
|
|
| | |
|
|||
|
|
|--2. 连接WebSocket----->| |
|
|||
|
|
| register | |
|
|||
|
|
| | |
|
|||
|
|
| |--3. 推送来电通知------->|
|
|||
|
|
| | incoming_call |
|
|||
|
|
| | |
|
|||
|
|
| |<--4. 接听通话----------|
|
|||
|
|
| | call_accept |
|
|||
|
|
| | |
|
|||
|
|
|<--5. 通知已接听--------|--5. 通知已接听-------->|
|
|||
|
|
| call_accepted | call_accepted |
|
|||
|
|
| | |
|
|||
|
|
|--6. 发送Offer--------->|--6. 转发Offer--------->|
|
|||
|
|
| | |
|
|||
|
|
|<--7. 接收Answer--------|<--7. 发送Answer--------|
|
|||
|
|
| | |
|
|||
|
|
|--8. 交换ICE候选------->|--8. 转发ICE候选------->|
|
|||
|
|
|<-----------------------|<-----------------------|
|
|||
|
|
| | |
|
|||
|
|
|========== WebRTC P2P 通话建立 =================|
|
|||
|
|
| | |
|
|||
|
|
|--9. 结束通话---------->| |
|
|||
|
|
| call_end | |
|
|||
|
|
| |--9. 通知结束---------->|
|
|||
|
|
| | call_ended |
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚙️ 核心功能特性
|
|||
|
|
|
|||
|
|
### 1. 通话管理
|
|||
|
|
- ✅ 发起语音/视频通话
|
|||
|
|
- ✅ 接听/拒绝/取消通话
|
|||
|
|
- ✅ 通话中状态检测
|
|||
|
|
- ✅ 通话时长统计
|
|||
|
|
- ✅ 通话记录保存
|
|||
|
|
|
|||
|
|
### 2. 状态管理
|
|||
|
|
- ✅ 用户在线状态检测
|
|||
|
|
- ✅ 通话状态实时同步
|
|||
|
|
- ✅ 忙线检测(防止重复呼叫)
|
|||
|
|
- ✅ 超时自动挂断(60秒)
|
|||
|
|
|
|||
|
|
### 3. 信令交换
|
|||
|
|
- ✅ WebSocket 长连接
|
|||
|
|
- ✅ SDP Offer/Answer 交换
|
|||
|
|
- ✅ ICE Candidate 交换
|
|||
|
|
- ✅ 信令自动转发
|
|||
|
|
|
|||
|
|
### 4. 通话记录
|
|||
|
|
- ✅ 通话历史查询
|
|||
|
|
- ✅ 未接来电统计
|
|||
|
|
- ✅ 双向软删除
|
|||
|
|
- ✅ 通话详情查看
|
|||
|
|
|
|||
|
|
### 5. 异常处理
|
|||
|
|
- ✅ 网络断开自动结束
|
|||
|
|
- ✅ 超时自动挂断
|
|||
|
|
- ✅ 错误消息推送
|
|||
|
|
- ✅ 状态一致性保证
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔒 安全特性
|
|||
|
|
|
|||
|
|
1. **权限验证**: 所有接口需要登录认证
|
|||
|
|
2. **操作权限**: 只能操作自己参与的通话
|
|||
|
|
3. **状态校验**: 严格的状态机转换验证
|
|||
|
|
4. **防重复呼叫**: 检测用户是否正在通话中
|
|||
|
|
5. **超时保护**: 60秒超时自动结束未接通的通话
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📱 客户端集成指南
|
|||
|
|
|
|||
|
|
### 1. 发起通话
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 1. 调用 API 发起通话
|
|||
|
|
const response = await fetch('/api/front/call/initiate', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
calleeId: 456,
|
|||
|
|
callType: 'video'
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
const { callId, signalingUrl } = await response.json();
|
|||
|
|
|
|||
|
|
// 2. 连接 WebSocket
|
|||
|
|
const ws = new WebSocket('ws://your-domain/ws/call');
|
|||
|
|
|
|||
|
|
// 3. 注册用户
|
|||
|
|
ws.send(JSON.stringify({
|
|||
|
|
type: 'register',
|
|||
|
|
userId: currentUserId
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
// 4. 创建 WebRTC 连接
|
|||
|
|
const pc = new RTCPeerConnection(config);
|
|||
|
|
|
|||
|
|
// 5. 添加本地媒体流
|
|||
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|||
|
|
video: callType === 'video',
|
|||
|
|
audio: true
|
|||
|
|
});
|
|||
|
|
stream.getTracks().forEach(track => pc.addTrack(track, stream));
|
|||
|
|
|
|||
|
|
// 6. 创建并发送 Offer
|
|||
|
|
const offer = await pc.createOffer();
|
|||
|
|
await pc.setLocalDescription(offer);
|
|||
|
|
ws.send(JSON.stringify({
|
|||
|
|
type: 'offer',
|
|||
|
|
callId: callId,
|
|||
|
|
sdp: offer
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
// 7. 监听 ICE Candidate
|
|||
|
|
pc.onicecandidate = (event) => {
|
|||
|
|
if (event.candidate) {
|
|||
|
|
ws.send(JSON.stringify({
|
|||
|
|
type: 'ice-candidate',
|
|||
|
|
callId: callId,
|
|||
|
|
candidate: event.candidate
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 8. 接收远程流
|
|||
|
|
pc.ontrack = (event) => {
|
|||
|
|
remoteVideo.srcObject = event.streams[0];
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 接听通话
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 1. 收到来电通知
|
|||
|
|
ws.onmessage = async (event) => {
|
|||
|
|
const msg = JSON.parse(event.data);
|
|||
|
|
|
|||
|
|
if (msg.type === 'incoming_call') {
|
|||
|
|
// 显示来电界面
|
|||
|
|
showIncomingCallUI(msg);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 2. 用户点击接听
|
|||
|
|
async function acceptCall(callId) {
|
|||
|
|
// 调用接听 API
|
|||
|
|
await fetch(`/api/front/call/accept/${callId}`, {
|
|||
|
|
method: 'POST'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 创建 WebRTC 连接
|
|||
|
|
const pc = new RTCPeerConnection(config);
|
|||
|
|
|
|||
|
|
// 添加本地媒体流
|
|||
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|||
|
|
video: true,
|
|||
|
|
audio: true
|
|||
|
|
});
|
|||
|
|
stream.getTracks().forEach(track => pc.addTrack(track, stream));
|
|||
|
|
|
|||
|
|
// 监听 Offer
|
|||
|
|
ws.onmessage = async (event) => {
|
|||
|
|
const msg = JSON.parse(event.data);
|
|||
|
|
|
|||
|
|
if (msg.type === 'offer') {
|
|||
|
|
await pc.setRemoteDescription(msg.sdp);
|
|||
|
|
const answer = await pc.createAnswer();
|
|||
|
|
await pc.setLocalDescription(answer);
|
|||
|
|
|
|||
|
|
// 发送 Answer
|
|||
|
|
ws.send(JSON.stringify({
|
|||
|
|
type: 'answer',
|
|||
|
|
callId: callId,
|
|||
|
|
sdp: answer
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (msg.type === 'ice-candidate') {
|
|||
|
|
await pc.addIceCandidate(msg.candidate);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试建议
|
|||
|
|
|
|||
|
|
### 功能测试
|
|||
|
|
1. 正常通话流程测试
|
|||
|
|
2. 拒绝/取消通话测试
|
|||
|
|
3. 超时未接测试
|
|||
|
|
4. 忙线检测测试
|
|||
|
|
5. 通话记录查询测试
|
|||
|
|
|
|||
|
|
### 异常测试
|
|||
|
|
1. 网络断开重连测试
|
|||
|
|
2. 对方离线测试
|
|||
|
|
3. 并发呼叫测试
|
|||
|
|
4. WebSocket 断开测试
|
|||
|
|
|
|||
|
|
### 性能测试
|
|||
|
|
1. 并发通话数量测试
|
|||
|
|
2. 信令延迟测试
|
|||
|
|
3. 内存泄漏测试
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 注意事项
|
|||
|
|
|
|||
|
|
1. **WebRTC 兼容性**: 确保客户端浏览器支持 WebRTC
|
|||
|
|
2. **HTTPS 要求**: WebRTC 需要在 HTTPS 环境下运行(本地开发除外)
|
|||
|
|
3. **STUN/TURN 服务器**: 生产环境需要配置 STUN/TURN 服务器用于 NAT 穿透
|
|||
|
|
4. **媒体权限**: 需要用户授权摄像头和麦克风权限
|
|||
|
|
5. **超时时间**: 默认 60 秒超时,可根据需求调整
|
|||
|
|
6. **通话记录清理**: 建议定期清理过期的通话记录
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 后续优化建议
|
|||
|
|
|
|||
|
|
1. **群组通话**: 支持多人视频会议
|
|||
|
|
2. **屏幕共享**: 支持屏幕共享功能
|
|||
|
|
3. **通话质量**: 添加网络质量检测和自适应码率
|
|||
|
|
4. **通话录制**: 支持通话录制功能
|
|||
|
|
5. **美颜滤镜**: 集成美颜和滤镜功能
|
|||
|
|
6. **通话统计**: 添加通话质量统计和分析
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档版本**: v1.0
|
|||
|
|
**最后更新**: 2024年12月26日
|
|||
|
|
**维护状态**: ✅ 已完成
|