zhibo/模块文档/18-通话功能模块.md
2025-12-30 11:11:11 +08:00

12 KiB
Raw Blame History

通话功能模块

模块概述

通话功能模块负责处理用户之间的语音和视频通话功能,包括发起通话、接听/拒绝通话、通话记录等。

相关文件

  • 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):

{
  "calleeId": "integer",     // 被叫用户ID必填
  "callType": "string"       // 通话类型: voice/video必填
}

返回数据 (InitiateCallResponse):

{
  "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

请求参数: 无

返回数据:

{
  "code": 200,
  "msg": "接听成功",
  "data": true
}

3. 拒绝通话

接口地址: POST /api/front/call/reject/{callId}

路径参数:

callId: string    // 通话ID

请求参数: 无

返回数据:

{
  "code": 200,
  "msg": "已拒绝",
  "data": true
}

4. 取消通话

接口地址: POST /api/front/call/cancel/{callId}

路径参数:

callId: string    // 通话ID

请求参数: 无

返回数据:

{
  "code": 200,
  "msg": "已取消",
  "data": true
}

5. 结束通话

接口地址: POST /api/front/call/end/{callId}

路径参数:

callId: string    // 通话ID

请求参数:

endReason: string    // 结束原因(可选)

返回数据:

{
  "code": 200,
  "msg": "通话已结束",
  "data": true
}

6. 获取通话记录

接口地址: GET /api/front/call/history

请求参数:

page: integer     // 页码默认1
limit: integer    // 每页数量默认20

返回数据 (CallHistoryResponse):

{
  "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

请求参数: 无

返回数据:

{
  "code": 200,
  "msg": "删除成功",
  "data": true
}

8. 获取未接来电数量

接口地址: GET /api/front/call/missed/count

请求参数: 无

返回数据:

{
  "code": 200,
  "msg": "success",
  "data": 5    // 未接来电数量
}

9. 获取通话状态

接口地址: GET /api/front/call/status

请求参数: 无

返回数据:

{
  "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):

{
  "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推送

来电通知

{
  "type": "incoming_call",
  "callId": "call123",
  "callType": "video",
  "callerId": 123,
  "callerName": "张三",
  "callerAvatar": "https://..."
}

通话状态变化

{
  "type": "call_status",
  "callId": "call123",
  "status": "connected",
  "timestamp": 1703001234567
}

对方挂断

{
  "type": "call_ended",
  "callId": "call123",
  "endReason": "normal",
  "duration": 120
}

使用示例

1. 发起语音通话

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. 接听通话

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. 获取通话记录

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库实现音视频通话