527 lines
12 KiB
Markdown
527 lines
12 KiB
Markdown
# 通话功能模块
|
||
|
||
## 模块概述
|
||
通话功能模块负责处理用户之间的语音和视频通话功能,包括发起通话、接听/拒绝通话、通话记录等。
|
||
|
||
## 相关文件
|
||
- `CallActivity.java` - 通话界面
|
||
- `IncomingCallActivity.java` - 来电界面
|
||
- `CallHistoryActivity.java` - 通话记录界面
|
||
- `CallManager.java` - 通话管理器
|
||
- `CallSignalingClient.java` - 信令客户端
|
||
- `CallApiService.java` - 通话API接口
|
||
- `InitiateCallRequest.java` - 发起通话请求模型
|
||
- `InitiateCallResponse.java` - 发起通话响应模型
|
||
- `CallRecordResponse.java` - 通话记录响应模型
|
||
- `CallHistoryResponse.java` - 通话历史响应模型
|
||
|
||
---
|
||
|
||
## 接口列表
|
||
|
||
### 1. 发起通话
|
||
**接口地址**: `POST /api/front/call/initiate`
|
||
|
||
**请求参数** (InitiateCallRequest):
|
||
```json
|
||
{
|
||
"calleeId": "integer", // 被叫用户ID(必填)
|
||
"callType": "string" // 通话类型: voice/video(必填)
|
||
}
|
||
```
|
||
|
||
**返回数据** (InitiateCallResponse):
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"callId": "string", // 通话ID
|
||
"callType": "string", // 通话类型
|
||
"calleeId": "integer", // 被叫用户ID
|
||
"calleeName": "string", // 被叫用户昵称
|
||
"calleeAvatar": "string", // 被叫用户头像
|
||
"status": "string", // 通话状态: calling
|
||
"signalingUrl": "string" // 信令服务器URL
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 接听通话
|
||
**接口地址**: `POST /api/front/call/accept/{callId}`
|
||
|
||
**路径参数**:
|
||
```
|
||
callId: string // 通话ID
|
||
```
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "接听成功",
|
||
"data": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 拒绝通话
|
||
**接口地址**: `POST /api/front/call/reject/{callId}`
|
||
|
||
**路径参数**:
|
||
```
|
||
callId: string // 通话ID
|
||
```
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "已拒绝",
|
||
"data": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 取消通话
|
||
**接口地址**: `POST /api/front/call/cancel/{callId}`
|
||
|
||
**路径参数**:
|
||
```
|
||
callId: string // 通话ID
|
||
```
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "已取消",
|
||
"data": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 结束通话
|
||
**接口地址**: `POST /api/front/call/end/{callId}`
|
||
|
||
**路径参数**:
|
||
```
|
||
callId: string // 通话ID
|
||
```
|
||
|
||
**请求参数**:
|
||
```
|
||
endReason: string // 结束原因(可选)
|
||
```
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "通话已结束",
|
||
"data": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6. 获取通话记录
|
||
**接口地址**: `GET /api/front/call/history`
|
||
|
||
**请求参数**:
|
||
```
|
||
page: integer // 页码,默认1
|
||
limit: integer // 每页数量,默认20
|
||
```
|
||
|
||
**返回数据** (CallHistoryResponse):
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"records": [
|
||
{
|
||
"id": "long", // 记录ID
|
||
"callId": "string", // 通话ID
|
||
"callType": "string", // 通话类型: voice/video
|
||
"callDirection": "string", // 通话方向: outgoing/incoming
|
||
"otherUserId": "integer", // 对方用户ID
|
||
"otherUserName": "string", // 对方用户昵称
|
||
"otherUserAvatar": "string",// 对方用户头像
|
||
"status": "string", // 状态: completed/missed/rejected/cancelled
|
||
"duration": "integer", // 通话时长(秒)
|
||
"startTime": "long", // 开始时间戳
|
||
"endTime": "long" // 结束时间戳
|
||
}
|
||
],
|
||
"total": "integer",
|
||
"page": "integer",
|
||
"limit": "integer"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 7. 删除通话记录
|
||
**接口地址**: `DELETE /api/front/call/record/{recordId}`
|
||
|
||
**路径参数**:
|
||
```
|
||
recordId: long // 记录ID
|
||
```
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "删除成功",
|
||
"data": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 8. 获取未接来电数量
|
||
**接口地址**: `GET /api/front/call/missed/count`
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": 5 // 未接来电数量
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 9. 获取通话状态
|
||
**接口地址**: `GET /api/front/call/status`
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"inCall": "boolean", // 是否正在通话中
|
||
"callId": "string", // 当前通话ID
|
||
"callType": "string", // 通话类型
|
||
"otherUserId": "integer", // 对方用户ID
|
||
"startTime": "long" // 通话开始时间
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 10. 获取通话详情
|
||
**接口地址**: `GET /api/front/call/detail/{callId}`
|
||
|
||
**路径参数**:
|
||
```
|
||
callId: string // 通话ID
|
||
```
|
||
|
||
**请求参数**: 无
|
||
|
||
**返回数据** (CallRecordResponse):
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": "long",
|
||
"callId": "string",
|
||
"callType": "string",
|
||
"callDirection": "string",
|
||
"callerId": "integer",
|
||
"callerName": "string",
|
||
"callerAvatar": "string",
|
||
"calleeId": "integer",
|
||
"calleeName": "string",
|
||
"calleeAvatar": "string",
|
||
"status": "string",
|
||
"duration": "integer",
|
||
"startTime": "long",
|
||
"endTime": "long",
|
||
"endReason": "string"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 功能说明
|
||
|
||
### 通话类型
|
||
|
||
| 类型 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| 语音通话 | voice | 仅语音通话 |
|
||
| 视频通话 | video | 视频+语音通话 |
|
||
|
||
### 通话状态
|
||
|
||
| 状态 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| 呼叫中 | calling | 正在呼叫对方 |
|
||
| 振铃中 | ringing | 对方正在振铃 |
|
||
| 通话中 | connected | 通话已接通 |
|
||
| 已完成 | completed | 通话正常结束 |
|
||
| 未接听 | missed | 对方未接听 |
|
||
| 已拒绝 | rejected | 对方拒绝接听 |
|
||
| 已取消 | cancelled | 主叫方取消 |
|
||
| 已结束 | ended | 通话结束 |
|
||
|
||
### 通话方向
|
||
|
||
| 方向 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| 呼出 | outgoing | 我发起的通话 |
|
||
| 呼入 | incoming | 对方发起的通话 |
|
||
|
||
---
|
||
|
||
## 通话流程
|
||
|
||
### 发起通话流程
|
||
```
|
||
1. 用户A点击通话按钮
|
||
↓
|
||
2. 调用发起通话接口
|
||
↓
|
||
3. 获取callId和信令服务器地址
|
||
↓
|
||
4. 连接信令服务器
|
||
↓
|
||
5. 等待对方响应
|
||
↓
|
||
6. 对方接听 → 建立P2P连接 → 开始通话
|
||
或
|
||
对方拒绝/超时 → 结束流程
|
||
```
|
||
|
||
### 接听通话流程
|
||
```
|
||
1. 用户B收到来电通知(WebSocket推送)
|
||
↓
|
||
2. 显示来电界面
|
||
↓
|
||
3. 用户点击接听按钮
|
||
↓
|
||
4. 调用接听通话接口
|
||
↓
|
||
5. 连接信令服务器
|
||
↓
|
||
6. 建立P2P连接
|
||
↓
|
||
7. 开始通话
|
||
```
|
||
|
||
### 结束通话流程
|
||
```
|
||
1. 用户点击挂断按钮
|
||
↓
|
||
2. 调用结束通话接口
|
||
↓
|
||
3. 断开P2P连接
|
||
↓
|
||
4. 断开信令连接
|
||
↓
|
||
5. 保存通话记录
|
||
↓
|
||
6. 返回上一页面
|
||
```
|
||
|
||
---
|
||
|
||
## WebSocket推送
|
||
|
||
通话相关的实时通知通过WebSocket推送:
|
||
|
||
### 来电通知
|
||
```json
|
||
{
|
||
"type": "incoming_call",
|
||
"callId": "call123",
|
||
"callType": "video",
|
||
"callerId": 123,
|
||
"callerName": "张三",
|
||
"callerAvatar": "https://..."
|
||
}
|
||
```
|
||
|
||
### 通话状态变化
|
||
```json
|
||
{
|
||
"type": "call_status",
|
||
"callId": "call123",
|
||
"status": "connected",
|
||
"timestamp": 1703001234567
|
||
}
|
||
```
|
||
|
||
### 对方挂断
|
||
```json
|
||
{
|
||
"type": "call_ended",
|
||
"callId": "call123",
|
||
"endReason": "normal",
|
||
"duration": 120
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
### 1. 发起语音通话
|
||
```java
|
||
private void initiateVoiceCall(int calleeId) {
|
||
CallApiService callApiService = // 获取CallApiService实例
|
||
|
||
InitiateCallRequest request = new InitiateCallRequest(calleeId, "voice");
|
||
Call<CallApiResponse<InitiateCallResponse>> call =
|
||
callApiService.initiateCall(request);
|
||
|
||
call.enqueue(new Callback<CallApiResponse<InitiateCallResponse>>() {
|
||
@Override
|
||
public void onResponse(Call<CallApiResponse<InitiateCallResponse>> call,
|
||
Response<CallApiResponse<InitiateCallResponse>> response) {
|
||
if (response.isSuccessful() && response.body() != null) {
|
||
CallApiResponse<InitiateCallResponse> apiResponse = response.body();
|
||
if (apiResponse.getCode() == 200) {
|
||
InitiateCallResponse data = apiResponse.getData();
|
||
String callId = data.getCallId();
|
||
String signalingUrl = data.getSignalingUrl();
|
||
|
||
// 跳转到通话界面
|
||
Intent intent = new Intent(this, CallActivity.class);
|
||
intent.putExtra("callId", callId);
|
||
intent.putExtra("callType", "voice");
|
||
intent.putExtra("signalingUrl", signalingUrl);
|
||
startActivity(intent);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(Call<CallApiResponse<InitiateCallResponse>> call, Throwable t) {
|
||
Toast.makeText(this, "发起通话失败", Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
### 2. 接听通话
|
||
```java
|
||
private void acceptCall(String callId) {
|
||
CallApiService callApiService = // 获取CallApiService实例
|
||
|
||
Call<CallApiResponse<Boolean>> call = callApiService.acceptCall(callId);
|
||
|
||
call.enqueue(new Callback<CallApiResponse<Boolean>>() {
|
||
@Override
|
||
public void onResponse(Call<CallApiResponse<Boolean>> call,
|
||
Response<CallApiResponse<Boolean>> response) {
|
||
if (response.isSuccessful() && response.body() != null) {
|
||
CallApiResponse<Boolean> apiResponse = response.body();
|
||
if (apiResponse.getCode() == 200) {
|
||
// 跳转到通话界面
|
||
Intent intent = new Intent(this, CallActivity.class);
|
||
intent.putExtra("callId", callId);
|
||
startActivity(intent);
|
||
finish();
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(Call<CallApiResponse<Boolean>> call, Throwable t) {
|
||
Toast.makeText(this, "接听失败", Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
### 3. 获取通话记录
|
||
```java
|
||
private void loadCallHistory(int page) {
|
||
CallApiService callApiService = // 获取CallApiService实例
|
||
|
||
Call<CallApiResponse<CallHistoryResponse>> call =
|
||
callApiService.getCallHistory(page, 20);
|
||
|
||
call.enqueue(new Callback<CallApiResponse<CallHistoryResponse>>() {
|
||
@Override
|
||
public void onResponse(Call<CallApiResponse<CallHistoryResponse>> call,
|
||
Response<CallApiResponse<CallHistoryResponse>> response) {
|
||
if (response.isSuccessful() && response.body() != null) {
|
||
CallApiResponse<CallHistoryResponse> apiResponse = response.body();
|
||
if (apiResponse.getCode() == 200) {
|
||
CallHistoryResponse data = apiResponse.getData();
|
||
List<CallRecordResponse> records = data.getRecords();
|
||
|
||
// 更新UI显示通话记录
|
||
updateCallHistoryList(records);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(Call<CallApiResponse<CallHistoryResponse>> call, Throwable t) {
|
||
Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 技术实现
|
||
|
||
### WebRTC集成
|
||
通话功能基于WebRTC技术实现:
|
||
- 使用信令服务器交换SDP和ICE候选
|
||
- 建立P2P连接进行音视频传输
|
||
- 支持NAT穿透
|
||
|
||
### 权限要求
|
||
- 语音通话:`RECORD_AUDIO`
|
||
- 视频通话:`RECORD_AUDIO` + `CAMERA`
|
||
- 网络访问:`INTERNET`
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
1. 所有接口都需要登录认证
|
||
2. 通话前需要检查对方是否在线
|
||
3. 需要申请音频和摄像头权限
|
||
4. 通话过程中保持屏幕常亮
|
||
5. 通话时禁用锁屏
|
||
6. 建议使用耳机以获得更好的通话质量
|
||
7. 通话记录保存30天
|
||
8. 未接来电会显示红点提示
|
||
9. 通话过程中网络断开需要自动重连
|
||
10. 需要集成WebRTC库实现音视频通话
|