Files
nl-im-pc/src/stores/chat.js
liqi 95ddd9925c 修复:
1. 本地视频流能获取到了
2. 不知道为什么偶尔能连接成功

现有问题:
1. 不稳定,连接不是每次都成功10%大约
2. 对方获取不到本地推送
2025-07-09 14:05:12 +08:00

1132 lines
36 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from "pinia"
import { ref, computed, watch } from "vue"
import { chatDB } from "@/utils/db"
import { useWebRTC } from "@/composables/useWebRTC"
import { useUserStore } from "@/stores/user"
import { sendCallSignal, callAPI } from "@/utils/request.js"
export const useChatStore = defineStore("chat", () => {
const friends = ref([])
const currentFriend = ref(null)
const messages = ref([])
const connectionStatus = ref("disconnected")
const socket = ref(null)
// 通话相关状态增强
const currentCall = ref(null)
const incomingCall = ref(null)
const callStatus = ref("idle") // idle, calling, ringing, connecting, ongoing, ended, disconnected, hangup
const callError = ref(null)
const callConnectionStatus = ref("")
const callTimeout = ref(null)
const webrtc = useWebRTC()
const userStore = useUserStore()
// 计算属性
const isConnected = computed(() => connectionStatus.value === "connected")
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)
},
)
// 重置所有通话状态
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 sendCallSignalInternal = async (signal) => {
try {
console.log("发送通话信令:", signal)
if (!userStore.currentUser?.id) {
throw new Error("用户未登录")
}
if (!signal.receiver_user_id) {
throw new Error("缺少接收方用户ID")
}
if (!signal.call_id) {
throw new Error("缺少通话ID")
}
if (!signal.call_status) {
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),
message_content: JSON.stringify(signal.data || {}),
call_id: signal.call_id,
call_status: signal.call_status,
}
console.log("发送通话信令请求数据:", requestData)
const response = await sendCallSignal(requestData)
console.log("发送通话信令成功:", signal)
return response
} catch (error) {
console.error("发送通话信令失败:", error)
throw error
}
}
// 初始化数据库
const initDB = async () => {
try {
await chatDB.init()
console.log("数据库初始化成功")
} catch (error) {
console.error("数据库初始化失败:", error)
}
}
// 加载好友列表
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(
{
...friend,
lastMessage: "",
unreadCount: 0,
},
currentUserId,
)
}
}
friends.value = friendList
} catch (error) {
console.error("加载好友列表失败:", error)
const friendList = presetUsers.filter((user) => user.id !== currentUserId)
friendList.forEach((friend) => {
friend.lastMessage = ""
friend.unreadCount = 0
})
friends.value = friendList
}
}
// 设置当前好友
const setCurrentFriend = (friend) => {
currentFriend.value = friend
if (friend) {
friend.unreadCount = 0
}
}
// 切换当前聊天好友
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)
} catch (error) {
console.error("加载聊天记录失败:", error)
messages.value = []
}
}
// 添加消息
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
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)
} catch (error) {
console.error("保存消息失败:", error)
}
}
}
// 处理接收到的消息
const handleIncomingMessage = async (messageData, currentUserId) => {
if (messageData.isFromHttp) {
console.log("忽略来自HTTP的消息避免循环")
return
}
const senderId = messageData.sender_user_id
const receiverId = messageData.receiver_user_id
if (receiverId !== currentUserId) return
if (messageData.call_id || messageData.call_status) {
handleCallSignal(messageData)
return
}
const newMessage = {
type: getMessageType(messageData.message_type),
content: messageData.content,
time: new Date().toLocaleTimeString().slice(0, 5),
senderId: senderId,
read: false,
duration: messageData.duration || 0,
timestamp: Date.now(),
}
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
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)
}
}
// 根据消息类型编号获取类型字符串
const getMessageType = (type) => {
const types = [
"text",
"image",
"audio",
"video",
"prescription",
"medical-record",
"video-call",
"audio-call",
"file",
]
return types[type] || "text"
}
// 处理通话信令
const handleCallSignal = (data) => {
console.log("处理通话信令:", data)
console.log("通话状态:", data.call_status)
switch (data.call_status) {
case "invite":
handleIncomingCall(data)
break
case "accepted":
handleCallAccepted(data)
break
case "rejected":
handleCallRejected(data)
break
case "ended":
handleCallEnded(data)
break
case "offer":
handleOffer(data)
break
case "answer":
handleAnswer(data)
break
case "candidate":
handleIceCandidate(data)
break
case "no-answer":
handleCallNoAnswer(data)
break
case "connecting":
handleCallConnecting(data)
break
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
}
}
// 处理Offer信令
const handleOffer = async (data) => {
if (incomingCall.value && incomingCall.value.callId === data.call_id) {
if (callStatus.value === "ongoing") {
console.log("通话已建立,忽略重复的 Offer")
return
}
try {
const offer = JSON.parse(data.content || data.message_content || "{}")
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
console.log("📥 收到对方 OFFER 信令:")
console.log("📋 Offer 类型:", offer.type)
console.log("📋 Offer SDP (前200字符):", offer.sdp ? offer.sdp.substring(0, 200) + "..." : "无SDP")
console.log("📋 通话ID:", data.call_id)
console.log("📋 发送方:", data.sender_user_id)
console.log("📋 完整 Offer 对象:", offer)
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
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)
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
console.log("📤 准备发送 ANSWER 信令:")
console.log("📋 Answer 类型:", answer.type)
console.log("📋 Answer SDP (前200字符):", answer.sdp ? answer.sdp.substring(0, 200) + "..." : "无SDP")
console.log("📋 通话ID:", data.call_id)
console.log("📋 接收方:", data.sender_user_id)
console.log("📋 完整 Answer 对象:", answer)
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
await callAPI.sendAnswer(userStore.currentUser.id, data.sender_user_id, data.call_id, answer, data.message_type)
console.log("✅ 发送Answer成功")
} catch (error) {
console.error("处理Offer失败:", error)
rejectCall("failed")
}
}
}
// 处理Answer信令
const handleAnswer = async (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
if (callStatus.value === "ongoing") {
console.log("通话已建立,忽略重复的 Answer")
return
}
try {
const answer = JSON.parse(data.content || data.message_content || "{}")
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
console.log("📥 收到对方 ANSWER 信令:")
console.log("📋 Answer 类型:", answer.type)
console.log("📋 Answer SDP (前200字符):", answer.sdp ? answer.sdp.substring(0, 200) + "..." : "无SDP")
console.log("📋 通话ID:", data.call_id)
console.log("📋 发送方:", data.sender_user_id)
console.log("📋 完整 Answer 对象:", answer)
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
await webrtc.setRemoteAnswer(answer)
callStatus.value = "connecting"
callConnectionStatus.value = "正在建立连接..."
console.log("✅ Answer处理成功开始建立连接")
} catch (error) {
console.error("处理Answer失败:", error)
endCall("failed")
}
}
}
// 处理连接中状态
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)
}
setTimeout(() => {
resetCallState()
}, 5000)
}
}
// 处理来电
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: "[未接来电]",
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,
callType: data.message_type === 6 ? "video" : "audio",
content: data.content,
}
callStatus.value = "ringing"
callError.value = null
callConnectionStatus.value = "收到来电"
console.log("收到来电:", incomingCall.value)
setCallTimeout(() => {
if (callStatus.value === "ringing" && incomingCall.value) {
console.log("来电超时,自动拒绝")
rejectCall("no-answer")
}
}, 30000)
}
// 处理通话接受
const handleCallAccepted = async (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
if (callStatus.value === "connecting" || callStatus.value === "ongoing") {
console.log("通话已在连接中,忽略重复的 accepted 信令")
return
}
callStatus.value = "connecting"
callError.value = null
callConnectionStatus.value = "对方已接听,正在连接..."
console.log("通话已被接受,开始交换信令")
// 显示友好提示
setTimeout(() => {
if (callStatus.value === "connecting") {
callConnectionStatus.value = "对方已接听,正在建立连接..."
}
}, 1000)
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)
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
console.log("📤 准备发送 OFFER 信令:")
console.log("📋 Offer 类型:", offer.type)
console.log("📋 Offer SDP (前200字符):", offer.sdp ? offer.sdp.substring(0, 200) + "..." : "无SDP")
console.log("📋 通话ID:", currentCall.value.callId)
console.log("📋 接收方:", currentCall.value.peerId)
console.log("📋 完整 Offer 对象:", offer)
console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯")
await callAPI.sendOffer(
userStore.currentUser.id,
currentCall.value.peerId,
currentCall.value.callId,
offer,
currentCall.value.callType === "video" ? 6 : 7,
)
console.log("✅ 发送Offer成功")
} catch (error) {
console.error("创建Offer失败:", error)
endCall("failed")
}
}
}
// 处理通话拒绝
const handleCallRejected = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
resetCallState()
callConnectionStatus.value = "对方已拒绝"
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)
}
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理通话结束
const handleCallEnded = (data) => {
if (
(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) {
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 = "通话已结束"
console.log("通话已结束")
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理ICE候选
const handleIceCandidate = (data) => {
if (
(currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)
) {
try {
const candidate = JSON.parse(data.content || data.message_content || "{}")
console.log("收到ICE候选:", candidate)
webrtc.addIceCandidate(candidate)
} catch (error) {
console.error("处理ICE候选失败:", error)
}
}
}
// 处理无人接听
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 = {
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 = "对方无人接听"
console.log("对方无人接听")
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方忙线
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 = {
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 = "对方忙线中"
console.log("对方忙线中")
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方挂断
const handleCallHangup = (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: duration > 0 ? `[对方已挂断] ${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, friend.lastMessage, friend.unreadCount)
}
callStatus.value = "hangup"
callConnectionStatus.value = "对方已挂断"
console.log("对方已挂断通话")
setTimeout(() => {
resetCallState()
}, 3000)
}
}
// 处理对方掉线
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: duration > 0 ? `[网络中断] ${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, friend.lastMessage, friend.unreadCount)
}
callStatus.value = "disconnected"
callConnectionStatus.value = "网络连接已断开"
console.log("网络连接已断开")
setTimeout(() => {
resetCallState()
}, 3000)
}
}
// 处理对方终止呼叫
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("对方已终止呼叫")
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 发起通话
const startCall = async (peerId, type) => {
if (isInCall.value) {
alert("您正在通话中,请先结束当前通话。")
return
}
const friend = friends.value.find((f) => f.id === peerId)
if (!friend) {
alert("好友信息无效")
return
}
if (!userStore.currentUser?.id) {
alert("用户未登录,无法发起通话")
return
}
resetCallState()
const callId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
currentCall.value = {
callId: callId,
peerId: peerId,
callType: type,
startTime: Date.now(),
status: "calling",
}
callStatus.value = "calling"
callConnectionStatus.value = "等待对方接听..."
// 立即获取本地媒体并显示
try {
console.log("发起通话,立即获取本地媒体...")
await webrtc.getLocalMedia({
audio: true,
video: type === "video",
})
console.log("本地媒体获取成功,可以显示自己的摄像头")
} catch (error) {
console.warn("获取本地媒体失败,但继续通话流程:", error)
}
// 设置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: "[未接来电]",
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)
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}, 30000)
// 发送通话请求
try {
console.log("准备发送通话邀请:", {
senderUserId: userStore.currentUser.id,
receiverUserId: peerId,
callId: callId,
callType: type === "video" ? 6 : 7,
})
await callAPI.invite(userStore.currentUser.id, peerId, callId, type === "video" ? 6 : 7)
console.log("发起通话成功:", currentCall.value)
} catch (error) {
console.error("发起通话失败:", error)
resetCallState()
let errorMessage = "发起通话失败"
if (error.response?.status === 400) {
errorMessage = "请求参数错误,请检查网络连接"
} else if (error.response?.status === 500) {
errorMessage = "服务器错误,请稍后重试"
} else if (error.message) {
errorMessage = error.message
}
alert(errorMessage)
}
}
// 接听来电
const acceptCall = async () => {
if (!incomingCall.value) {
console.log("没有来电,无法接听")
return
}
if (callStatus.value === "connecting" || callStatus.value === "ongoing") {
console.log("通话已在进行中,忽略重复接听")
return
}
clearCallTimeout()
console.log("开始接听通话:", incomingCall.value)
callStatus.value = "connecting"
callConnectionStatus.value = "正在连接..."
try {
// 获取本地媒体
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,
incomingCall.value.callId,
incomingCall.value.callType === "video" ? 6 : 7,
)
currentCall.value = {
callId: incomingCall.value.callId,
peerId: incomingCall.value.callerId,
callType: incomingCall.value.callType,
startTime: Date.now(),
status: "connecting",
}
console.log("接听通话成功,等待信令交换...")
} catch (error) {
console.error("接听通话失败:", error)
rejectCall("failed")
}
}
// 拒绝来电
const rejectCall = async (reason = "rejected") => {
if (!incomingCall.value) {
console.log("没有来电,无法拒绝")
return
}
if (callStatus.value === "connecting" || callStatus.value === "ongoing") {
console.log("通话已在进行中,无法拒绝")
return
}
console.log("开始拒绝通话:", reason)
try {
if (reason === "rejected") {
await callAPI.reject(
userStore.currentUser.id,
incomingCall.value.callerId,
incomingCall.value.callId,
incomingCall.value.callType === "video" ? 6 : 7,
)
} else {
await callAPI.sendFailed(
userStore.currentUser.id,
incomingCall.value.callerId,
incomingCall.value.callId,
incomingCall.value.callType === "video" ? 6 : 7,
)
}
const friend = friends.value.find((f) => f.id === incomingCall.value.callerId)
if (friend) {
const content = reason === "busy" ? "[对方忙线]" : reason === "no-answer" ? "[未接来电]" : "[已拒绝]"
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)
}
resetCallState()
callConnectionStatus.value = reason === "busy" ? "对方忙线" : reason === "no-answer" ? "无人接听" : "已拒绝"
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
console.log("拒绝通话成功")
} catch (error) {
console.error("拒绝通话失败:", error)
}
}
// 结束通话
const endCall = async (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) {
resetCallState()
return
}
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 =
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 === "video" ? "视频" : "语音"}通话] ${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" ? "对方无人接听" : "通话结束"
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
console.log("结束通话成功")
} catch (error) {
console.error("结束通话失败:", error)
resetCallState()
}
}
return {
friends,
currentFriend,
messages,
connectionStatus,
socket,
currentCall,
incomingCall,
callStatus,
callError,
callConnectionStatus,
isConnected,
isInCall,
isCalling,
isRinging,
isConnecting,
isOngoingCall,
isDisconnected,
isHangup,
initDB,
loadFriends,
setCurrentFriend,
switchFriend,
addMessage,
handleIncomingMessage,
handleCallSignal,
startCall,
acceptCall,
rejectCall,
endCall,
resetCallState,
sendCallSignal: sendCallSignalInternal,
clearCallTimeout,
}
})