204 lines
5.5 KiB
Markdown
204 lines
5.5 KiB
Markdown
|
|
# 现在需要修复的问题
|
|||
|
|
1. 媒体预览的组件有问题:Uncaught TypeError: $setup.previewMedia is not a function
|
|||
|
|
at _createElementBlock.onClick._cache.<computed>._cache.<computed> (MessageBubble.vue:27:21)
|
|||
|
|
2. 点击视频聊天或者语音聊天后应该请求弹框(等待对方接听,对方传输接收则开始聊天,对方拒绝则给出消息提示然后存在聊天记录中),对方也要弹出视频/语音通话邀请,可以接收或者拒绝。
|
|||
|
|
3. 根据{前端获取信令示例}和{后端交互代码}来完成整个聊天功能,并且要考虑用户掉线、断网、网络异常等情况,以及用户退出登录、用户被删除等情况。
|
|||
|
|
4. 在好友管理点击聊天后要吧侧边导航变成聊天
|
|||
|
|
5. 聊天记录列表和好友管理中中,点击对方的头像要有对方的信息
|
|||
|
|
6. 完整的基于WebRTC实现语音/视频通话功能
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 前端获取信令示例
|
|||
|
|
```javascript
|
|||
|
|
// 发起通话邀请
|
|||
|
|
function startCall(targetUserId, isVideo) {
|
|||
|
|
const callId = generateUniqueId();
|
|||
|
|
const callType = isVideo ? 6 : 7; // 6=视频,7=语音
|
|||
|
|
|
|||
|
|
const callSignal = {
|
|||
|
|
action: "invite",
|
|||
|
|
call_type: callType,
|
|||
|
|
call_id: callId,
|
|||
|
|
caller_id: currentUserId,
|
|||
|
|
callee_id: targetUserId,
|
|||
|
|
data: JSON.stringify(offer) // SDP offer
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const payload = {
|
|||
|
|
sender_user_id: currentUserId,
|
|||
|
|
receiver_user_id: targetUserId,
|
|||
|
|
message_type: callType,
|
|||
|
|
message_content: JSON.stringify(callSignal)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 调用/send-to-user接口
|
|||
|
|
fetch('/send-to-user', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(payload)
|
|||
|
|
}).then(response => {
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error('Failed to send call invite');
|
|||
|
|
}
|
|||
|
|
return response.json();
|
|||
|
|
}).then(data => {
|
|||
|
|
console.log('Call invite sent:', data);
|
|||
|
|
}).catch(error => {
|
|||
|
|
console.error('Error sending call invite:', error);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 接受通话
|
|||
|
|
function acceptCall(callId, callType) {
|
|||
|
|
const callSignal = {
|
|||
|
|
action: "accept",
|
|||
|
|
call_id: callId,
|
|||
|
|
data: JSON.stringify(answer) // SDP answer
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const payload = {
|
|||
|
|
sender_user_id: currentUserId,
|
|||
|
|
receiver_user_id: callerUserId,
|
|||
|
|
message_type: callType,
|
|||
|
|
message_content: JSON.stringify(callSignal)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fetch('/send-to-user', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(payload)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 拒绝通话
|
|||
|
|
function rejectCall(callId, callType, reason) {
|
|||
|
|
const callSignal = {
|
|||
|
|
action: "reject",
|
|||
|
|
call_id: callId,
|
|||
|
|
data: reason || "用户忙"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const payload = {
|
|||
|
|
sender_user_id: currentUserId,
|
|||
|
|
receiver_user_id: callerUserId,
|
|||
|
|
message_type: callType,
|
|||
|
|
message_content: JSON.stringify(callSignal)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fetch('/send-to-user', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(payload)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 结束通话
|
|||
|
|
function endCall(callId, callType) {
|
|||
|
|
const callSignal = {
|
|||
|
|
action: "end",
|
|||
|
|
call_id: callId
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const payload = {
|
|||
|
|
sender_user_id: currentUserId,
|
|||
|
|
receiver_user_id: otherUserId,
|
|||
|
|
message_type: callType,
|
|||
|
|
message_content: JSON.stringify(callSignal)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fetch('/send-to-user', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(payload)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送ICE候选
|
|||
|
|
function sendCandidate(callId, callType, candidate, targetUserId) {
|
|||
|
|
const callSignal = {
|
|||
|
|
action: "candidate",
|
|||
|
|
call_id: callId,
|
|||
|
|
data: JSON.stringify(candidate)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const payload = {
|
|||
|
|
sender_user_id: currentUserId,
|
|||
|
|
receiver_user_id: targetUserId,
|
|||
|
|
message_type: callType,
|
|||
|
|
message_content: JSON.stringify(callSignal)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fetch('/send-to-user', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(payload)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 后端交互代码
|
|||
|
|
1. {websocket_controller.go}
|
|||
|
|
|
|||
|
|
## 消息结构
|
|||
|
|
1. {messages.go}
|
|||
|
|
|
|||
|
|
## 通话流程图
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
A[用户A点击通话按钮] --> B{选择通话类型}
|
|||
|
|
B --> |语音通话| C1[设置 message_type=7]
|
|||
|
|
B --> |视频通话| C2[设置 message_type=6]
|
|||
|
|
|
|||
|
|
C1 --> D[生成唯一CallID]
|
|||
|
|
D --> E[创建PeerConnection对象]
|
|||
|
|
E --> F[获取本地媒体流]
|
|||
|
|
F --> G[创建SDP offer]
|
|||
|
|
G --> H[构造信令对象]
|
|||
|
|
|
|||
|
|
H --> I[发送邀请信令]
|
|||
|
|
I --> J[后端转发给用户B]
|
|||
|
|
|
|||
|
|
J --> K[用户B收到来电通知]
|
|||
|
|
K --> L{用户B选择}
|
|||
|
|
L --> |接听| M[创建SDP answer]
|
|||
|
|
L --> |拒绝| N[发送拒绝信令]
|
|||
|
|
|
|||
|
|
M --> O[发送接受信令]
|
|||
|
|
O --> P[后端转发给用户A]
|
|||
|
|
|
|||
|
|
P --> Q[用户A设置远程描述]
|
|||
|
|
Q --> R[交换ICE候选]
|
|||
|
|
|
|||
|
|
R --> S[建立P2P连接]
|
|||
|
|
S --> T[开始语音通话]
|
|||
|
|
|
|||
|
|
N --> U[用户A收到拒绝通知]
|
|||
|
|
U --> V[结束通话流程]
|
|||
|
|
|
|||
|
|
subgraph 信令交换
|
|||
|
|
I[发送邀请信令]
|
|||
|
|
O[发送接受信令]
|
|||
|
|
R[交换ICE候选]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph WebRTC连接
|
|||
|
|
E[创建PeerConnection]
|
|||
|
|
F[获取本地媒体流]
|
|||
|
|
G[创建SDP offer]
|
|||
|
|
M[创建SDP answer]
|
|||
|
|
Q[设置远程描述]
|
|||
|
|
S[建立P2P连接]
|
|||
|
|
end
|
|||
|
|
```
|