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 @@
@@ -36,6 +35,14 @@
+ +
+
+ + {{ friendlyMessage }} +
+
+
@@ -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)