对方忙线中
@@ -216,7 +216,6 @@ const callTimer = ref(null);
const isCameraWorking = ref(true);
const isMicWorking = ref(true);
const isDragging = ref(false);
-const isOngoing = ref(false);
// 拖拽相关状态
const position = reactive({ x: 0, y: 0 });
@@ -230,7 +229,7 @@ const remoteVideoPreview = ref(null);
// 获取当前通话状态
const callStatus = computed(() => {
- return chatStore.callError;
+ return chatStore.callStatus;
});
// 连接状态文本
@@ -241,16 +240,16 @@ const connectionText = computed(() => {
}
// 处理忙线状态
- if (chatStore.callError === 'busy') {
+ if (callStatus.value === "busy") {
return '对方忙线中';
}
// 默认状态显示
- if (props.isIncoming && !isOngoing.value) {
+ if (props.isIncoming && callStatus.value !== "ongoing") {
return '来电中...';
}
- if (isOngoing.value) {
+ if (callStatus.value === "ongoing") {
return `通话中 ${formattedDuration.value}`;
}
@@ -354,19 +353,27 @@ const endCall = () => {
if (remoteVideoPreview.value) remoteVideoPreview.value.srcObject = null;
isVisible.value = false;
+ chatStore.endCall();
emit('end-call');
};
// 接听通话
const acceptCall = () => {
- isOngoing.value = true;
+ // 确保只接听一次
+ if (chatStore.callStatus === "ongoing") return;
+
initializeMedia();
+ chatStore.acceptCall();
emit('accept-call');
};
// 拒绝通话
const rejectCall = () => {
+ // 确保只拒绝一次
+ if (chatStore.callStatus === "idle") return;
+
isVisible.value = false;
+ chatStore.rejectCall();
emit('reject-call');
};
@@ -458,9 +465,12 @@ watch(() => webrtc.remoteStream.value, (newStream) => {
// 监听通话状态
watch(() => chatStore.callStatus, (status) => {
- if (status === 'ongoing') {
- isOngoing.value = true;
- } else if (status === 'ended') {
+ if (status === "ongoing") {
+ // 只在状态变化时初始化媒体
+ if (!hasLocalVideo.value) {
+ initializeMedia();
+ }
+ } else if (status === "ended" || status === "idle") {
endCall();
}
});
@@ -473,7 +483,7 @@ onMounted(() => {
window.addEventListener('resize', handleWindowResize);
// 如果是呼出方或已接听,初始化媒体
- if (!props.isIncoming || isOngoing.value) {
+ if (!props.isIncoming || callStatus.value === 'ongoing') {
initializeMedia();
}
});
diff --git a/src/composables/useWebRTC.js b/src/composables/useWebRTC.js
index c2a580a..3ec5bb3 100644
--- a/src/composables/useWebRTC.js
+++ b/src/composables/useWebRTC.js
@@ -1,4 +1,6 @@
import { ref } from "vue"
+import { useChatStore } from "@/stores/chat"
+import { useUserStore } from "@/stores/user"
export function useWebRTC() {
const localStream = ref(null)
@@ -7,19 +9,52 @@ export function useWebRTC() {
const isConnected = ref(false)
const isConnecting = ref(false)
+ const chatStore = useChatStore()
+ const userStore = useUserStore()
+
const configuration = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
- { urls: "stun:stun1.l.google.com:19302" }
+ { urls: "stun:stun1.l.google.com:19302" },
+ { urls: "stun:stun2.l.google.com:19302" },
+ { urls: "stun:stun3.l.google.com:19302" },
+ { urls: "stun:stun4.l.google.com:19302" },
+ // {urls:'stun:stun01.sipphone.com'},
+ // {urls:'stun:stun.ekiga.net'},
+ // {urls:'stun:stun.fwdnet.net'},
+ // {urls:'stun:stun.ideasip.com'},
+ // {urls:'stun:stun.iptel.org'},
+ // {urls:'stun:stun.rixtelecom.se'},
+ // {urls:'stun:stun.schlund.de'},
+ // {urls:'stun:stun.l.google.com:19302'},
+ // {urls:'stun:stun1.l.google.com:19302'},
+ // {urls:'stun:stun2.l.google.com:19302'},
+ // {urls:'stun:stun3.l.google.com:19302'},
+ // {urls:'stun:stun4.l.google.com:19302'},
+ // {urls:'stun:stunserver.org'},
+ // {urls:'stun:stun.softjoys.com'},
+ // {urls:'stun:stun.voiparound.com'},
+ // {urls:'stun:stun.voipbuster.com'},
+ // {urls:'stun:stun.voipstunt.com'},
+ // {urls:'stun:stun.voxgratia.org'},
+ // {urls:'stun:stun.xten.com'},
]
}
- const createPeerConnection = () => {
+ const createPeerConnection = (callId, peerId) => {
peerConnection.value = new RTCPeerConnection(configuration)
peerConnection.value.onicecandidate = (event) => {
if (event.candidate) {
console.log("ICE候选:", event.candidate)
+
+ // 发送ICE候选给对方
+ chatStore.sendCallSignal({
+ call_id: callId,
+ call_status: "candidate",
+ candidate: event.candidate,
+ receiver_user_id: peerId
+ })
}
}
@@ -40,6 +75,7 @@ export function useWebRTC() {
} else if (state === "disconnected" || state === "failed") {
isConnected.value = false
isConnecting.value = false
+ closeConnection();
}
}
@@ -56,9 +92,9 @@ export function useWebRTC() {
}
}
- const createOffer = async () => {
+ const createOffer = async (callId, peerId) => {
if (!peerConnection.value) {
- createPeerConnection()
+ createPeerConnection(callId, peerId)
}
// 添加本地流到连接
@@ -78,9 +114,9 @@ export function useWebRTC() {
return offer
}
- const createAnswer = async (offer) => {
+ const createAnswer = async (offer, callId, peerId) => {
if (!peerConnection.value) {
- createPeerConnection()
+ createPeerConnection(callId, peerId)
}
// 添加本地流到连接
diff --git a/src/stores/chat.js b/src/stores/chat.js
index 10607c1..315596a 100644
--- a/src/stores/chat.js
+++ b/src/stores/chat.js
@@ -1,5 +1,5 @@
import { defineStore } from "pinia"
-import { ref, computed } from "vue"
+import { ref, computed, watch } from "vue"
import { chatDB } from "@/utils/db"
import { useWebRTC } from "@/composables/useWebRTC"
import { useUserStore } from "@/stores/user"
@@ -12,22 +12,82 @@ export const useChatStore = defineStore("chat", () => {
const connectionStatus = ref("disconnected")
const socket = ref(null)
- // 通话相关状态
+ // 通话相关状态增强
const currentCall = ref(null)
const incomingCall = ref(null)
- const callStatus = ref("idle") // idle, calling, ringing, ongoing, ended
- const callError = ref(null) // 错误状态:'rejected', 'no-answer', 'busy'
- const callConnectionStatus = ref("") // 连接状态:等待接听、正在连接等
+ 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 webrtc = useWebRTC()
const userStore = useUserStore()
// 计算属性
const isConnected = computed(() => connectionStatus.value === "connected")
- const isInCall = computed(() => callStatus.value !== "idle")
+ const isInCall = computed(() =>
+ ["calling", "ringing", "connecting", "ongoing"].includes(callStatus.value)
+ )
const isCalling = computed(() => callStatus.value === "calling")
const isRinging = computed(() => callStatus.value === "ringing")
+ const isConnecting = computed(() => callStatus.value === "connecting")
const isOngoingCall = computed(() => callStatus.value === "ongoing")
+ const isDisconnected = computed(() => callStatus.value === "disconnected")
+ const isHangup = computed(() => callStatus.value === "hangup")
+
+ // 监听通话状态变化
+ watch(() => callStatus.value, (newStatus) => {
+ console.log("通话状态变化:", newStatus)
+ switch(newStatus) {
+ case "disconnected":
+ handleCallDisconnected()
+ break
+ case "hangup":
+ handleCallHangup()
+ break
+ }
+ })
+
+ // 重置所有通话状态
+ const resetCallState = () => {
+ clearCallTimeout()
+ callStatus.value = "idle"
+ callError.value = null
+ callConnectionStatus.value = ""
+ currentCall.value = null
+ incomingCall.value = null
+ }
+
+ // 清除通话超时
+ const clearCallTimeout = () => {
+ if (callTimeout.value) {
+ clearTimeout(callTimeout.value)
+ callTimeout.value = null
+ }
+ }
+
+ // 设置通话超时
+ const setCallTimeout = (callback, duration = 30000) => {
+ clearCallTimeout()
+ callTimeout.value = setTimeout(() => {
+ callback()
+ clearCallTimeout()
+ }, duration)
+ }
+
+ // 发送通话信令
+ const sendCallSignal = (signal) => {
+ if (socket.value && socket.value.readyState === WebSocket.OPEN) {
+ const message = {
+ request_type: "call_signal",
+ ...signal,
+ sender_user_id: userStore.currentUser?.id
+ }
+ socket.value.send(JSON.stringify(message))
+ } else {
+ console.error("WebSocket未连接,无法发送通话信令")
+ }
+ }
// 初始化数据库
const initDB = async () => {
@@ -106,24 +166,25 @@ export const useChatStore = defineStore("chat", () => {
const addMessage = async (message, currentUserId) => {
messages.value.push(message)
- if (currentFriend.value) {
+ // 更新好友列表中的最后消息
+ const friend = friends.value.find((f) => (f.id === message.senderId || f.id === currentFriend.value?.id))
+ if (friend) {
+ let lastMessage = message.content
+ if (message.type === "image") lastMessage = "[图片]"
+ if (message.type === "video") lastMessage = "[视频]"
+ if (message.type === "audio") lastMessage = "[语音]"
+ if (message.type === "video-call" || message.type === "audio-call") lastMessage = "[通话]"
+
+ 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)
-
- // 更新好友列表中的最后消息
- const friend = friends.value.find((f) => f.id === currentFriend.value.id)
- if (friend) {
- let lastMessage = message.content
- if (message.type === "image") lastMessage = "[图片]"
- if (message.type === "video") lastMessage = "[视频]"
- if (message.type === "audio") lastMessage = "[语音]"
-
- friend.lastMessage = lastMessage.length > 20 ? lastMessage.substring(0, 20) + "..." : lastMessage
-
- // 更新数据库中的好友信息
- await chatDB.updateFriendLastMessage(friend.id, currentUserId, friend.lastMessage, 0)
- }
} catch (error) {
console.error("保存消息失败:", error)
}
@@ -172,18 +233,22 @@ export const useChatStore = defineStore("chat", () => {
const friend = friends.value.find((f) => f.id == senderId)
if (friend) {
friend.unreadCount = (friend.unreadCount || 0) + 1
-
- // 更新最后消息
- let lastMessage = newMessage.content
- if (newMessage.type === "image") lastMessage = "[图片]"
- if (newMessage.type === "video") lastMessage = "[视频]"
- if (newMessage.type === "audio") lastMessage = "[语音]"
- friend.lastMessage = lastMessage
-
- // 更新数据库
- await chatDB.updateFriendLastMessage(friend.id, currentUserId, lastMessage, friend.unreadCount)
}
}
+
+ // 更新最后消息
+ const friend = friends.value.find((f) => f.id == senderId)
+ if (friend) {
+ let lastMessage = newMessage.content
+ if (newMessage.type === "image") lastMessage = "[图片]"
+ if (newMessage.type === "video") lastMessage = "[视频]"
+ if (newMessage.type === "audio") lastMessage = "[语音]"
+ if (newMessage.type === "video-call" || newMessage.type === "audio-call") lastMessage = "[通话]"
+ friend.lastMessage = lastMessage
+
+ // 更新数据库
+ await chatDB.updateFriendLastMessage(friend.id, currentUserId, lastMessage, friend.unreadCount)
+ }
} catch (error) {
console.error("处理接收消息失败:", error)
}
@@ -202,6 +267,7 @@ export const useChatStore = defineStore("chat", () => {
// 处理通话信令
const handleCallSignal = (data) => {
console.log("处理通话信令:", data)
+ console.log("通话状态:", data.call_status)
// 根据通话状态处理
switch (data.call_status) {
@@ -220,22 +286,79 @@ export const useChatStore = defineStore("chat", () => {
case "candidate":
handleIceCandidate(data)
break
- case "no-answer": // 添加无人接听状态
+ case "no-answer":
handleCallNoAnswer(data)
break
- case "connecting": // 新增连接状态
- callConnectionStatus.value = "正在连接..."
+ case "connecting":
+ handleCallConnecting(data)
break
- case "busy": // 对方忙线中
+ case "busy":
handleCallBusy(data)
break
+ case "hangup": // 对方主动挂断
+ handleCallHangup(data)
+ break
+ case "disconnected": // 对方掉线
+ handleCallDisconnected(data)
+ break
+ case "terminated": // 对方终止呼叫
+ handleCallTerminated(data)
+ break
+ case "failed":
+ handleCallFailed(data)
+ break
+ }
+ }
+
+ // 处理连接中状态
+ const handleCallConnecting = (data) => {
+ callConnectionStatus.value = "正在连接..."
+ if (currentCall.value?.callId === data.call_id) {
+ callStatus.value = "connecting"
+ }
+ }
+
+ // 处理通话失败
+ const handleCallFailed = (data) => {
+ if ((currentCall.value && currentCall.value.callId === data.call_id) ||
+ (incomingCall.value && incomingCall.value.callId === data.call_id)) {
+ callError.value = "failed"
+ callConnectionStatus.value = "连接失败"
+
+ // 添加通话记录
+ const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
+ const friend = friends.value.find(f => f.id === peerId)
+ if (friend) {
+ const callMessage = {
+ type: currentCall.value?.callType === "video" || incomingCall.value?.callType === "video" ?
+ "video-call" : "audio-call",
+ content: "[连接失败]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: peerId,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = "[连接失败]"
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[连接失败]", friend.unreadCount)
+ }
+
+ // 5秒后清除状态
+ setTimeout(() => {
+ resetCallState()
+ }, 5000)
}
}
// 处理来电
const handleIncomingCall = (data) => {
- // 如果自己正在通话中,自动拒绝并发送忙线状态
+ // 只有在当前没有通话时才处理来电
if (isInCall.value) {
+ // console.log 输出当前状态
+ console.log("当前正在通话中,发送忙线状态")
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: data.sender_user_id,
@@ -249,11 +372,26 @@ export const useChatStore = defineStore("chat", () => {
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: "[未接来电]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: data.sender_user_id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
}
return
}
+ // 重置所有通话状态
+ resetCallState();
+
incomingCall.value = {
callId: data.call_id,
callerId: data.sender_user_id,
@@ -280,16 +418,31 @@ export const useChatStore = defineStore("chat", () => {
// 处理通话拒绝
const handleCallRejected = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- callStatus.value = "ended"
- callError.value = "rejected"
+ resetCallState();
callConnectionStatus.value = "对方已拒绝"
- currentCall.value = null
console.log("通话已被拒绝")
+ // 添加通话记录
+ const friend = friends.value.find(f => f.id === currentCall.value.peerId)
+ if (friend) {
+ const callMessage = {
+ type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
+ content: "[对方已拒绝]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = "[对方已拒绝]"
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已拒绝]", friend.unreadCount)
+ }
+
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
- callError.value = null
callConnectionStatus.value = ""
}, 5000)
}
@@ -299,16 +452,38 @@ export const useChatStore = defineStore("chat", () => {
const handleCallEnded = (data) => {
if ((currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)) {
- callStatus.value = "ended"
- callError.value = null
+ // 添加通话记录
+ const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
+ const friend = friends.value.find(f => f.id === peerId)
+ if (friend) {
+ const duration = callStatus.value === "ongoing" ?
+ Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0
+
+ const callMessage = {
+ type: currentCall.value?.callType === "video" || incomingCall.value?.callType === "video" ?
+ "video-call" : "audio-call",
+ content: callStatus.value === "ongoing" ?
+ `[${currentCall.value?.callType === "video" ? "视频" : "语音"}通话] ${Math.floor(duration/60)}分${duration%60}秒` :
+ "[通话结束]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: peerId,
+ read: true,
+ timestamp: Date.now(),
+ duration: duration
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = callMessage.content
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, callMessage.content, friend.unreadCount)
+ }
+
+ resetCallState();
callConnectionStatus.value = "通话已结束"
- currentCall.value = null
- incomingCall.value = null
console.log("通话已结束")
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
callConnectionStatus.value = ""
}, 5000)
}
@@ -316,25 +491,43 @@ export const useChatStore = defineStore("chat", () => {
// 处理ICE候选
const handleIceCandidate = (data) => {
- if (currentCall.value && currentCall.value.callId === data.call_id) {
- console.log("收到ICE候选:", data.content)
- // 这里可以添加WebRTC处理逻辑
+ if ((currentCall.value && currentCall.value.callId === data.call_id) ||
+ (incomingCall.value && incomingCall.value.callId === data.call_id)) {
+ console.log("收到ICE候选:", data.candidate)
+
+ // 添加到WebRTC连接
+ webrtc.addIceCandidate(data.candidate)
}
}
// 处理无人接听
const handleCallNoAnswer = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- callStatus.value = "ended"
- callError.value = "no-answer"
+ // 添加未接来电记录
+ const friend = friends.value.find(f => f.id === currentCall.value.peerId)
+ if (friend) {
+ const callMessage = {
+ type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
+ content: "[未接来电]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = "[未接来电]"
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
+ }
+
+ resetCallState();
callConnectionStatus.value = "对方无人接听"
- currentCall.value = null
console.log("对方无人接听")
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
- callError.value = null
callConnectionStatus.value = ""
}, 5000)
}
@@ -343,16 +536,152 @@ export const useChatStore = defineStore("chat", () => {
// 处理对方忙线
const handleCallBusy = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
- callStatus.value = "ended"
- callError.value = "busy"
+ // 添加忙线记录
+ const friend = friends.value.find(f => f.id === currentCall.value.peerId)
+ if (friend) {
+ const callMessage = {
+ type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
+ content: "[对方忙线]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = "[对方忙线]"
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方忙线]", friend.unreadCount)
+ }
+
+ resetCallState();
callConnectionStatus.value = "对方忙线中"
- currentCall.value = null
console.log("对方忙线中")
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
- callError.value = null
+ callConnectionStatus.value = ""
+ }, 5000)
+ }
+ }
+
+ // 处理对方挂断
+ const handleCallHangup = (data) => {
+ const callId = data.call_id
+ const peerId = data.sender_user_id
+
+ if ((currentCall.value && currentCall.value.callId === callId) ||
+ (incomingCall.value && incomingCall.value.callId === callId)) {
+
+ // 添加通话记录
+ const friend = friends.value.find(f => f.id === peerId)
+ if (friend) {
+ const duration = callStatus.value === "ongoing" ?
+ Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0
+
+ const callMessage = {
+ type: currentCall.value?.callType || incomingCall.value?.callType === "video" ?
+ "video-call" : "audio-call",
+ content: `[对方已挂断] ${Math.floor(duration/60)}分${duration%60}秒`,
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: peerId,
+ read: true,
+ timestamp: Date.now(),
+ duration: duration
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = `[对方已挂断] ${Math.floor(duration/60)}分${duration%60}秒`
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
+ }
+
+ resetCallState()
+ callConnectionStatus.value = "对方已挂断"
+ console.log("对方已挂断通话")
+
+ // 5秒后清除状态
+ setTimeout(() => {
+ callConnectionStatus.value = ""
+ }, 5000)
+ }
+ }
+
+ // 处理对方掉线
+ const handleCallDisconnected = (data) => {
+ const callId = data?.call_id || currentCall.value?.callId || incomingCall.value?.callId
+ 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 = callStatus.value === "ongoing" ?
+ Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0
+
+ const callMessage = {
+ type: currentCall.value?.callType || incomingCall.value?.callType === "video" ?
+ "video-call" : "audio-call",
+ content: `[网络中断] ${Math.floor(duration/60)}分${duration%60}秒`,
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: peerId,
+ read: true,
+ timestamp: Date.now(),
+ duration: duration
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = `[网络中断] ${Math.floor(duration/60)}分${duration%60}秒`
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
+ }
+
+ resetCallState()
+ callConnectionStatus.value = "网络连接已断开"
+ console.log("网络连接已断开")
+
+ // 5秒后清除状态
+ setTimeout(() => {
+ callConnectionStatus.value = ""
+ }, 5000)
+ }
+ }
+
+ // 处理对方终止呼叫
+ const handleCallTerminated = (data) => {
+ const callId = data.call_id
+ const peerId = data.sender_user_id
+
+ if ((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 = {
+ type: currentCall.value?.callType || incomingCall.value?.callType === "video" ?
+ "video-call" : "audio-call",
+ content: "[对方已终止呼叫]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: peerId,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = "[对方已终止呼叫]"
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已终止呼叫]", friend.unreadCount)
+ }
+
+ resetCallState()
+ callConnectionStatus.value = "对方已终止呼叫"
+ console.log("对方已终止呼叫")
+
+ // 5秒后清除状态
+ setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
@@ -371,6 +700,8 @@ export const useChatStore = defineStore("chat", () => {
return
}
+ resetCallState()
+
const callId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
currentCall.value = {
@@ -382,14 +713,32 @@ export const useChatStore = defineStore("chat", () => {
}
callStatus.value = "calling"
- callError.value = null
callConnectionStatus.value = "等待对方接听..."
+ // 创建WebRTC连接
+ webrtc.createPeerConnection(callId, peerId, () => {
+ // WebRTC连接成功回调
+ callStatus.value = "ongoing"
+ callConnectionStatus.value = "通话中"
+ clearCallTimeout()
+ }, (error) => {
+ // WebRTC连接失败回调
+ console.error("WebRTC连接失败:", error)
+ sendMessage({
+ senderId: userStore.currentUser?.id,
+ receiverId: peerId,
+ type: type === "video" ? "video-call" : "audio-call",
+ callId: callId,
+ callStatus: "failed"
+ })
+ resetCallState()
+ callConnectionStatus.value = "连接失败"
+ })
+
// 设置30秒超时(无人接听)
- const timeoutId = setTimeout(() => {
+ setCallTimeout(() => {
if (callStatus.value === "calling") {
- callStatus.value = "ended"
- callError.value = "no-answer"
+ resetCallState()
callConnectionStatus.value = "对方无人接听"
console.log("呼叫超时,对方无人接听")
@@ -406,17 +755,25 @@ export const useChatStore = defineStore("chat", () => {
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
+ // 添加通话记录
+ const callMessage = {
+ type: type === "video" ? "video-call" : "audio-call",
+ content: "[未接来电]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
- currentCall.value = null
callConnectionStatus.value = ""
}, 5000)
}
}, 30000)
- currentCall.value.timeoutId = timeoutId
-
// 发送通话请求
sendMessage({
senderId: userStore.currentUser?.id,
@@ -433,37 +790,67 @@ export const useChatStore = defineStore("chat", () => {
const acceptCall = () => {
if (!incomingCall.value) return
- // 清除可能存在的超时计时器
- if (currentCall.value?.timeoutId) {
- clearTimeout(currentCall.value.timeoutId)
- }
-
- // 发送接受信号
+ // 发送连接中信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: incomingCall.value.callerId,
type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
callId: incomingCall.value.callId,
- callStatus: "accepted"
+ callStatus: "connecting"
})
- currentCall.value = {
- callId: incomingCall.value.callId,
- peerId: incomingCall.value.callerId,
- callType: incomingCall.value.callType,
- startTime: Date.now(),
- status: 'ongoing'
- }
+ callStatus.value = "connecting"
+ callConnectionStatus.value = "正在连接..."
- callStatus.value = "ongoing"
- callConnectionStatus.value = "通话中"
- incomingCall.value = null
- callError.value = null
- console.log("接听通话")
+ // 创建WebRTC连接
+ webrtc.createPeerConnection(
+ incomingCall.value.callId,
+ incomingCall.value.callerId,
+ () => {
+ // WebRTC连接成功回调
+ callStatus.value = "ongoing"
+ callConnectionStatus.value = "通话中"
+
+ // 发送接受信号
+ sendMessage({
+ senderId: userStore.currentUser?.id,
+ receiverId: incomingCall.value.callerId,
+ type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
+ callId: incomingCall.value.callId,
+ callStatus: "accepted"
+ })
+
+ currentCall.value = {
+ callId: incomingCall.value.callId,
+ peerId: incomingCall.value.callerId,
+ callType: incomingCall.value.callType,
+ startTime: Date.now(),
+ status: 'ongoing'
+ }
+
+ incomingCall.value = null
+ console.log("通话已连接")
+ },
+ (error) => {
+ // WebRTC连接失败回调
+ console.error("WebRTC连接失败:", error)
+ sendMessage({
+ senderId: userStore.currentUser?.id,
+ receiverId: incomingCall.value.callerId,
+ type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
+ callId: incomingCall.value.callId,
+ callStatus: "failed"
+ })
+ resetCallState()
+ callConnectionStatus.value = "连接失败"
+ }
+ )
+
+ console.log("正在连接通话...")
}
// 拒绝来电
- const rejectCall = () => {
+ const rejectCall = (reason = "rejected") => {
if (!incomingCall.value) return
// 发送拒绝信号
@@ -472,26 +859,35 @@ export const useChatStore = defineStore("chat", () => {
receiverId: incomingCall.value.callerId,
type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
callId: incomingCall.value.callId,
- callStatus: "rejected"
+ callStatus: reason // 可以是'rejected'或'busy'
})
- callStatus.value = "ended"
- callConnectionStatus.value = "已拒绝"
- callError.value = "rejected"
-
- // 更新好友最后消息为已拒绝
+ // 添加通话记录
const friend = friends.value.find(f => f.id === incomingCall.value.callerId)
if (friend) {
- friend.lastMessage = "[已拒绝]"
- chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[已拒绝]", friend.unreadCount)
+ const content = reason === "busy" ? "[对方忙线]" : "[已拒绝]"
+
+ const callMessage = {
+ type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
+ content: content,
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = content
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, content, friend.unreadCount)
}
- incomingCall.value = null
+ resetCallState()
+ callConnectionStatus.value = reason === "busy" ? "对方忙线" : "已拒绝"
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
- callError.value = null
callConnectionStatus.value = ""
}, 5000)
@@ -499,41 +895,157 @@ export const useChatStore = defineStore("chat", () => {
}
// 结束通话
- const endCall = () => {
+ const endCall = (reason = "ended") => {
const callId = currentCall.value?.callId || incomingCall.value?.callId
const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
const callTypeVal = currentCall.value?.callType || incomingCall.value?.callType
if (!callId || !peerId) return
- // 清除超时计时器
- if (currentCall.value?.timeoutId) {
- clearTimeout(currentCall.value.timeoutId)
- }
-
// 发送结束信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
type: callTypeVal === "video" ? "video-call" : "audio-call",
callId: callId,
- callStatus: "ended"
+ callStatus: reason // 可以是'ended'或'no-answer'
})
- callStatus.value = "ended"
- callConnectionStatus.value = "通话结束"
+ // 添加通话记录
+ const friend = friends.value.find(f => f.id === peerId)
+ if (friend) {
+ const duration = callStatus.value === "ongoing" ?
+ Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0
+
+ let content = ""
+ if (reason === "no-answer") {
+ content = "[未接来电]"
+ } else if (duration > 0) {
+ content = `[${callTypeVal === "视频" ? "视频" : "语音"}通话] ${Math.floor(duration/60)}分${duration%60}秒`
+ } else {
+ content = "[通话结束]"
+ }
+
+ const callMessage = {
+ type: callTypeVal === "video" ? "video-call" : "audio-call",
+ content: content,
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: duration
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = content
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, content, friend.unreadCount)
+ }
+
+ resetCallState()
+ callConnectionStatus.value = reason === "no-answer" ? "对方无人接听" : "通话结束"
+
// 5秒后清除状态
setTimeout(() => {
- callStatus.value = "idle"
- currentCall.value = null
- incomingCall.value = null
- callError.value = null
callConnectionStatus.value = ""
}, 5000)
console.log("结束通话")
}
+ // 主动挂断通话(用户点击挂断按钮)
+ const hangupCall = () => {
+ const callId = currentCall.value?.callId || incomingCall.value?.callId
+ const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
+ const callTypeVal = currentCall.value?.callType || incomingCall.value?.callType
+
+ if (!callId || !peerId) return
+
+ // 发送挂断信号
+ sendMessage({
+ senderId: userStore.currentUser?.id,
+ receiverId: peerId,
+ type: callTypeVal === "video" ? "video-call" : "audio-call",
+ callId: callId,
+ callStatus: "hangup"
+ })
+
+ // 添加通话记录
+ const friend = friends.value.find(f => f.id === peerId)
+ if (friend) {
+ const duration = callStatus.value === "ongoing" ?
+ Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0
+
+ const callMessage = {
+ type: callTypeVal === "video" ? "video-call" : "audio-call",
+ content: `[已挂断] ${Math.floor(duration/60)}分${duration%60}秒`,
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: duration
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = `[已挂断] ${Math.floor(duration/60)}分${duration%60}秒`
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
+ }
+
+ resetCallState()
+ callConnectionStatus.value = "已挂断"
+
+ // 5秒后清除状态
+ setTimeout(() => {
+ callConnectionStatus.value = ""
+ }, 5000)
+
+ console.log("主动挂断通话")
+ }
+
+ // 终止呼叫(在对方接听前取消)
+ const terminateCall = () => {
+ if (!currentCall.value) return
+
+ // 发送终止信号
+ sendMessage({
+ senderId: userStore.currentUser?.id,
+ receiverId: currentCall.value.peerId,
+ type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
+ callId: currentCall.value.callId,
+ callStatus: "terminated"
+ })
+
+ // 添加通话记录
+ const friend = friends.value.find(f => f.id === currentCall.value.peerId)
+ if (friend) {
+ const callMessage = {
+ type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
+ content: "[已取消]",
+ time: new Date().toLocaleTimeString().slice(0, 5),
+ senderId: userStore.currentUser?.id,
+ read: true,
+ timestamp: Date.now(),
+ duration: 0
+ }
+ chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
+
+ // 更新最后消息
+ friend.lastMessage = "[已取消]"
+ chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[已取消]", friend.unreadCount)
+ }
+
+ resetCallState()
+ callConnectionStatus.value = "已取消呼叫"
+
+ // 5秒后清除状态
+ setTimeout(() => {
+ callConnectionStatus.value = ""
+ }, 5000)
+
+ console.log("终止呼叫")
+ }
+
return {
friends,
currentFriend,
@@ -549,7 +1061,10 @@ export const useChatStore = defineStore("chat", () => {
isInCall,
isCalling,
isRinging,
+ isConnecting,
isOngoingCall,
+ isDisconnected,
+ isHangup,
initDB,
loadFriends,
setCurrentFriend,
@@ -560,6 +1075,11 @@ export const useChatStore = defineStore("chat", () => {
startCall,
acceptCall,
rejectCall,
- endCall
+ endCall,
+ hangupCall,
+ terminateCall,
+ resetCallState,
+ sendCallSignal,
+ clearCallTimeout
}
})
\ No newline at end of file
diff --git a/src/views/Chat.vue b/src/views/Chat.vue
index 2d1f950..0da62f2 100644
--- a/src/views/Chat.vue
+++ b/src/views/Chat.vue
@@ -93,7 +93,8 @@ const handleNavChange = (nav) => {
// 监听通话状态变化
watch(() => chatStore.callStatus, (status) => {
- showVideoCall.value = status !== 'idle';
+ // 只有当通话状态是活跃状态时才显示视频组件
+ showVideoCall.value = status === "connecting" || status === "calling" || status === "ringing" || status === "ongoing";
});
// 监听来电
@@ -113,6 +114,9 @@ watch(() => chatStore.currentCall, (call) => {
callerInfo.value = chatStore.friends.find(f => f.id === call.peerId);
callType.value = call.callType;
isIncoming.value = false;
+ } else {
+ // 当通话结束时重置状态
+ showVideoCall.value = false;
}
});
@@ -126,10 +130,11 @@ const handleRejectCall = () => {
chatStore.rejectCall();
};
-// 处理结束通话
+// 处理结束通话 - 修复挂断逻辑
const handleEndCall = () => {
chatStore.endCall();
};
+
// 加载主题
themeStore.loadTheme();
@@ -150,7 +155,6 @@ onUnmounted(() => {
disconnectWebSocket();
});
-