优化交互

This commit is contained in:
2025-07-09 08:18:59 +08:00
parent 36bb210539
commit 3637c8b7c6
6 changed files with 390 additions and 376 deletions

View File

@@ -96,7 +96,7 @@
<!-- 本地视频 -->
<div
class="local-video-container"
v-if="hasLocalVideo && callType === 'video' && isCameraWorking"
v-if="callType === 'video' && isCameraWorking"
>
<video
ref="localVideo"
@@ -292,7 +292,9 @@ const initializeMedia = async () => {
// 获取本地媒体流
await webrtc.getLocalMedia(constraints);
startCallTimer();
if (callStatus.value === "ongoing") {
startCallTimer();
}
} catch (error) {
console.error('无法访问媒体设备:', error);
isCameraOn.value = false;
@@ -362,7 +364,6 @@ const acceptCall = () => {
// 确保只接听一次
if (chatStore.callStatus === "ongoing") return;
initializeMedia();
chatStore.acceptCall();
emit('accept-call');
};
@@ -466,9 +467,9 @@ watch(() => webrtc.remoteStream.value, (newStream) => {
// 监听通话状态
watch(() => chatStore.callStatus, (status) => {
if (status === "ongoing") {
// 只在状态变化时初始化媒体
if (!hasLocalVideo.value) {
initializeMedia();
// 开始计时
if (!callTimer.value) {
startCallTimer();
}
} else if (status === "ended" || status === "idle") {
endCall();
@@ -928,4 +929,4 @@ onUnmounted(() => {
height: 95vh !important;
}
}
</style>
</style>

View File

@@ -19,41 +19,26 @@ export function useWebRTC() {
{ 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 = (callId, peerId) => {
if (peerConnection.value) {
closeConnection()
}
peerConnection.value = new RTCPeerConnection(configuration)
peerConnection.value.onicecandidate = (event) => {
if (event.candidate) {
console.log("ICE候选:", event.candidate)
console.log("发送ICE候选:", event.candidate)
// 发送ICE候选给对方
chatStore.sendCallSignal({
call_id: callId,
call_status: "candidate",
candidate: event.candidate,
receiver_user_id: peerId
receiver_user_id: peerId,
})
}
}
@@ -72,10 +57,30 @@ export function useWebRTC() {
if (state === "connected") {
isConnected.value = true
isConnecting.value = false
chatStore.callStatus = "ongoing"
chatStore.callConnectionStatus = "通话中"
} else if (state === "connecting") {
isConnecting.value = true
chatStore.callConnectionStatus = "正在连接..."
} else if (state === "disconnected" || state === "failed") {
isConnected.value = false
isConnecting.value = false
closeConnection();
if (chatStore.callStatus === "ongoing") {
chatStore.callStatus = "disconnected"
chatStore.callConnectionStatus = "连接已断开"
}
}
}
peerConnection.value.oniceconnectionstatechange = () => {
const state = peerConnection.value.iceConnectionState
console.log("ICE连接状态变化:", state)
if (state === "failed" || state === "disconnected") {
if (chatStore.callStatus === "ongoing") {
chatStore.callStatus = "disconnected"
chatStore.callConnectionStatus = "网络连接已断开"
}
}
}
@@ -84,7 +89,12 @@ export function useWebRTC() {
const getLocalMedia = async (constraints = { audio: true, video: true }) => {
try {
if (localStream.value) {
localStream.value.getTracks().forEach((track) => track.stop())
}
localStream.value = await navigator.mediaDevices.getUserMedia(constraints)
console.log("获取本地媒体成功:", localStream.value)
return localStream.value
} catch (error) {
console.error("获取本地媒体失败:", error)
@@ -104,14 +114,28 @@ export function useWebRTC() {
})
}
const offer = await peerConnection.value.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
})
try {
const offer = await peerConnection.value.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
})
await peerConnection.value.setLocalDescription(offer)
await peerConnection.value.setLocalDescription(offer)
console.log("创建Offer成功:", offer)
return offer
// 发送offer给对方
chatStore.sendCallSignal({
call_id: callId,
call_status: "offer",
offer: offer,
receiver_user_id: peerId,
})
return offer
} catch (error) {
console.error("创建Offer失败:", error)
throw error
}
}
const createAnswer = async (offer, callId, peerId) => {
@@ -126,49 +150,83 @@ export function useWebRTC() {
})
}
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
const answer = await peerConnection.value.createAnswer()
await peerConnection.value.setLocalDescription(answer)
try {
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
const answer = await peerConnection.value.createAnswer()
await peerConnection.value.setLocalDescription(answer)
return answer
console.log("创建Answer成功:", answer)
// 发送answer给对方
chatStore.sendCallSignal({
call_id: callId,
call_status: "answer",
answer: answer,
receiver_user_id: peerId,
})
return answer
} catch (error) {
console.error("创建Answer失败:", error)
throw error
}
}
const setRemoteAnswer = async (answer) => {
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(answer))
try {
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(answer))
console.log("设置远程Answer成功")
} catch (error) {
console.error("设置远程Answer失败:", error)
throw error
}
}
const setRemoteOffer = async (offer) => {
try {
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
console.log("设置远程Offer成功")
} catch (error) {
console.error("设置远程Offer失败:", error)
throw error
}
}
const addIceCandidate = async (candidate) => {
if (peerConnection.value && peerConnection.value.remoteDescription) {
try {
await peerConnection.value.addIceCandidate(new RTCIceCandidate(candidate))
console.log("添加ICE候选成功")
} catch (error) {
console.error("添加ICE候选失败:", error)
}
} else {
console.warn("无法添加ICE候选连接未准备好")
}
}
const closeConnection = () => {
console.log("关闭WebRTC连接")
if (localStream.value) {
localStream.value.getTracks().forEach((track) => {
track.stop();
if (localStream.value) {
localStream.value.removeTrack(track);
}
});
localStream.value = null;
track.stop()
})
localStream.value = null
}
if (peerConnection.value) {
peerConnection.value.onicecandidate = null;
peerConnection.value.ontrack = null;
peerConnection.value.onconnectionstatechange = null;
peerConnection.value.close();
peerConnection.value = null;
peerConnection.value.onicecandidate = null
peerConnection.value.ontrack = null
peerConnection.value.onconnectionstatechange = null
peerConnection.value.oniceconnectionstatechange = null
peerConnection.value.close()
peerConnection.value = null
}
remoteStream.value = null;
isConnected.value = false;
isConnecting.value = false;
remoteStream.value = null
isConnected.value = false
isConnecting.value = false
}
return {
@@ -182,7 +240,8 @@ export function useWebRTC() {
createOffer,
createAnswer,
setRemoteAnswer,
setRemoteOffer,
addIceCandidate,
closeConnection,
}
}
}

View File

@@ -141,7 +141,7 @@ export function useWebSocket() {
return send({
request_type: "call_signal",
...signal,
sender_user_id: userStore.currentUser?.id
sender_user_id: userStore.currentUser?.id,
})
}
@@ -181,4 +181,4 @@ export function useWebSocket() {
bindUser,
generateCallId,
}
}
}

View File

@@ -3,7 +3,6 @@ import { ref, computed, watch } from "vue"
import { chatDB } from "@/utils/db"
import { useWebRTC } from "@/composables/useWebRTC"
import { useUserStore } from "@/stores/user"
import { sendMessage } from "@/utils/request.js"
export const useChatStore = defineStore("chat", () => {
const friends = ref([])
@@ -25,9 +24,7 @@ export const useChatStore = defineStore("chat", () => {
// 计算属性
const isConnected = computed(() => connectionStatus.value === "connected")
const isInCall = computed(() =>
["calling", "ringing", "connecting", "ongoing"].includes(callStatus.value)
)
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")
@@ -36,17 +33,20 @@ export const useChatStore = defineStore("chat", () => {
const isHangup = computed(() => callStatus.value === "hangup")
// 监听通话状态变化
watch(() => callStatus.value, (newStatus) => {
console.log("通话状态变化:", newStatus)
switch(newStatus) {
case "disconnected":
handleCallDisconnected()
break
case "hangup":
handleCallHangup()
break
}
})
watch(
() => callStatus.value,
(newStatus) => {
console.log("通话状态变化:", newStatus)
switch (newStatus) {
case "disconnected":
handleCallDisconnected()
break
case "hangup":
handleCallHangup()
break
}
},
)
// 重置所有通话状态
const resetCallState = () => {
@@ -56,6 +56,7 @@ export const useChatStore = defineStore("chat", () => {
callConnectionStatus.value = ""
currentCall.value = null
incomingCall.value = null
webrtc.closeConnection()
}
// 清除通话超时
@@ -79,11 +80,15 @@ export const useChatStore = defineStore("chat", () => {
const sendCallSignal = (signal) => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
const message = {
request_type: "call_signal",
...signal,
sender_user_id: userStore.currentUser?.id
call_status: signal.call_status,
call_type: signal.call_type || (signal.receiver_user_id ? 6 : 7), // 默认视频通话
call_id: signal.call_id,
caller_id: userStore.currentUser.id,
callee_id: signal.receiver_user_id,
data: JSON.stringify(signal.offer || signal.answer || signal.candidate || {}),
}
socket.value.send(JSON.stringify(message))
console.log("发送通话信令:", message)
} else {
console.error("WebSocket未连接无法发送通话信令")
}
@@ -117,7 +122,7 @@ export const useChatStore = defineStore("chat", () => {
lastMessage: "",
unreadCount: 0,
},
currentUserId
currentUserId,
)
}
}
@@ -167,7 +172,7 @@ export const useChatStore = defineStore("chat", () => {
messages.value.push(message)
// 更新好友列表中的最后消息
const friend = friends.value.find((f) => (f.id === message.senderId || f.id === currentFriend.value?.id))
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 = "[图片]"
@@ -257,9 +262,15 @@ export const useChatStore = defineStore("chat", () => {
// 根据消息类型编号获取类型字符串
const getMessageType = (type) => {
const types = [
"text", "image", "audio", "video",
"prescription", "medical-record",
"video-call", "audio-call", "file"
"text",
"image",
"audio",
"video",
"prescription",
"medical-record",
"video-call",
"audio-call",
"file",
]
return types[type] || "text"
}
@@ -283,6 +294,12 @@ export const useChatStore = defineStore("chat", () => {
case "ended":
handleCallEnded(data)
break
case "offer":
handleOffer(data)
break
case "answer":
handleAnswer(data)
break
case "candidate":
handleIceCandidate(data)
break
@@ -310,6 +327,44 @@ export const useChatStore = defineStore("chat", () => {
}
}
// 处理Offer信令
const handleOffer = async (data) => {
if (incomingCall.value && incomingCall.value.callId === data.call_id) {
try {
const offer = JSON.parse(data.data)
console.log("收到Offer:", offer)
// 设置远程Offer并创建Answer
await webrtc.setRemoteOffer(offer)
const answer = await webrtc.createAnswer(offer, data.call_id, data.caller_id)
console.log("发送Answer:", answer)
} catch (error) {
console.error("处理Offer失败:", error)
rejectCall("failed")
}
}
}
// 处理Answer信令
const handleAnswer = async (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
try {
const answer = JSON.parse(data.data)
console.log("收到Answer:", answer)
// 设置远程Answer
await webrtc.setRemoteAnswer(answer)
callStatus.value = "connecting"
callConnectionStatus.value = "正在建立连接..."
} catch (error) {
console.error("处理Answer失败:", error)
endCall("failed")
}
}
}
// 处理连接中状态
const handleCallConnecting = (data) => {
callConnectionStatus.value = "正在连接..."
@@ -320,24 +375,28 @@ export const useChatStore = defineStore("chat", () => {
// 处理通话失败
const handleCallFailed = (data) => {
if ((currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)) {
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)
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",
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
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -357,31 +416,28 @@ export const useChatStore = defineStore("chat", () => {
const handleIncomingCall = (data) => {
// 只有在当前没有通话时才处理来电
if (isInCall.value) {
// console.log 输出当前状态
console.log("当前正在通话中,发送忙线状态")
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: data.sender_user_id,
type: data.message_type === 6 ? "video-call" : "audio-call",
callId: data.call_id,
callStatus: "busy"
sendCallSignal({
call_status: "busy",
call_id: data.call_id,
receiver_user_id: data.caller_id,
})
// 添加未接来电消息
const friend = friends.value.find(f => f.id === data.sender_user_id)
const friend = friends.value.find((f) => f.id === data.caller_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",
type: data.call_type === 6 ? "video-call" : "audio-call",
content: "[未接来电]",
time: new Date().toLocaleTimeString().slice(0, 5),
senderId: data.sender_user_id,
senderId: data.caller_id,
read: true,
timestamp: Date.now(),
duration: 0
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
}
@@ -390,13 +446,13 @@ export const useChatStore = defineStore("chat", () => {
}
// 重置所有通话状态
resetCallState();
resetCallState()
incomingCall.value = {
callId: data.call_id,
callerId: data.sender_user_id,
callType: data.message_type === 6 ? "video" : "audio",
content: data.content
callerId: data.caller_id,
callType: data.call_type === 6 ? "video" : "audio",
content: data.data,
}
callStatus.value = "ringing"
callError.value = null
@@ -408,22 +464,36 @@ export const useChatStore = defineStore("chat", () => {
// 处理通话接受
const handleCallAccepted = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
callStatus.value = "ongoing"
callStatus.value = "connecting"
callError.value = null
callConnectionStatus.value = "通话中"
console.log("通话已被接受")
callConnectionStatus.value = "对方已接听,正在连接..."
console.log("通话已被接受,开始交换信令")
// 开始创建Offer
setTimeout(async () => {
try {
await webrtc.getLocalMedia({
audio: true,
video: currentCall.value.callType === "video",
})
await webrtc.createOffer(currentCall.value.callId, currentCall.value.peerId)
} catch (error) {
console.error("创建Offer失败:", error)
endCall("failed")
}
}, 100)
}
}
// 处理通话拒绝
const handleCallRejected = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
resetCallState();
resetCallState()
callConnectionStatus.value = "对方已拒绝"
console.log("通话已被拒绝")
// 添加通话记录
const friend = friends.value.find(f => f.id === currentCall.value.peerId)
const friend = friends.value.find((f) => f.id === currentCall.value.peerId)
if (friend) {
const callMessage = {
type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
@@ -432,7 +502,7 @@ export const useChatStore = defineStore("chat", () => {
senderId: userStore.currentUser?.id,
read: true,
timestamp: Date.now(),
duration: 0
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -450,26 +520,33 @@ 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)) {
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)
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 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}` :
"[通话结束]",
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
duration: duration,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -478,7 +555,7 @@ export const useChatStore = defineStore("chat", () => {
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, callMessage.content, friend.unreadCount)
}
resetCallState();
resetCallState()
callConnectionStatus.value = "通话已结束"
console.log("通话已结束")
@@ -491,12 +568,19 @@ export const useChatStore = defineStore("chat", () => {
// 处理ICE候选
const handleIceCandidate = (data) => {
if ((currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)) {
console.log("收到ICE候选:", data.candidate)
if (
(currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)
) {
try {
const candidate = JSON.parse(data.data)
console.log("收到ICE候选:", candidate)
// 添加到WebRTC连接
webrtc.addIceCandidate(data.candidate)
// 添加到WebRTC连接
webrtc.addIceCandidate(candidate)
} catch (error) {
console.error("处理ICE候选失败:", error)
}
}
}
@@ -504,7 +588,7 @@ 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)
const friend = friends.value.find((f) => f.id === currentCall.value.peerId)
if (friend) {
const callMessage = {
type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
@@ -513,7 +597,7 @@ export const useChatStore = defineStore("chat", () => {
senderId: userStore.currentUser?.id,
read: true,
timestamp: Date.now(),
duration: 0
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -522,7 +606,7 @@ export const useChatStore = defineStore("chat", () => {
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
}
resetCallState();
resetCallState()
callConnectionStatus.value = "对方无人接听"
console.log("对方无人接听")
@@ -537,7 +621,7 @@ 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)
const friend = friends.value.find((f) => f.id === currentCall.value.peerId)
if (friend) {
const callMessage = {
type: currentCall.value.callType === "video" ? "video-call" : "audio-call",
@@ -546,7 +630,7 @@ export const useChatStore = defineStore("chat", () => {
senderId: userStore.currentUser?.id,
read: true,
timestamp: Date.now(),
duration: 0
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -555,7 +639,7 @@ export const useChatStore = defineStore("chat", () => {
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方忙线]", friend.unreadCount)
}
resetCallState();
resetCallState()
callConnectionStatus.value = "对方忙线中"
console.log("对方忙线中")
@@ -568,32 +652,31 @@ export const useChatStore = defineStore("chat", () => {
// 处理对方挂断
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 callId = data?.call_id || currentCall.value?.callId || incomingCall.value?.callId
const peerId = data?.caller_id || data?.callee_id || currentCall.value?.peerId || incomingCall.value?.callerId
if (callId && peerId) {
// 添加通话记录
const friend = friends.value.find(f => f.id === 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 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}`,
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
duration: duration,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
friend.lastMessage = `[对方已挂断] ${Math.floor(duration/60)}${duration%60}`
friend.lastMessage = `[对方已挂断] ${Math.floor(duration / 60)}${duration % 60}`
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
}
@@ -611,29 +694,30 @@ export const useChatStore = defineStore("chat", () => {
// 处理对方掉线
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
const peerId = data?.caller_id || data?.callee_id || currentCall.value?.peerId || incomingCall.value?.callerId
if (callId && peerId) {
// 添加通话记录
const friend = friends.value.find(f => f.id === 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 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}`,
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
duration: duration,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
friend.lastMessage = `[网络中断] ${Math.floor(duration/60)}${duration%60}`
friend.lastMessage = `[网络中断] ${Math.floor(duration / 60)}${duration % 60}`
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
}
@@ -651,23 +735,23 @@ export const useChatStore = defineStore("chat", () => {
// 处理对方终止呼叫
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 peerId = data.caller_id || data.callee_id
if (
(currentCall.value && currentCall.value.callId === callId) ||
(incomingCall.value && incomingCall.value.callId === callId)
) {
// 添加通话记录
const friend = friends.value.find(f => f.id === peerId)
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",
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
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -688,15 +772,15 @@ export const useChatStore = defineStore("chat", () => {
}
// 发起通话
const startCall = (peerId, type) => {
const startCall = async (peerId, type) => {
if (isInCall.value) {
alert('您正在通话中,请先结束当前通话。')
alert("您正在通话中,请先结束当前通话。")
return
}
const friend = friends.value.find(f => f.id === peerId)
const friend = friends.value.find((f) => f.id === peerId)
if (!friend) {
alert('好友信息无效')
alert("好友信息无效")
return
}
@@ -709,31 +793,24 @@ export const useChatStore = defineStore("chat", () => {
peerId: peerId,
callType: type,
startTime: Date.now(),
status: 'calling'
status: "calling",
}
callStatus.value = "calling"
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"
// 获取本地媒体
try {
await webrtc.getLocalMedia({
audio: true,
video: type === "video",
})
} catch (error) {
console.error("获取本地媒体失败:", error)
resetCallState()
callConnectionStatus.value = "连接失败"
})
alert("无法访问摄像头或麦克风")
return
}
// 设置30秒超时无人接听
setCallTimeout(() => {
@@ -743,12 +820,10 @@ export const useChatStore = defineStore("chat", () => {
console.log("呼叫超时,对方无人接听")
// 发送无人接听信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
type: type === "video" ? "video-call" : "audio-call",
callId: callId,
callStatus: "no-answer"
sendCallSignal({
call_status: "no-answer",
call_id: callId,
receiver_user_id: peerId,
})
// 更新好友最后消息为未接来电
@@ -763,7 +838,7 @@ export const useChatStore = defineStore("chat", () => {
senderId: userStore.currentUser?.id,
read: true,
timestamp: Date.now(),
duration: 0
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -775,78 +850,50 @@ export const useChatStore = defineStore("chat", () => {
}, 30000)
// 发送通话请求
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
type: type === "video" ? "video-call" : "audio-call",
callId: callId,
callStatus: "invite"
sendCallSignal({
call_status: "invite",
call_id: callId,
call_type: type === "video" ? 6 : 7,
receiver_user_id: peerId,
})
console.log("发起通话:", currentCall.value)
}
// 接听来电
const acceptCall = () => {
const acceptCall = async () => {
if (!incomingCall.value) return
// 发送连接中信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: incomingCall.value.callerId,
type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
callId: incomingCall.value.callId,
callStatus: "connecting"
})
callStatus.value = "connecting"
callConnectionStatus.value = "正在连接..."
// 创建WebRTC连接
webrtc.createPeerConnection(
incomingCall.value.callId,
incomingCall.value.callerId,
() => {
// WebRTC连接成功回调
callStatus.value = "ongoing"
callConnectionStatus.value = "通话中"
try {
// 获取本地媒体
await webrtc.getLocalMedia({
audio: true,
video: incomingCall.value.callType === "video",
})
// 发送接受信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: incomingCall.value.callerId,
type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
callId: incomingCall.value.callId,
callStatus: "accepted"
})
// 发送接受信号
sendCallSignal({
call_status: "accepted",
call_id: incomingCall.value.callId,
receiver_user_id: incomingCall.value.callerId,
})
currentCall.value = {
callId: incomingCall.value.callId,
peerId: incomingCall.value.callerId,
callType: incomingCall.value.callType,
startTime: Date.now(),
status: 'ongoing'
}
currentCall.value = {
callId: incomingCall.value.callId,
peerId: incomingCall.value.callerId,
callType: incomingCall.value.callType,
startTime: Date.now(),
status: "connecting",
}
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("正在连接通话...")
console.log("接听通话等待Offer...")
} catch (error) {
console.error("接听通话失败:", error)
rejectCall("failed")
}
}
// 拒绝来电
@@ -854,16 +901,14 @@ export const useChatStore = defineStore("chat", () => {
if (!incomingCall.value) return
// 发送拒绝信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: incomingCall.value.callerId,
type: incomingCall.value.callType === "video" ? "video-call" : "audio-call",
callId: incomingCall.value.callId,
callStatus: reason // 可以是'rejected'或'busy'
sendCallSignal({
call_status: reason,
call_id: incomingCall.value.callId,
receiver_user_id: incomingCall.value.callerId,
})
// 添加通话记录
const friend = friends.value.find(f => f.id === incomingCall.value.callerId)
const friend = friends.value.find((f) => f.id === incomingCall.value.callerId)
if (friend) {
const content = reason === "busy" ? "[对方忙线]" : "[已拒绝]"
@@ -874,7 +919,7 @@ export const useChatStore = defineStore("chat", () => {
senderId: userStore.currentUser?.id,
read: true,
timestamp: Date.now(),
duration: 0
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -900,28 +945,31 @@ export const useChatStore = defineStore("chat", () => {
const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
const callTypeVal = currentCall.value?.callType || incomingCall.value?.callType
if (!callId || !peerId) return
if (!callId || !peerId) {
resetCallState()
return
}
// 发送结束信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
type: callTypeVal === "video" ? "video-call" : "audio-call",
callId: callId,
callStatus: reason // 可以是'ended'或'no-answer'
sendCallSignal({
call_status: reason,
call_id: callId,
receiver_user_id: peerId,
})
// 添加通话记录
const friend = friends.value.find(f => f.id === 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 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}`
content = `[${callTypeVal === "video" ? "视频" : "语音"}通话] ${Math.floor(duration / 60)}${duration % 60}`
} else {
content = "[通话结束]"
}
@@ -933,7 +981,7 @@ export const useChatStore = defineStore("chat", () => {
senderId: userStore.currentUser?.id,
read: true,
timestamp: Date.now(),
duration: duration
duration: duration,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
@@ -953,99 +1001,6 @@ export const useChatStore = defineStore("chat", () => {
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,
@@ -1076,10 +1031,8 @@ export const useChatStore = defineStore("chat", () => {
acceptCall,
rejectCall,
endCall,
hangupCall,
terminateCall,
resetCallState,
sendCallSignal,
clearCallTimeout
clearCallTimeout,
}
})
})

View File

@@ -7,16 +7,16 @@ export const useUserStore = defineStore("user", () => {
// 预设用户数据
const presetUsers = ref([
{ id: "1", name: "张三", avatar: "avatar1", color: "#4cc9f0" },
{ id: "2", name: "李四", avatar: "avatar2", color: "#ff6b6b" },
{ id: "3", name: "王五", avatar: "avatar3", color: "#6a0dad" },
{ id: "4", name: "赵六", avatar: "avatar4", color: "#20b2aa" },
{ id: "5", name: "钱七", avatar: "avatar5", color: "#ffa500" },
{ id: "6", name: "孙八", avatar: "avatar6", color: "#9acd32" },
{ id: "7", name: "周九", avatar: "avatar7", color: "#ff1493" },
{ id: "8", name: "吴十", avatar: "avatar8", color: "#4682b4" },
{ id: "9", name: "郑十一", avatar: "avatar9", color: "#c71585" },
{ id: "10", name: "王十二", avatar: "avatar10", color: "#2e8b57" },
{ id: "1", room_id: "10001", name: "张三", avatar: "avatar1", color: "#4cc9f0" },
{ id: "2", room_id: "10002", name: "李四", avatar: "avatar2", color: "#ff6b6b" },
{ id: "3", room_id: "10003", name: "王五", avatar: "avatar3", color: "#6a0dad" },
{ id: "4", room_id: "10004", name: "赵六", avatar: "avatar4", color: "#20b2aa" },
{ id: "5", room_id: "10005", name: "钱七", avatar: "avatar5", color: "#ffa500" },
{ id: "6", room_id: "10006", name: "孙八", avatar: "avatar6", color: "#9acd32" },
{ id: "7", room_id: "10007", name: "周九", avatar: "avatar7", color: "#ff1493" },
{ id: "8", room_id: "10008", name: "吴十", avatar: "avatar8", color: "#4682b4" },
{ id: "9", room_id: "10009", name: "郑十一", avatar: "avatar9", color: "#c71585" },
{ id: "10", room_id: "100010", name: "王十二", avatar: "avatar10", color: "#2e8b57" },
])
const login = (user) => {

View File

@@ -101,6 +101,7 @@ watch(() => chatStore.callStatus, (status) => {
watch(() => chatStore.incomingCall, (call) => {
if (call) {
isIncoming.value = true;
console.log(call, 'ssssssssssssssss')
callerInfo.value = chatStore.friends.find(f => f.id === call.callerId);
callType.value = call.callType;
} else {
@@ -185,4 +186,4 @@ onUnmounted(() => {
.animate-float-slow {
animation: float-slow 18s infinite ease-in-out;
}
</style>
</style>