diff --git a/src/components/VideoCallComponent.vue b/src/components/VideoCallComponent.vue
index 86ca4f7..f6dd33e 100644
--- a/src/components/VideoCallComponent.vue
+++ b/src/components/VideoCallComponent.vue
@@ -13,7 +13,6 @@
+
+
@@ -44,6 +51,9 @@
麦克风未工作
+
+ 对方未开启摄像头
+
@@ -96,7 +106,7 @@
@@ -184,14 +194,16 @@
@@ -670,13 +760,47 @@ onUnmounted(() => {
background: #ff6b6b;
}
-.device-status-container {
+.friendly-message {
position: absolute;
top: 65px;
left: 0;
right: 0;
display: flex;
justify-content: center;
+ z-index: 15;
+ padding: 5px;
+}
+
+.message-content {
+ background: rgba(76, 175, 80, 0.9);
+ color: white;
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ animation: slideInDown 0.3s ease-out;
+}
+
+@keyframes slideInDown {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.device-status-container {
+ position: absolute;
+ top: 90px;
+ left: 0;
+ right: 0;
+ display: flex;
+ justify-content: center;
gap: 15px;
z-index: 10;
padding: 5px;
@@ -691,6 +815,10 @@ onUnmounted(() => {
animation: blink 1.5s infinite;
}
+.status-alert.remote-camera-off {
+ background: rgba(255, 152, 0, 0.8);
+}
+
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.6; }
@@ -738,6 +866,7 @@ onUnmounted(() => {
justify-content: center;
font-weight: bold;
color: white;
+ font-size: 14px;
}
.mini-info {
@@ -799,6 +928,7 @@ onUnmounted(() => {
justify-content: center;
font-size: 32px;
font-weight: bold;
+ color: white;
margin-bottom: 15px;
}
@@ -852,6 +982,12 @@ onUnmounted(() => {
color: white;
}
+.local-video {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
.call-controls {
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
@@ -898,7 +1034,6 @@ onUnmounted(() => {
background: #ff5252;
}
-/* 新增来电控制按钮样式 */
.incoming-call-controls {
position: absolute;
bottom: 100px;
@@ -942,7 +1077,6 @@ onUnmounted(() => {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
-/* 添加忙线状态样式 */
.busy-status {
background: rgba(0, 0, 0, 0.7);
color: #ff6b6b;
@@ -958,7 +1092,6 @@ onUnmounted(() => {
font-size: 20px;
}
-/* 响应式设计 */
@media (max-width: 600px) {
.video-call-container:not(.minimized) {
width: 95vw !important;
diff --git a/src/composables/useWebRTC.js b/src/composables/useWebRTC.js
index fba96f8..e5dfd09 100644
--- a/src/composables/useWebRTC.js
+++ b/src/composables/useWebRTC.js
@@ -9,9 +9,11 @@ export function useWebRTC() {
const peerConnection = ref(null)
const isConnected = ref(false)
const isConnecting = ref(false)
+ const hasLocalVideo = ref(false)
+ const hasLocalAudio = ref(false)
const userStore = useUserStore()
- const chatStore = useChatStore() // Moved to top-level
+ const chatStore = useChatStore()
const configuration = {
iceServers: [
@@ -36,29 +38,27 @@ export function useWebRTC() {
if (event.candidate) {
console.log("发送ICE候选:", event.candidate)
- // 只有在连接建立过程中才发送 ICE 候选
if (chatStore.callStatus === "connecting" || chatStore.callStatus === "ongoing") {
try {
- await callAPI.sendCandidate(
- userStore.currentUser?.id,
- peerId,
- callId,
- event.candidate,
- 6, // 默认视频通话类型
- )
+ await callAPI.sendCandidate(userStore.currentUser?.id, peerId, callId, event.candidate, 6)
} catch (error) {
console.error("发送ICE候选失败:", error)
}
- } else {
- console.log("通话状态不正确,跳过发送ICE候选:", chatStore.callStatus)
}
}
}
peerConnection.value.ontrack = (event) => {
if (event.streams && event.streams[0]) {
+ console.log("接收到远程流:", event.streams[0])
remoteStream.value = event.streams[0]
- console.log("接收到远程流:", remoteStream.value)
+
+ const videoTracks = event.streams[0].getVideoTracks()
+ const audioTracks = event.streams[0].getAudioTracks()
+ console.log("远程流包含:", {
+ video: videoTracks.length,
+ audio: audioTracks.length,
+ })
}
}
@@ -66,19 +66,12 @@ export function useWebRTC() {
const state = peerConnection.value?.connectionState
console.log("WebRTC连接状态变化:", state)
- // 只有在有效的通话状态下才处理连接状态变化
- if (!chatStore.isInCall) {
- console.log("不在通话中,忽略连接状态变化")
- return
- }
-
if (state === "connected") {
isConnected.value = true
isConnecting.value = false
- // 只有在 connecting 状态时才切换到 ongoing
if (chatStore.callStatus === "connecting") {
chatStore.callStatus = "ongoing"
- chatStore.callConnectionStatus = "通话中"
+ chatStore.callConnectionStatus = "通话已连接"
console.log("WebRTC连接已建立,通话开始")
}
} else if (state === "connecting") {
@@ -90,7 +83,6 @@ export function useWebRTC() {
console.log("WebRTC连接断开")
isConnected.value = false
isConnecting.value = false
- // 只有在通话中才处理断开
if (chatStore.callStatus === "ongoing") {
chatStore.callStatus = "disconnected"
chatStore.callConnectionStatus = "连接已断开"
@@ -110,13 +102,14 @@ export function useWebRTC() {
const state = peerConnection.value?.iceConnectionState
console.log("ICE连接状态变化:", state)
- // 只有在有效的通话状态下才处理ICE状态变化
- if (!chatStore.isInCall) {
- console.log("不在通话中,忽略ICE状态变化")
- return
- }
-
- if (state === "failed") {
+ if (state === "connected" || state === "completed") {
+ console.log("ICE连接成功")
+ isConnected.value = true
+ if (chatStore.callStatus === "connecting") {
+ chatStore.callStatus = "ongoing"
+ chatStore.callConnectionStatus = "通话中"
+ }
+ } else if (state === "failed") {
console.log("ICE连接失败")
if (chatStore.callStatus === "ongoing" || chatStore.callStatus === "connecting") {
chatStore.callStatus = "failed"
@@ -139,15 +132,140 @@ export function useWebRTC() {
// 如果已有本地流,先停止
if (localStream.value) {
localStream.value.getTracks().forEach((track) => track.stop())
+ localStream.value = null
+ hasLocalVideo.value = false
+ hasLocalAudio.value = false
}
console.log("获取本地媒体,约束:", constraints)
- localStream.value = await navigator.mediaDevices.getUserMedia(constraints)
- console.log("获取本地媒体成功:", localStream.value)
- return localStream.value
+
+ try {
+ localStream.value = await navigator.mediaDevices.getUserMedia(constraints)
+ console.log("获取本地媒体成功:", localStream.value)
+
+ // 检查实际获取到的轨道
+ const videoTracks = localStream.value.getVideoTracks()
+ const audioTracks = localStream.value.getAudioTracks()
+
+ hasLocalVideo.value = videoTracks.length > 0 && videoTracks[0].enabled
+ hasLocalAudio.value = audioTracks.length > 0 && audioTracks[0].enabled
+
+ console.log("本地媒体状态:", {
+ hasVideo: hasLocalVideo.value,
+ hasAudio: hasLocalAudio.value,
+ videoTracks: videoTracks.length,
+ audioTracks: audioTracks.length,
+ })
+
+ return localStream.value
+ } catch (mediaError) {
+ console.warn("获取完整媒体失败,尝试仅获取音频:", mediaError)
+
+ if (constraints.video) {
+ try {
+ localStream.value = await navigator.mediaDevices.getUserMedia({ audio: constraints.audio, video: false })
+ console.log("仅获取音频成功:", localStream.value)
+
+ const audioTracks = localStream.value.getAudioTracks()
+ hasLocalVideo.value = false
+ hasLocalAudio.value = audioTracks.length > 0 && audioTracks[0].enabled
+
+ console.log("本地媒体状态(仅音频):", { hasVideo: hasLocalVideo.value, hasAudio: hasLocalAudio.value })
+
+ return localStream.value
+ } catch (audioError) {
+ console.warn("获取音频也失败:", audioError)
+ hasLocalVideo.value = false
+ hasLocalAudio.value = false
+ return null
+ }
+ } else {
+ hasLocalVideo.value = false
+ hasLocalAudio.value = false
+ return null
+ }
+ }
} catch (error) {
- console.error("获取本地媒体失败:", error)
- throw error
+ console.error("获取本地媒体完全失败:", error)
+ hasLocalVideo.value = false
+ hasLocalAudio.value = false
+ return null
+ }
+ }
+
+ // 重新获取视频流(用于摄像头开关)
+ const toggleVideoStream = async (enable) => {
+ try {
+ if (enable) {
+ console.log("重新获取视频流")
+ const newStream = await navigator.mediaDevices.getUserMedia({
+ audio: true,
+ video: true,
+ })
+
+ // 停止旧的流
+ if (localStream.value) {
+ localStream.value.getTracks().forEach((track) => track.stop())
+ }
+
+ localStream.value = newStream
+ hasLocalVideo.value = true
+ hasLocalAudio.value = true
+
+ // 如果有PeerConnection,替换轨道
+ if (peerConnection.value) {
+ const videoTrack = newStream.getVideoTracks()[0]
+ const audioTrack = newStream.getAudioTracks()[0]
+
+ const senders = peerConnection.value.getSenders()
+
+ // 替换视频轨道
+ const videoSender = senders.find((sender) => sender.track && sender.track.kind === "video")
+ if (videoSender && videoTrack) {
+ await videoSender.replaceTrack(videoTrack)
+ console.log("替换视频轨道成功")
+ } else if (videoTrack) {
+ peerConnection.value.addTrack(videoTrack, newStream)
+ console.log("添加新视频轨道")
+ }
+
+ // 替换音频轨道
+ const audioSender = senders.find((sender) => sender.track && sender.track.kind === "audio")
+ if (audioSender && audioTrack) {
+ await audioSender.replaceTrack(audioTrack)
+ console.log("替换音频轨道成功")
+ } else if (audioTrack) {
+ peerConnection.value.addTrack(audioTrack, newStream)
+ console.log("添加新音频轨道")
+ }
+ }
+
+ return newStream
+ } else {
+ // 关闭摄像头:只保留音频
+ if (localStream.value) {
+ const videoTracks = localStream.value.getVideoTracks()
+ videoTracks.forEach((track) => {
+ track.stop()
+ localStream.value.removeTrack(track)
+ })
+ hasLocalVideo.value = false
+
+ // 如果有PeerConnection,移除视频轨道
+ if (peerConnection.value) {
+ const senders = peerConnection.value.getSenders()
+ const videoSender = senders.find((sender) => sender.track && sender.track.kind === "video")
+ if (videoSender) {
+ await videoSender.replaceTrack(null)
+ console.log("移除视频轨道")
+ }
+ }
+ }
+ return localStream.value
+ }
+ } catch (error) {
+ console.error("切换视频流失败:", error)
+ return localStream.value
}
}
@@ -156,19 +274,33 @@ export function useWebRTC() {
createPeerConnection(callId, peerId)
}
- // 添加本地流到连接
+ // 确保本地流已添加到连接
if (localStream.value) {
+ console.log("添加本地流到PeerConnection")
+ const existingSenders = peerConnection.value.getSenders()
+
localStream.value.getTracks().forEach((track) => {
- peerConnection.value.addTrack(track, localStream.value)
+ // 检查是否已经添加了这个轨道
+ const existingSender = existingSenders.find((sender) => sender.track === track)
+ if (!existingSender) {
+ console.log("添加轨道到PeerConnection:", track.kind, track.enabled)
+ peerConnection.value.addTrack(track, localStream.value)
+ } else {
+ console.log("轨道已存在,跳过添加:", track.kind)
+ }
})
+ } else {
+ console.warn("本地流不存在,无法添加到PeerConnection")
}
try {
+ console.log("开始创建Offer")
const offer = await peerConnection.value.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
})
+ console.log("设置本地描述")
await peerConnection.value.setLocalDescription(offer)
console.log("创建Offer成功:", offer)
@@ -184,19 +316,35 @@ export function useWebRTC() {
createPeerConnection(callId, peerId)
}
- // 添加本地流到连接
- if (localStream.value) {
- localStream.value.getTracks().forEach((track) => {
- peerConnection.value.addTrack(track, localStream.value)
- })
- }
-
try {
+ console.log("设置远程Offer描述")
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
- const answer = await peerConnection.value.createAnswer()
- await peerConnection.value.setLocalDescription(answer)
+ // 确保本地流已添加到连接
+ if (localStream.value) {
+ console.log("添加本地流到PeerConnection")
+ const existingSenders = peerConnection.value.getSenders()
+
+ localStream.value.getTracks().forEach((track) => {
+ const existingSender = existingSenders.find((sender) => sender.track === track)
+ if (!existingSender) {
+ console.log("添加轨道到PeerConnection:", track.kind, track.enabled)
+ peerConnection.value.addTrack(track, localStream.value)
+ } else {
+ console.log("轨道已存在,跳过添加:", track.kind)
+ }
+ })
+ } else {
+ console.warn("本地流不存在,无法添加到PeerConnection")
+ }
+
+ console.log("开始创建Answer")
+ const answer = await peerConnection.value.createAnswer()
+
+ console.log("设置本地Answer描述")
+ await peerConnection.value.setLocalDescription(answer)
console.log("创建Answer成功:", answer)
+
return answer
} catch (error) {
console.error("创建Answer失败:", error)
@@ -206,6 +354,10 @@ export function useWebRTC() {
const setRemoteAnswer = async (answer) => {
try {
+ if (!peerConnection.value) {
+ throw new Error("PeerConnection 未初始化")
+ }
+ console.log("设置远程Answer描述")
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(answer))
console.log("设置远程Answer成功")
} catch (error) {
@@ -216,6 +368,10 @@ export function useWebRTC() {
const setRemoteOffer = async (offer) => {
try {
+ if (!peerConnection.value) {
+ throw new Error("PeerConnection 未初始化")
+ }
+ console.log("设置远程Offer描述")
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
console.log("设置远程Offer成功")
} catch (error) {
@@ -227,6 +383,7 @@ export function useWebRTC() {
const addIceCandidate = async (candidate) => {
if (peerConnection.value && peerConnection.value.remoteDescription) {
try {
+ console.log("添加ICE候选:", candidate)
await peerConnection.value.addIceCandidate(new RTCIceCandidate(candidate))
console.log("添加ICE候选成功")
} catch (error) {
@@ -259,6 +416,8 @@ export function useWebRTC() {
remoteStream.value = null
isConnected.value = false
isConnecting.value = false
+ hasLocalVideo.value = false
+ hasLocalAudio.value = false
}
return {
@@ -267,8 +426,11 @@ export function useWebRTC() {
peerConnection,
isConnected,
isConnecting,
+ hasLocalVideo,
+ hasLocalAudio,
createPeerConnection,
getLocalMedia,
+ toggleVideoStream,
createOffer,
createAnswer,
setRemoteAnswer,
diff --git a/src/stores/chat.js b/src/stores/chat.js
index 6c0ec1f..d44cdde 100644
--- a/src/stores/chat.js
+++ b/src/stores/chat.js
@@ -16,9 +16,9 @@ export const useChatStore = defineStore("chat", () => {
const currentCall = ref(null)
const incomingCall = ref(null)
const callStatus = ref("idle") // idle, calling, ringing, connecting, ongoing, ended, disconnected, hangup
- const callError = ref(null) // 错误状态:'rejected', 'no-answer', 'busy', 'failed'
- const callConnectionStatus = ref("") // 连接状态描述
- const callTimeout = ref(null) // 通话超时计时器
+ const callError = ref(null)
+ const callConnectionStatus = ref("")
+ const callTimeout = ref(null)
const webrtc = useWebRTC()
const userStore = useUserStore()
@@ -38,14 +38,6 @@ export const useChatStore = defineStore("chat", () => {
() => callStatus.value,
(newStatus) => {
console.log("通话状态变化:", newStatus)
- switch (newStatus) {
- case "disconnected":
- handleCallDisconnected()
- break
- case "hangup":
- handleCallHangup()
- break
- }
},
)
@@ -57,7 +49,6 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = ""
currentCall.value = null
incomingCall.value = null
- // 注意:这里不要立即关闭WebRTC连接,让组件自己处理
}
// 清除通话超时
@@ -77,12 +68,11 @@ export const useChatStore = defineStore("chat", () => {
}, duration)
}
- // 发送通话信令 - 使用新的专用方法
+ // 发送通话信令
const sendCallSignalInternal = async (signal) => {
try {
console.log("发送通话信令:", signal)
- // 确保必要参数存在
if (!userStore.currentUser?.id) {
throw new Error("用户未登录")
}
@@ -99,11 +89,10 @@ export const useChatStore = defineStore("chat", () => {
throw new Error("缺少通话状态")
}
- // 构建请求数据
const requestData = {
sender_user_id: userStore.currentUser.id,
receiver_user_id: signal.receiver_user_id,
- message_type: signal.call_type || (signal.call_status === "invite" ? 6 : 7), // 6=视频, 7=语音
+ message_type: signal.call_type || (signal.call_status === "invite" ? 6 : 7),
message_content: JSON.stringify(signal.data || {}),
call_id: signal.call_id,
call_status: signal.call_status,
@@ -133,14 +122,11 @@ export const useChatStore = defineStore("chat", () => {
// 加载好友列表
const loadFriends = async (presetUsers, currentUserId) => {
try {
- // 从数据库获取好友列表
let friendList = await chatDB.getFriends(currentUserId)
- // 如果数据库中没有好友,使用预设用户初始化
if (friendList.length === 0) {
friendList = presetUsers.filter((user) => user.id !== currentUserId)
- // 保存到数据库
for (const friend of friendList) {
await chatDB.saveFriend(
{
@@ -156,7 +142,6 @@ export const useChatStore = defineStore("chat", () => {
friends.value = friendList
} catch (error) {
console.error("加载好友列表失败:", error)
- // 降级到预设用户
const friendList = presetUsers.filter((user) => user.id !== currentUserId)
friendList.forEach((friend) => {
friend.lastMessage = ""
@@ -169,7 +154,6 @@ export const useChatStore = defineStore("chat", () => {
// 设置当前好友
const setCurrentFriend = (friend) => {
currentFriend.value = friend
- // 清除未读消息计数
if (friend) {
friend.unreadCount = 0
}
@@ -179,11 +163,9 @@ export const useChatStore = defineStore("chat", () => {
const switchFriend = async (friend, currentUserId) => {
currentFriend.value = friend
- // 清空未读消息计数
friend.unreadCount = 0
await chatDB.clearUnreadCount(friend.id)
- // 加载聊天记录
try {
const chatHistory = await chatDB.getChatHistory(currentUserId, friend.id)
messages.value = chatHistory.sort((a, b) => a.timestamp - b.timestamp)
@@ -197,7 +179,6 @@ export const useChatStore = defineStore("chat", () => {
const addMessage = async (message, currentUserId) => {
messages.value.push(message)
- // 更新好友列表中的最后消息
const friend = friends.value.find((f) => f.id === message.senderId || f.id === currentFriend.value?.id)
if (friend) {
let lastMessage = message.content
@@ -208,13 +189,11 @@ export const useChatStore = defineStore("chat", () => {
friend.lastMessage = lastMessage.length > 20 ? lastMessage.substring(0, 20) + "..." : lastMessage
- // 更新数据库中的好友信息
await chatDB.updateFriendLastMessage(friend.id, currentUserId, friend.lastMessage, 0)
}
if (currentFriend.value && friend && friend.id === currentFriend.value.id) {
try {
- // 保存到数据库
await chatDB.saveMessage(message, currentUserId, currentFriend.value.id)
} catch (error) {
console.error("保存消息失败:", error)
@@ -224,7 +203,6 @@ export const useChatStore = defineStore("chat", () => {
// 处理接收到的消息
const handleIncomingMessage = async (messageData, currentUserId) => {
- // 添加来源检查 - 防止循环
if (messageData.isFromHttp) {
console.log("忽略来自HTTP的消息,避免循环")
return
@@ -235,7 +213,6 @@ export const useChatStore = defineStore("chat", () => {
if (receiverId !== currentUserId) return
- // 如果是通话信令,直接处理
if (messageData.call_id || messageData.call_status) {
handleCallSignal(messageData)
return
@@ -252,22 +229,18 @@ export const useChatStore = defineStore("chat", () => {
}
try {
- // 保存消息到数据库
await chatDB.saveMessage(newMessage, currentUserId, senderId)
- // 如果消息来自当前聊天用户,直接显示
if (currentFriend.value && senderId == currentFriend.value.id) {
newMessage.read = true
messages.value.push(newMessage)
} else {
- // 更新未读消息计数
const friend = friends.value.find((f) => f.id == senderId)
if (friend) {
friend.unreadCount = (friend.unreadCount || 0) + 1
}
}
- // 更新最后消息
const friend = friends.value.find((f) => f.id == senderId)
if (friend) {
let lastMessage = newMessage.content
@@ -277,7 +250,6 @@ export const useChatStore = defineStore("chat", () => {
if (newMessage.type === "video-call" || newMessage.type === "audio-call") lastMessage = "[通话]"
friend.lastMessage = lastMessage
- // 更新数据库
await chatDB.updateFriendLastMessage(friend.id, currentUserId, lastMessage, friend.unreadCount)
}
} catch (error) {
@@ -306,7 +278,6 @@ export const useChatStore = defineStore("chat", () => {
console.log("处理通话信令:", data)
console.log("通话状态:", data.call_status)
- // 根据通话状态处理
switch (data.call_status) {
case "invite":
handleIncomingCall(data)
@@ -338,13 +309,13 @@ export const useChatStore = defineStore("chat", () => {
case "busy":
handleCallBusy(data)
break
- case "hangup": // 对方主动挂断
+ case "hangup":
handleCallHangup(data)
break
- case "disconnected": // 对方掉线
+ case "disconnected":
handleCallDisconnected(data)
break
- case "terminated": // 对方终止呼叫
+ case "terminated":
handleCallTerminated(data)
break
case "failed":
@@ -356,7 +327,6 @@ export const useChatStore = defineStore("chat", () => {
// 处理Offer信令
const handleOffer = async (data) => {
if (incomingCall.value && incomingCall.value.callId === data.call_id) {
- // 确保只处理一次 Offer
if (callStatus.value === "ongoing") {
console.log("通话已建立,忽略重复的 Offer")
return
@@ -366,11 +336,13 @@ export const useChatStore = defineStore("chat", () => {
const offer = JSON.parse(data.content || data.message_content || "{}")
console.log("收到Offer:", offer)
- // 设置远程Offer并创建Answer
- await webrtc.setRemoteOffer(offer)
+ if (!webrtc.peerConnection.value) {
+ console.log("创建 WebRTC 连接以处理 Offer")
+ webrtc.createPeerConnection(data.call_id, data.sender_user_id)
+ }
+
const answer = await webrtc.createAnswer(offer, data.call_id, data.sender_user_id)
- // 发送Answer - 使用便捷方法
await callAPI.sendAnswer(userStore.currentUser.id, data.sender_user_id, data.call_id, answer, data.message_type)
console.log("发送Answer成功")
@@ -384,7 +356,6 @@ export const useChatStore = defineStore("chat", () => {
// 处理Answer信令
const handleAnswer = async (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- // 确保只处理一次 Answer
if (callStatus.value === "ongoing") {
console.log("通话已建立,忽略重复的 Answer")
return
@@ -394,7 +365,6 @@ export const useChatStore = defineStore("chat", () => {
const answer = JSON.parse(data.content || data.message_content || "{}")
console.log("收到Answer:", answer)
- // 设置远程Answer
await webrtc.setRemoteAnswer(answer)
callStatus.value = "connecting"
@@ -423,7 +393,6 @@ export const useChatStore = defineStore("chat", () => {
callError.value = "failed"
callConnectionStatus.value = "连接失败"
- // 添加通话记录
const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
@@ -441,12 +410,10 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = "[连接失败]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[连接失败]", friend.unreadCount)
}
- // 5秒后清除状态
setTimeout(() => {
resetCallState()
}, 5000)
@@ -455,18 +422,15 @@ export const useChatStore = defineStore("chat", () => {
// 处理来电
const handleIncomingCall = (data) => {
- // 只有在当前没有通话时才处理来电
if (isInCall.value) {
console.log("当前正在通话中,发送忙线状态")
callAPI.sendBusy(userStore.currentUser.id, data.sender_user_id, data.call_id, data.message_type)
- // 添加未接来电消息
const friend = friends.value.find((f) => f.id === data.sender_user_id)
if (friend) {
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
- // 添加通话记录
const callMessage = {
type: data.message_type === 6 ? "video-call" : "audio-call",
content: "[未接来电]",
@@ -482,7 +446,6 @@ export const useChatStore = defineStore("chat", () => {
return
}
- // 重置所有通话状态
resetCallState()
incomingCall.value = {
@@ -497,7 +460,6 @@ export const useChatStore = defineStore("chat", () => {
console.log("收到来电:", incomingCall.value)
- // 设置来电超时(30秒后自动拒绝)
setCallTimeout(() => {
if (callStatus.value === "ringing" && incomingCall.value) {
console.log("来电超时,自动拒绝")
@@ -509,7 +471,6 @@ export const useChatStore = defineStore("chat", () => {
// 处理通话接受
const handleCallAccepted = async (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- // 防止重复处理 accepted 状态
if (callStatus.value === "connecting" || callStatus.value === "ongoing") {
console.log("通话已在连接中,忽略重复的 accepted 信令")
return
@@ -520,11 +481,17 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = "对方已接听,正在连接..."
console.log("通话已被接受,开始交换信令")
- // 接受通话后开始创建Offer
try {
+ if (!webrtc.localStream.value) {
+ console.log("获取本地媒体用于创建Offer")
+ await webrtc.getLocalMedia({
+ audio: true,
+ video: currentCall.value.callType === "video",
+ })
+ }
+
const offer = await webrtc.createOffer(currentCall.value.callId, currentCall.value.peerId)
- // 发送Offer给对方 - 使用便捷方法
await callAPI.sendOffer(
userStore.currentUser.id,
currentCall.value.peerId,
@@ -548,7 +515,6 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = "对方已拒绝"
console.log("通话已被拒绝")
- // 添加通话记录
const friend = friends.value.find((f) => f.id === currentCall.value.peerId)
if (friend) {
const callMessage = {
@@ -562,12 +528,10 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = "[对方已拒绝]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已拒绝]", friend.unreadCount)
}
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
@@ -580,7 +544,6 @@ export const useChatStore = defineStore("chat", () => {
(currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)
) {
- // 添加通话记录
const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
@@ -606,7 +569,6 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = callMessage.content
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, callMessage.content, friend.unreadCount)
}
@@ -615,7 +577,6 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = "通话已结束"
console.log("通话已结束")
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
@@ -632,7 +593,6 @@ export const useChatStore = defineStore("chat", () => {
const candidate = JSON.parse(data.content || data.message_content || "{}")
console.log("收到ICE候选:", candidate)
- // 添加到WebRTC连接
webrtc.addIceCandidate(candidate)
} catch (error) {
console.error("处理ICE候选失败:", error)
@@ -643,7 +603,6 @@ export const useChatStore = defineStore("chat", () => {
// 处理无人接听
const handleCallNoAnswer = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- // 添加未接来电记录
const friend = friends.value.find((f) => f.id === currentCall.value.peerId)
if (friend) {
const callMessage = {
@@ -657,7 +616,6 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
}
@@ -666,7 +624,6 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = "对方无人接听"
console.log("对方无人接听")
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
@@ -676,7 +633,6 @@ export const useChatStore = defineStore("chat", () => {
// 处理对方忙线
const handleCallBusy = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- // 添加忙线记录
const friend = friends.value.find((f) => f.id === currentCall.value.peerId)
if (friend) {
const callMessage = {
@@ -690,7 +646,6 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = "[对方忙线]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方忙线]", friend.unreadCount)
}
@@ -699,7 +654,6 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = "对方忙线中"
console.log("对方忙线中")
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
@@ -712,7 +666,6 @@ export const useChatStore = defineStore("chat", () => {
const peerId = data?.sender_user_id || currentCall.value?.peerId || incomingCall.value?.callerId
if (callId && peerId) {
- // 添加通话记录
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
const duration =
@@ -722,7 +675,7 @@ export const useChatStore = defineStore("chat", () => {
const callMessage = {
type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call",
- content: `[对方已挂断] ${Math.floor(duration / 60)}分${duration % 60}秒`,
+ content: duration > 0 ? `[对方已挂断] ${Math.floor(duration / 60)}分${duration % 60}秒` : "[对方已挂断]",
time: new Date().toLocaleTimeString().slice(0, 5),
senderId: peerId,
read: true,
@@ -731,19 +684,17 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
- friend.lastMessage = `[对方已挂断] ${Math.floor(duration / 60)}分${duration % 60}秒`
+ friend.lastMessage = callMessage.content
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
}
- resetCallState()
+ callStatus.value = "hangup"
callConnectionStatus.value = "对方已挂断"
console.log("对方已挂断通话")
- // 5秒后清除状态
setTimeout(() => {
- callConnectionStatus.value = ""
- }, 5000)
+ resetCallState()
+ }, 3000)
}
}
@@ -753,7 +704,6 @@ export const useChatStore = defineStore("chat", () => {
const peerId = data?.sender_user_id || currentCall.value?.peerId || incomingCall.value?.callerId
if (callId && peerId) {
- // 添加通话记录
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
const duration =
@@ -763,7 +713,7 @@ export const useChatStore = defineStore("chat", () => {
const callMessage = {
type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call",
- content: `[网络中断] ${Math.floor(duration / 60)}分${duration % 60}秒`,
+ content: duration > 0 ? `[网络中断] ${Math.floor(duration / 60)}分${duration % 60}秒` : "[网络中断]",
time: new Date().toLocaleTimeString().slice(0, 5),
senderId: peerId,
read: true,
@@ -772,19 +722,17 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
- friend.lastMessage = `[网络中断] ${Math.floor(duration / 60)}分${duration % 60}秒`
+ friend.lastMessage = callMessage.content
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
}
- resetCallState()
+ callStatus.value = "disconnected"
callConnectionStatus.value = "网络连接已断开"
console.log("网络连接已断开")
- // 5秒后清除状态
setTimeout(() => {
- callConnectionStatus.value = ""
- }, 5000)
+ resetCallState()
+ }, 3000)
}
}
@@ -797,7 +745,6 @@ export const useChatStore = defineStore("chat", () => {
(currentCall.value && currentCall.value.callId === callId) ||
(incomingCall.value && incomingCall.value.callId === callId)
) {
- // 添加通话记录
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
const callMessage = {
@@ -811,7 +758,6 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = "[对方已终止呼叫]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已终止呼叫]", friend.unreadCount)
}
@@ -820,7 +766,6 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = "对方已终止呼叫"
console.log("对方已终止呼叫")
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
@@ -840,7 +785,6 @@ export const useChatStore = defineStore("chat", () => {
return
}
- // 验证用户登录状态
if (!userStore.currentUser?.id) {
alert("用户未登录,无法发起通话")
return
@@ -861,24 +805,30 @@ export const useChatStore = defineStore("chat", () => {
callStatus.value = "calling"
callConnectionStatus.value = "等待对方接听..."
- // 先不获取本地媒体,等对方接听后再获取
- console.log("发起通话,等待对方接听...")
+ // 立即获取本地媒体并显示
+ try {
+ console.log("发起通话,立即获取本地媒体...")
+ await webrtc.getLocalMedia({
+ audio: true,
+ video: type === "video",
+ })
+ console.log("本地媒体获取成功,可以显示自己的摄像头")
+ } catch (error) {
+ console.warn("获取本地媒体失败,但继续通话流程:", error)
+ }
- // 设置30秒超时(无人接听)
+ // 设置30秒超时
setCallTimeout(() => {
if (callStatus.value === "calling") {
resetCallState()
callConnectionStatus.value = "对方无人接听"
console.log("呼叫超时,对方无人接听")
- // 发送无人接听信号 - 使用便捷方法
callAPI.sendNoAnswer(userStore.currentUser.id, peerId, callId, type === "video" ? 6 : 7)
- // 更新好友最后消息为未接来电
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
- // 添加通话记录
const callMessage = {
type: type === "video" ? "video-call" : "audio-call",
content: "[未接来电]",
@@ -890,14 +840,13 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}, 30000)
- // 发送通话请求 - 使用便捷方法
+ // 发送通话请求
try {
console.log("准备发送通话邀请:", {
senderUserId: userStore.currentUser.id,
@@ -913,7 +862,6 @@ export const useChatStore = defineStore("chat", () => {
console.error("发起通话失败:", error)
resetCallState()
- // 提供更友好的错误提示
let errorMessage = "发起通话失败"
if (error.response?.status === 400) {
errorMessage = "请求参数错误,请检查网络连接"
@@ -934,13 +882,11 @@ export const useChatStore = defineStore("chat", () => {
return
}
- // 防止重复接听
if (callStatus.value === "connecting" || callStatus.value === "ongoing") {
console.log("通话已在进行中,忽略重复接听")
return
}
- // 清除任何可能的超时定时器
clearCallTimeout()
console.log("开始接听通话:", incomingCall.value)
@@ -950,12 +896,17 @@ export const useChatStore = defineStore("chat", () => {
try {
// 获取本地媒体
- await webrtc.getLocalMedia({
- audio: true,
- video: incomingCall.value.callType === "video",
- })
+ try {
+ await webrtc.getLocalMedia({
+ audio: true,
+ video: incomingCall.value.callType === "video",
+ })
+ console.log("接听时获取本地媒体成功")
+ } catch (mediaError) {
+ console.warn("接听时获取本地媒体失败,但继续通话流程:", mediaError)
+ }
- // 发送接受信号 - 使用便捷方法
+ // 发送接受信号
await callAPI.accept(
userStore.currentUser.id,
incomingCall.value.callerId,
@@ -985,7 +936,6 @@ export const useChatStore = defineStore("chat", () => {
return
}
- // 如果已经在连接中,不允许拒绝
if (callStatus.value === "connecting" || callStatus.value === "ongoing") {
console.log("通话已在进行中,无法拒绝")
return
@@ -994,7 +944,6 @@ export const useChatStore = defineStore("chat", () => {
console.log("开始拒绝通话:", reason)
try {
- // 发送拒绝信号 - 使用便捷方法
if (reason === "rejected") {
await callAPI.reject(
userStore.currentUser.id,
@@ -1011,7 +960,6 @@ export const useChatStore = defineStore("chat", () => {
)
}
- // 添加通话记录
const friend = friends.value.find((f) => f.id === incomingCall.value.callerId)
if (friend) {
const content = reason === "busy" ? "[对方忙线]" : reason === "no-answer" ? "[未接来电]" : "[已拒绝]"
@@ -1027,7 +975,6 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = content
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, content, friend.unreadCount)
}
@@ -1035,7 +982,6 @@ export const useChatStore = defineStore("chat", () => {
resetCallState()
callConnectionStatus.value = reason === "busy" ? "对方忙线" : reason === "no-answer" ? "无人接听" : "已拒绝"
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
@@ -1058,10 +1004,8 @@ export const useChatStore = defineStore("chat", () => {
}
try {
- // 发送结束信号 - 使用便捷方法
await callAPI.end(userStore.currentUser.id, peerId, callId, callTypeVal === "video" ? 6 : 7)
- // 添加通话记录
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
const duration =
@@ -1089,7 +1033,6 @@ export const useChatStore = defineStore("chat", () => {
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
- // 更新最后消息
friend.lastMessage = content
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, content, friend.unreadCount)
}
@@ -1097,7 +1040,6 @@ export const useChatStore = defineStore("chat", () => {
resetCallState()
callConnectionStatus.value = reason === "no-answer" ? "对方无人接听" : "通话结束"
- // 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)