Files
nl-im-websocket-demo/README2.md

204 lines
5.5 KiB
Markdown
Raw Normal View History

2025-07-05 15:57:28 +08:00
# 现在需要修复的问题
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
```