修复:
1. 本地视频流能获取到了 2. 不知道为什么偶尔能连接成功 现有问题: 1. 不稳定,连接不是每次都成功10%大约 2. 对方获取不到本地推送
This commit is contained in:
@@ -61,26 +61,51 @@
|
||||
v-if="isMinimized"
|
||||
class="minimized-content"
|
||||
@mousedown="startDrag"
|
||||
@dblclick="toggleMinimize"
|
||||
>
|
||||
<div class="remote-video-preview">
|
||||
<video
|
||||
v-if="callType === 'video' && hasRemoteVideo"
|
||||
ref="remoteVideoPreview"
|
||||
class="remote-video"
|
||||
autoplay
|
||||
playsinline
|
||||
></video>
|
||||
<div v-else class="video-placeholder">
|
||||
<div class="placeholder-avatar" :style="{ background: callerInfo.color }">
|
||||
<!-- 音频通话:显示头像和计时器 -->
|
||||
<template v-if="callType === 'audio'">
|
||||
<div class="audio-call-mini">
|
||||
<div class="mini-avatar" :style="{ background: callerInfo.color }">
|
||||
{{ callerInfo.name.charAt(0) }}
|
||||
</div>
|
||||
<div class="mini-info">
|
||||
<div class="mini-name">{{ callerInfo.name }}</div>
|
||||
<div class="mini-timer">{{ formattedDuration }}</div>
|
||||
</div>
|
||||
<button class="mini-expand-btn" @click="toggleMinimize" @mousedown.stop>
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="mini-info">
|
||||
<div class="mini-name">{{ callerInfo.name }}</div>
|
||||
<div class="mini-status">{{ formattedDuration }}</div>
|
||||
</div>
|
||||
<!-- 视频通话:显示对方视频流 -->
|
||||
<template v-else>
|
||||
<div class="video-call-mini">
|
||||
<div class="remote-video-preview">
|
||||
<video
|
||||
v-if="webrtc.remoteStream.value"
|
||||
ref="remoteVideoPreview"
|
||||
class="remote-video"
|
||||
autoplay
|
||||
playsinline
|
||||
:srcObject="webrtc.remoteStream.value"
|
||||
></video>
|
||||
<div v-else class="video-placeholder">
|
||||
<div class="placeholder-avatar" :style="{ background: callerInfo.color }">
|
||||
{{ callerInfo.name.charAt(0) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-info">
|
||||
<div class="mini-name">{{ callerInfo.name }}</div>
|
||||
<div class="mini-timer">{{ formattedDuration }}</div>
|
||||
</div>
|
||||
<button class="mini-expand-btn" @click="toggleMinimize" @mousedown.stop>
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 视频区域 -->
|
||||
@@ -88,11 +113,12 @@
|
||||
<!-- 远程视频 -->
|
||||
<div class="remote-video-container">
|
||||
<video
|
||||
v-if="hasRemoteVideo"
|
||||
v-if="webrtc.remoteStream.value"
|
||||
ref="remoteVideo"
|
||||
class="remote-video"
|
||||
autoplay
|
||||
playsinline
|
||||
:srcObject="webrtc.remoteStream.value"
|
||||
></video>
|
||||
<div v-else class="video-placeholder">
|
||||
<div class="placeholder-avatar" :style="{ background: callerInfo.color }">
|
||||
@@ -106,7 +132,7 @@
|
||||
<!-- 本地视频 -->
|
||||
<div
|
||||
class="local-video-container"
|
||||
v-if="callType === 'video' && showLocalVideo"
|
||||
v-if="callType === 'video' && webrtc.localStream.value"
|
||||
>
|
||||
<video
|
||||
ref="localVideo"
|
||||
@@ -114,6 +140,7 @@
|
||||
autoplay
|
||||
playsinline
|
||||
muted
|
||||
:srcObject="webrtc.localStream.value"
|
||||
></video>
|
||||
</div>
|
||||
<div
|
||||
@@ -124,6 +151,12 @@
|
||||
{{ userStore.currentUser?.name?.charAt(0) || 'U' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通话计时器显示 -->
|
||||
<div v-if="callStatus === 'ongoing'" class="call-timer-display">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span>{{ formattedDuration }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制栏 -->
|
||||
@@ -229,12 +262,10 @@ const isMinimized = ref(false);
|
||||
const isMicOn = ref(true);
|
||||
const isCameraOn = ref(props.callType === 'video');
|
||||
const isSpeakerOn = ref(true);
|
||||
const hasRemoteVideo = ref(false);
|
||||
const callDuration = ref(0);
|
||||
const callTimer = ref(null);
|
||||
const isDragging = ref(false);
|
||||
const isAccepting = ref(false);
|
||||
const showLocalVideo = ref(false);
|
||||
|
||||
// 友好提示相关
|
||||
const showFriendlyMessage = ref(false);
|
||||
@@ -256,12 +287,6 @@ const callStatus = computed(() => {
|
||||
return chatStore.callStatus;
|
||||
});
|
||||
|
||||
// 本地媒体状态
|
||||
const hasLocalVideo = computed(() => webrtc.hasLocalVideo.value);
|
||||
const hasLocalAudio = computed(() => webrtc.hasLocalAudio.value);
|
||||
const isCameraWorking = computed(() => hasLocalVideo.value && isCameraOn.value);
|
||||
const isMicWorking = computed(() => hasLocalAudio.value && isMicOn.value);
|
||||
|
||||
// 连接状态文本
|
||||
const connectionText = computed(() => {
|
||||
if (chatStore.callConnectionStatus) {
|
||||
@@ -285,7 +310,7 @@ const connectionText = computed(() => {
|
||||
}
|
||||
|
||||
if (callStatus.value === "ongoing") {
|
||||
return `通话中 ${formattedDuration.value}`;
|
||||
return `通话中`;
|
||||
}
|
||||
|
||||
if (callStatus.value === "connecting") {
|
||||
@@ -317,14 +342,14 @@ const startCallTimer = () => {
|
||||
callTimer.value = setInterval(() => {
|
||||
callDuration.value += 1;
|
||||
}, 1000);
|
||||
console.log("通话计时器已启动");
|
||||
console.log("⏰ 通话计时器已启动");
|
||||
};
|
||||
|
||||
const stopCallTimer = () => {
|
||||
if (callTimer.value) {
|
||||
clearInterval(callTimer.value);
|
||||
callTimer.value = null;
|
||||
console.log("通话计时器已停止");
|
||||
console.log("⏰ 通话计时器已停止");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -337,81 +362,52 @@ const formattedDuration = computed(() => {
|
||||
|
||||
// 计算容器样式
|
||||
const containerStyle = computed(() => {
|
||||
const width = isMinimized.value ? (props.callType === 'audio' ? '250px' : '200px') : '500px';
|
||||
const height = isMinimized.value ? '60px' : '380px';
|
||||
|
||||
return {
|
||||
left: `${position.x}px`,
|
||||
top: `${position.y}px`,
|
||||
width: isMinimized.value ? '200px' : '500px',
|
||||
height: isMinimized.value ? '60px' : '380px'
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
});
|
||||
|
||||
// 初始化媒体设备
|
||||
const initializeMedia = async () => {
|
||||
try {
|
||||
if (callStatus.value === "calling" || callStatus.value === "connecting" || callStatus.value === "ongoing") {
|
||||
console.log("🎥 初始化媒体设备,当前状态:", callStatus.value);
|
||||
|
||||
if (['calling', 'connecting', 'ongoing'].includes(callStatus.value) ||
|
||||
(props.isIncoming && callStatus.value === 'ringing')) {
|
||||
|
||||
const constraints = {
|
||||
audio: true,
|
||||
video: props.callType === 'video'
|
||||
};
|
||||
|
||||
console.log("初始化媒体设备,约束:", constraints);
|
||||
console.log("🎬 获取媒体约束:", constraints);
|
||||
await webrtc.getLocalMedia(constraints);
|
||||
console.log("✅ 媒体获取成功");
|
||||
|
||||
// 立即设置本地视频
|
||||
await nextTick();
|
||||
updateLocalVideo();
|
||||
|
||||
} else if (props.isIncoming && callStatus.value === "ringing") {
|
||||
console.log("来电状态,暂不获取媒体流");
|
||||
return;
|
||||
} else {
|
||||
console.log("当前状态不需要获取媒体:", callStatus.value);
|
||||
console.log("⏭️ 当前状态不需要获取媒体:", callStatus.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('无法访问媒体设备,但继续通话流程:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新本地视频显示
|
||||
const updateLocalVideo = async () => {
|
||||
await nextTick();
|
||||
if (webrtc.localStream.value && localVideo.value) {
|
||||
console.log("设置本地视频流到video元素");
|
||||
localVideo.value.srcObject = webrtc.localStream.value;
|
||||
showLocalVideo.value = props.callType === 'video' && webrtc.hasLocalVideo.value;
|
||||
console.log("本地视频显示状态:", showLocalVideo.value);
|
||||
} else {
|
||||
console.log("本地视频流或元素不存在:", {
|
||||
stream: !!webrtc.localStream.value,
|
||||
element: !!localVideo.value
|
||||
});
|
||||
showLocalVideo.value = false;
|
||||
console.warn('⚠️ 无法访问媒体设备:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 控制功能
|
||||
const toggleMic = () => {
|
||||
isMicOn.value = !isMicOn.value;
|
||||
if (webrtc.localStream.value) {
|
||||
webrtc.localStream.value.getAudioTracks().forEach(track => {
|
||||
track.enabled = isMicOn.value;
|
||||
});
|
||||
}
|
||||
webrtc.toggleMicrophone(isMicOn.value);
|
||||
};
|
||||
|
||||
const toggleCamera = async () => {
|
||||
if (props.callType === 'video') {
|
||||
isCameraOn.value = !isCameraOn.value;
|
||||
|
||||
try {
|
||||
await webrtc.toggleVideoStream(isCameraOn.value);
|
||||
await nextTick();
|
||||
updateLocalVideo();
|
||||
console.log("摄像头切换成功:", isCameraOn.value);
|
||||
} catch (error) {
|
||||
console.error("摄像头切换失败:", error);
|
||||
isCameraOn.value = !isCameraOn.value;
|
||||
}
|
||||
webrtc.toggleCamera(isCameraOn.value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -420,52 +416,55 @@ const toggleSpeaker = () => {
|
||||
if (remoteVideo.value) {
|
||||
remoteVideo.value.muted = !isSpeakerOn.value;
|
||||
}
|
||||
if (remoteVideoPreview.value) {
|
||||
remoteVideoPreview.value.muted = !isSpeakerOn.value;
|
||||
}
|
||||
console.log("🔊 扬声器状态:", isSpeakerOn.value);
|
||||
};
|
||||
|
||||
const toggleMinimize = () => {
|
||||
isMinimized.value = !isMinimized.value;
|
||||
console.log("📐 切换最小化状态:", isMinimized.value);
|
||||
|
||||
if (isMinimized.value) {
|
||||
position.x = Math.max(0, Math.min(position.x, window.innerWidth - 200));
|
||||
position.y = Math.max(0, Math.min(position.y, window.innerHeight - 60));
|
||||
} else {
|
||||
position.x = Math.max(0, Math.min(position.x, window.innerWidth - 500));
|
||||
position.y = Math.max(0, Math.min(position.y, window.innerHeight - 380));
|
||||
}
|
||||
const width = isMinimized.value ? (props.callType === 'audio' ? 250 : 200) : 500;
|
||||
const height = isMinimized.value ? 60 : 380;
|
||||
|
||||
position.x = Math.max(0, Math.min(position.x, window.innerWidth - width));
|
||||
position.y = Math.max(0, Math.min(position.y, window.innerHeight - height));
|
||||
};
|
||||
|
||||
const endCall = () => {
|
||||
stopCallTimer();
|
||||
|
||||
if (localVideo.value) localVideo.value.srcObject = null;
|
||||
if (remoteVideo.value) remoteVideo.value.srcObject = null;
|
||||
if (remoteVideoPreview.value) remoteVideoPreview.value.srcObject = null;
|
||||
|
||||
isVisible.value = false;
|
||||
chatStore.endCall();
|
||||
emit('end-call');
|
||||
};
|
||||
|
||||
// 接听通话
|
||||
const acceptCall = () => {
|
||||
if (isAccepting.value || chatStore.callStatus === "connecting" || chatStore.callStatus === "ongoing") {
|
||||
console.log("已在处理接听或通话中,忽略重复点击")
|
||||
const acceptCall = async () => {
|
||||
if (isAccepting.value || ['connecting', 'ongoing'].includes(chatStore.callStatus)) {
|
||||
console.log("⚠️ 已在处理接听或通话中,忽略重复点击")
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("用户点击接听按钮")
|
||||
console.log("📞 用户点击接听按钮")
|
||||
isAccepting.value = true;
|
||||
|
||||
chatStore.acceptCall().then(() => {
|
||||
console.log("接听请求已发送")
|
||||
try {
|
||||
// 先获取媒体流
|
||||
await initializeMedia();
|
||||
|
||||
// 然后接听通话
|
||||
await chatStore.acceptCall();
|
||||
console.log("✅ 接听请求已发送")
|
||||
showFriendlyNotification("正在接听通话...", 2000);
|
||||
}).catch((error) => {
|
||||
console.error("接听失败:", error)
|
||||
}).finally(() => {
|
||||
} catch (error) {
|
||||
console.error("❌ 接听失败:", error)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isAccepting.value = false;
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
emit('accept-call');
|
||||
};
|
||||
@@ -473,11 +472,11 @@ const acceptCall = () => {
|
||||
// 拒绝通话
|
||||
const rejectCall = () => {
|
||||
if (chatStore.callStatus === "idle" || isAccepting.value) {
|
||||
console.log("通话已结束或正在接听,忽略拒绝操作")
|
||||
console.log("⚠️ 通话已结束或正在接听,忽略拒绝操作")
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("用户点击拒绝按钮")
|
||||
console.log("❌ 用户点击拒绝按钮")
|
||||
isVisible.value = false;
|
||||
chatStore.rejectCall();
|
||||
emit('reject-call');
|
||||
@@ -485,7 +484,9 @@ const rejectCall = () => {
|
||||
|
||||
// 拖拽功能
|
||||
const startDrag = (event) => {
|
||||
if (event.target.closest('.action-btn') || event.target.closest('.control-btn')) {
|
||||
if (event.target.closest('.action-btn') ||
|
||||
event.target.closest('.control-btn') ||
|
||||
event.target.closest('.mini-expand-btn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -510,7 +511,7 @@ const handleDrag = (event) => {
|
||||
startPosition.x = event.clientX;
|
||||
startPosition.y = event.clientY;
|
||||
|
||||
const width = isMinimized.value ? 200 : 500;
|
||||
const width = isMinimized.value ? (props.callType === 'audio' ? 250 : 200) : 500;
|
||||
const height = isMinimized.value ? 60 : 380;
|
||||
|
||||
position.x = Math.max(0, Math.min(position.x, window.innerWidth - width));
|
||||
@@ -525,81 +526,49 @@ const stopDrag = () => {
|
||||
|
||||
// 窗口大小变化处理
|
||||
const handleWindowResize = () => {
|
||||
const width = isMinimized.value ? 200 : 500;
|
||||
const width = isMinimized.value ? (props.callType === 'audio' ? 250 : 200) : 500;
|
||||
const height = isMinimized.value ? 60 : 380;
|
||||
|
||||
position.x = Math.max(0, Math.min(position.x, window.innerWidth - width));
|
||||
position.y = Math.max(0, Math.min(position.y, window.innerHeight - height));
|
||||
};
|
||||
|
||||
// 监听本地流变化
|
||||
watch(() => webrtc.localStream.value, async (newStream) => {
|
||||
console.log("本地流变化:", !!newStream);
|
||||
await nextTick();
|
||||
updateLocalVideo();
|
||||
});
|
||||
|
||||
// 监听远程流变化
|
||||
watch(() => webrtc.remoteStream.value, (newStream) => {
|
||||
console.log("远程流变化:", !!newStream);
|
||||
if (newStream) {
|
||||
if (remoteVideo.value) {
|
||||
remoteVideo.value.srcObject = newStream;
|
||||
}
|
||||
if (remoteVideoPreview.value) {
|
||||
remoteVideoPreview.value.srcObject = newStream;
|
||||
}
|
||||
hasRemoteVideo.value = true;
|
||||
} else {
|
||||
if (remoteVideo.value) {
|
||||
remoteVideo.value.srcObject = null;
|
||||
}
|
||||
if (remoteVideoPreview.value) {
|
||||
remoteVideoPreview.value.srcObject = null;
|
||||
}
|
||||
hasRemoteVideo.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听通话状态
|
||||
watch(() => chatStore.callStatus, (status, oldStatus) => {
|
||||
console.log("VideoCallComponent 监听到通话状态变化:", oldStatus, "->", status);
|
||||
console.log("📞 VideoCallComponent 监听到通话状态变化:", oldStatus, "->", status);
|
||||
|
||||
if (status === "calling") {
|
||||
console.log("发起通话,初始化媒体设备")
|
||||
console.log("📞 发起通话,初始化媒体设备")
|
||||
initializeMedia();
|
||||
} else if (status === "ongoing") {
|
||||
if (!callTimer.value) {
|
||||
console.log("开始通话计时")
|
||||
console.log("⏰ 开始通话计时")
|
||||
startCallTimer();
|
||||
}
|
||||
showFriendlyNotification("通话已连接", 2000);
|
||||
} else if (status === "ended" || status === "idle") {
|
||||
console.log("通话结束,准备关闭组件");
|
||||
console.log("📞 通话结束,准备关闭组件");
|
||||
stopCallTimer();
|
||||
setTimeout(() => {
|
||||
endCall();
|
||||
}, 100);
|
||||
} else if (status === "connecting") {
|
||||
console.log("通话连接中,确保媒体设备已初始化")
|
||||
if (!webrtc.localStream.value) {
|
||||
initializeMedia();
|
||||
}
|
||||
console.log("🔗 通话连接中")
|
||||
showFriendlyNotification("正在建立连接...", 2000);
|
||||
} else if (status === "failed") {
|
||||
console.log("通话失败")
|
||||
console.log("❌ 通话失败")
|
||||
showFriendlyNotification("连接失败", 3000);
|
||||
setTimeout(() => {
|
||||
endCall();
|
||||
}, 3000);
|
||||
} else if (status === "hangup") {
|
||||
console.log("对方已挂断,3秒后关闭组件")
|
||||
console.log("📞 对方已挂断,3秒后关闭组件")
|
||||
showFriendlyNotification("对方已挂断", 3000);
|
||||
setTimeout(() => {
|
||||
endCall();
|
||||
}, 3000);
|
||||
} else if (status === "disconnected") {
|
||||
console.log("网络连接已断开,3秒后关闭组件")
|
||||
console.log("🌐 网络连接已断开,3秒后关闭组件")
|
||||
showFriendlyNotification("网络连接已断开", 3000);
|
||||
setTimeout(() => {
|
||||
endCall();
|
||||
@@ -628,7 +597,7 @@ onMounted(() => {
|
||||
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
|
||||
console.log("VideoCallComponent mounted, callStatus:", chatStore.callStatus, "isIncoming:", props.isIncoming);
|
||||
console.log("🚀 VideoCallComponent mounted, callStatus:", chatStore.callStatus, "isIncoming:", props.isIncoming);
|
||||
|
||||
if (['calling', 'connecting', 'ongoing'].includes(chatStore.callStatus)) {
|
||||
initializeMedia();
|
||||
@@ -636,7 +605,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log("VideoCallComponent unmounted");
|
||||
console.log("🔚 VideoCallComponent unmounted");
|
||||
|
||||
stopCallTimer();
|
||||
if (friendlyMessageTimer.value) {
|
||||
@@ -647,16 +616,6 @@ onUnmounted(() => {
|
||||
document.removeEventListener('mouseup', stopDrag);
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
|
||||
if (localVideo.value) {
|
||||
localVideo.value.srcObject = null;
|
||||
}
|
||||
if (remoteVideo.value) {
|
||||
remoteVideo.value.srcObject = null;
|
||||
}
|
||||
if (remoteVideoPreview.value) {
|
||||
remoteVideoPreview.value.srcObject = null;
|
||||
}
|
||||
|
||||
webrtc.closeConnection();
|
||||
});
|
||||
</script>
|
||||
@@ -831,15 +790,44 @@ onUnmounted(() => {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
color: white;
|
||||
padding: 0 10px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.audio-call-mini {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.video-call-mini {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mini-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.remote-video-preview {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.remote-video {
|
||||
@@ -872,7 +860,9 @@ onUnmounted(() => {
|
||||
.mini-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.mini-name {
|
||||
@@ -883,9 +873,30 @@ onUnmounted(() => {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mini-status {
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
.mini-timer {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.mini-expand-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mini-expand-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.video-area {
|
||||
@@ -943,6 +954,22 @@ onUnmounted(() => {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.call-timer-display {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-family: 'Courier New', monospace;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.local-video-container {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
|
||||
@@ -9,62 +9,91 @@ export function useWebRTC() {
|
||||
const peerConnection = ref(null)
|
||||
const isConnected = ref(false)
|
||||
const isConnecting = ref(false)
|
||||
const hasLocalVideo = ref(false)
|
||||
const hasLocalAudio = ref(false)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const configuration = {
|
||||
iceServers: [
|
||||
{ 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" },
|
||||
],
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }, { urls: "stun:stun1.l.google.com:19302" }],
|
||||
}
|
||||
|
||||
// 获取本地媒体流
|
||||
const getLocalMedia = async (constraints = { audio: true, video: true }) => {
|
||||
try {
|
||||
console.log("🎥 开始获取本地媒体流:", constraints)
|
||||
|
||||
// 如果已有流,先停止
|
||||
if (localStream.value) {
|
||||
localStream.value.getTracks().forEach((track) => track.stop())
|
||||
localStream.value = null
|
||||
}
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
localStream.value = stream
|
||||
|
||||
console.log("✅ 本地媒体流获取成功:", {
|
||||
streamId: stream.id,
|
||||
videoTracks: stream.getVideoTracks().length,
|
||||
audioTracks: stream.getAudioTracks().length,
|
||||
videoEnabled: stream.getVideoTracks()[0]?.enabled,
|
||||
audioEnabled: stream.getAudioTracks()[0]?.enabled,
|
||||
})
|
||||
|
||||
return stream
|
||||
} catch (error) {
|
||||
console.error("❌ 获取本地媒体流失败:", error)
|
||||
|
||||
// 如果视频失败,尝试只获取音频
|
||||
if (constraints.video) {
|
||||
try {
|
||||
console.log("🎵 尝试仅获取音频流")
|
||||
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
|
||||
localStream.value = audioStream
|
||||
console.log("✅ 音频流获取成功")
|
||||
return audioStream
|
||||
} catch (audioError) {
|
||||
console.error("❌ 音频流也获取失败:", audioError)
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 创建PeerConnection
|
||||
const createPeerConnection = (callId, peerId) => {
|
||||
console.log("🔗 创建PeerConnection:", { callId, peerId })
|
||||
|
||||
if (peerConnection.value) {
|
||||
console.log("关闭现有的WebRTC连接")
|
||||
closeConnection()
|
||||
console.log("🔄 关闭现有连接")
|
||||
peerConnection.value.close()
|
||||
}
|
||||
|
||||
console.log("创建新的WebRTC连接")
|
||||
peerConnection.value = new RTCPeerConnection(configuration)
|
||||
const pc = new RTCPeerConnection(configuration)
|
||||
peerConnection.value = pc
|
||||
|
||||
peerConnection.value.onicecandidate = async (event) => {
|
||||
// ICE候选事件
|
||||
pc.onicecandidate = async (event) => {
|
||||
if (event.candidate) {
|
||||
console.log("发送ICE候选:", event.candidate)
|
||||
|
||||
if (chatStore.callStatus === "connecting" || chatStore.callStatus === "ongoing") {
|
||||
try {
|
||||
await callAPI.sendCandidate(userStore.currentUser?.id, peerId, callId, event.candidate, 6)
|
||||
} catch (error) {
|
||||
console.error("发送ICE候选失败:", error)
|
||||
}
|
||||
console.log("📡 发送ICE候选")
|
||||
try {
|
||||
await callAPI.sendCandidate(userStore.currentUser?.id, peerId, callId, event.candidate, 6)
|
||||
} catch (error) {
|
||||
console.error("❌ 发送ICE候选失败:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
peerConnection.value.ontrack = (event) => {
|
||||
if (event.streams && event.streams[0]) {
|
||||
console.log("接收到远程流:", event.streams[0])
|
||||
remoteStream.value = event.streams[0]
|
||||
|
||||
const videoTracks = event.streams[0].getVideoTracks()
|
||||
const audioTracks = event.streams[0].getAudioTracks()
|
||||
console.log("远程流包含:", {
|
||||
video: videoTracks.length,
|
||||
audio: audioTracks.length,
|
||||
})
|
||||
}
|
||||
// 接收远程流
|
||||
pc.ontrack = (event) => {
|
||||
console.log("📹 接收到远程流:", event.streams[0])
|
||||
remoteStream.value = event.streams[0]
|
||||
}
|
||||
|
||||
peerConnection.value.onconnectionstatechange = () => {
|
||||
const state = peerConnection.value?.connectionState
|
||||
console.log("WebRTC连接状态变化:", state)
|
||||
// 连接状态变化
|
||||
pc.onconnectionstatechange = () => {
|
||||
const state = pc.connectionState
|
||||
console.log("🔗 连接状态:", state)
|
||||
|
||||
if (state === "connected") {
|
||||
isConnected.value = true
|
||||
@@ -72,352 +101,137 @@ export function useWebRTC() {
|
||||
if (chatStore.callStatus === "connecting") {
|
||||
chatStore.callStatus = "ongoing"
|
||||
chatStore.callConnectionStatus = "通话已连接"
|
||||
console.log("WebRTC连接已建立,通话开始")
|
||||
}
|
||||
} else if (state === "connecting") {
|
||||
isConnecting.value = true
|
||||
if (chatStore.callStatus === "connecting") {
|
||||
chatStore.callConnectionStatus = "正在建立连接..."
|
||||
}
|
||||
} else if (state === "disconnected") {
|
||||
console.log("WebRTC连接断开")
|
||||
} else if (state === "disconnected" || state === "failed") {
|
||||
isConnected.value = false
|
||||
isConnecting.value = false
|
||||
if (chatStore.callStatus === "ongoing") {
|
||||
chatStore.callStatus = "disconnected"
|
||||
chatStore.callConnectionStatus = "连接已断开"
|
||||
}
|
||||
} else if (state === "failed") {
|
||||
console.log("WebRTC连接失败")
|
||||
isConnected.value = false
|
||||
isConnecting.value = false
|
||||
if (chatStore.callStatus === "connecting" || chatStore.callStatus === "ongoing") {
|
||||
chatStore.callStatus = "failed"
|
||||
chatStore.callConnectionStatus = "连接失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
peerConnection.value.oniceconnectionstatechange = () => {
|
||||
const state = peerConnection.value?.iceConnectionState
|
||||
console.log("ICE连接状态变化:", state)
|
||||
|
||||
if (state === "connected" || state === "completed") {
|
||||
console.log("ICE连接成功")
|
||||
isConnected.value = true
|
||||
if (chatStore.callStatus === "connecting") {
|
||||
chatStore.callStatus = "ongoing"
|
||||
chatStore.callConnectionStatus = "通话中"
|
||||
}
|
||||
} else if (state === "failed") {
|
||||
console.log("ICE连接失败")
|
||||
if (chatStore.callStatus === "ongoing" || chatStore.callStatus === "connecting") {
|
||||
chatStore.callStatus = "failed"
|
||||
chatStore.callConnectionStatus = "网络连接失败"
|
||||
}
|
||||
} else if (state === "disconnected") {
|
||||
console.log("ICE连接断开")
|
||||
if (chatStore.callStatus === "ongoing") {
|
||||
chatStore.callStatus = "disconnected"
|
||||
chatStore.callConnectionStatus = "网络连接已断开"
|
||||
}
|
||||
}
|
||||
// 添加本地流到连接
|
||||
if (localStream.value) {
|
||||
console.log("➕ 添加本地流到PeerConnection")
|
||||
localStream.value.getTracks().forEach((track) => {
|
||||
pc.addTrack(track, localStream.value)
|
||||
})
|
||||
}
|
||||
|
||||
return peerConnection.value
|
||||
}
|
||||
|
||||
const getLocalMedia = async (constraints = { audio: true, video: true }) => {
|
||||
try {
|
||||
// 如果已有本地流,先停止
|
||||
if (localStream.value) {
|
||||
localStream.value.getTracks().forEach((track) => track.stop())
|
||||
localStream.value = null
|
||||
hasLocalVideo.value = false
|
||||
hasLocalAudio.value = false
|
||||
}
|
||||
|
||||
console.log("获取本地媒体,约束:", constraints)
|
||||
|
||||
try {
|
||||
localStream.value = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
console.log("获取本地媒体成功:", localStream.value)
|
||||
|
||||
// 检查实际获取到的轨道
|
||||
const videoTracks = localStream.value.getVideoTracks()
|
||||
const audioTracks = localStream.value.getAudioTracks()
|
||||
|
||||
hasLocalVideo.value = videoTracks.length > 0 && videoTracks[0].enabled
|
||||
hasLocalAudio.value = audioTracks.length > 0 && audioTracks[0].enabled
|
||||
|
||||
console.log("本地媒体状态:", {
|
||||
hasVideo: hasLocalVideo.value,
|
||||
hasAudio: hasLocalAudio.value,
|
||||
videoTracks: videoTracks.length,
|
||||
audioTracks: audioTracks.length,
|
||||
})
|
||||
|
||||
return localStream.value
|
||||
} catch (mediaError) {
|
||||
console.warn("获取完整媒体失败,尝试仅获取音频:", mediaError)
|
||||
|
||||
if (constraints.video) {
|
||||
try {
|
||||
localStream.value = await navigator.mediaDevices.getUserMedia({ audio: constraints.audio, video: false })
|
||||
console.log("仅获取音频成功:", localStream.value)
|
||||
|
||||
const audioTracks = localStream.value.getAudioTracks()
|
||||
hasLocalVideo.value = false
|
||||
hasLocalAudio.value = audioTracks.length > 0 && audioTracks[0].enabled
|
||||
|
||||
console.log("本地媒体状态(仅音频):", { hasVideo: hasLocalVideo.value, hasAudio: hasLocalAudio.value })
|
||||
|
||||
return localStream.value
|
||||
} catch (audioError) {
|
||||
console.warn("获取音频也失败:", audioError)
|
||||
hasLocalVideo.value = false
|
||||
hasLocalAudio.value = false
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
hasLocalVideo.value = false
|
||||
hasLocalAudio.value = false
|
||||
return null
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取本地媒体完全失败:", error)
|
||||
hasLocalVideo.value = false
|
||||
hasLocalAudio.value = false
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 重新获取视频流(用于摄像头开关)
|
||||
const toggleVideoStream = async (enable) => {
|
||||
try {
|
||||
if (enable) {
|
||||
console.log("重新获取视频流")
|
||||
const newStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: true,
|
||||
})
|
||||
|
||||
// 停止旧的流
|
||||
if (localStream.value) {
|
||||
localStream.value.getTracks().forEach((track) => track.stop())
|
||||
}
|
||||
|
||||
localStream.value = newStream
|
||||
hasLocalVideo.value = true
|
||||
hasLocalAudio.value = true
|
||||
|
||||
// 如果有PeerConnection,替换轨道
|
||||
if (peerConnection.value) {
|
||||
const videoTrack = newStream.getVideoTracks()[0]
|
||||
const audioTrack = newStream.getAudioTracks()[0]
|
||||
|
||||
const senders = peerConnection.value.getSenders()
|
||||
|
||||
// 替换视频轨道
|
||||
const videoSender = senders.find((sender) => sender.track && sender.track.kind === "video")
|
||||
if (videoSender && videoTrack) {
|
||||
await videoSender.replaceTrack(videoTrack)
|
||||
console.log("替换视频轨道成功")
|
||||
} else if (videoTrack) {
|
||||
peerConnection.value.addTrack(videoTrack, newStream)
|
||||
console.log("添加新视频轨道")
|
||||
}
|
||||
|
||||
// 替换音频轨道
|
||||
const audioSender = senders.find((sender) => sender.track && sender.track.kind === "audio")
|
||||
if (audioSender && audioTrack) {
|
||||
await audioSender.replaceTrack(audioTrack)
|
||||
console.log("替换音频轨道成功")
|
||||
} else if (audioTrack) {
|
||||
peerConnection.value.addTrack(audioTrack, newStream)
|
||||
console.log("添加新音频轨道")
|
||||
}
|
||||
}
|
||||
|
||||
return newStream
|
||||
} else {
|
||||
// 关闭摄像头:只保留音频
|
||||
if (localStream.value) {
|
||||
const videoTracks = localStream.value.getVideoTracks()
|
||||
videoTracks.forEach((track) => {
|
||||
track.stop()
|
||||
localStream.value.removeTrack(track)
|
||||
})
|
||||
hasLocalVideo.value = false
|
||||
|
||||
// 如果有PeerConnection,移除视频轨道
|
||||
if (peerConnection.value) {
|
||||
const senders = peerConnection.value.getSenders()
|
||||
const videoSender = senders.find((sender) => sender.track && sender.track.kind === "video")
|
||||
if (videoSender) {
|
||||
await videoSender.replaceTrack(null)
|
||||
console.log("移除视频轨道")
|
||||
}
|
||||
}
|
||||
}
|
||||
return localStream.value
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("切换视频流失败:", error)
|
||||
return localStream.value
|
||||
}
|
||||
return pc
|
||||
}
|
||||
|
||||
// 创建Offer
|
||||
const createOffer = async (callId, peerId) => {
|
||||
console.log("📤 创建Offer")
|
||||
|
||||
if (!peerConnection.value) {
|
||||
createPeerConnection(callId, peerId)
|
||||
}
|
||||
|
||||
// 确保本地流已添加到连接
|
||||
if (localStream.value) {
|
||||
console.log("添加本地流到PeerConnection")
|
||||
const existingSenders = peerConnection.value.getSenders()
|
||||
|
||||
localStream.value.getTracks().forEach((track) => {
|
||||
// 检查是否已经添加了这个轨道
|
||||
const existingSender = existingSenders.find((sender) => sender.track === track)
|
||||
if (!existingSender) {
|
||||
console.log("添加轨道到PeerConnection:", track.kind, track.enabled)
|
||||
peerConnection.value.addTrack(track, localStream.value)
|
||||
} else {
|
||||
console.log("轨道已存在,跳过添加:", track.kind)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn("本地流不存在,无法添加到PeerConnection")
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("开始创建Offer")
|
||||
const offer = await peerConnection.value.createOffer({
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true,
|
||||
})
|
||||
|
||||
console.log("设置本地描述")
|
||||
const offer = await peerConnection.value.createOffer()
|
||||
await peerConnection.value.setLocalDescription(offer)
|
||||
console.log("创建Offer成功:", offer)
|
||||
|
||||
console.log("✅ Offer创建成功:", offer.type)
|
||||
console.log("📋 SDP:", offer.sdp.substring(0, 100) + "...")
|
||||
|
||||
return offer
|
||||
} catch (error) {
|
||||
console.error("创建Offer失败:", error)
|
||||
console.error("❌ 创建Offer失败:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Answer
|
||||
const createAnswer = async (offer, callId, peerId) => {
|
||||
console.log("📥 处理Offer并创建Answer")
|
||||
|
||||
if (!peerConnection.value) {
|
||||
createPeerConnection(callId, peerId)
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("设置远程Offer描述")
|
||||
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
|
||||
|
||||
// 确保本地流已添加到连接
|
||||
if (localStream.value) {
|
||||
console.log("添加本地流到PeerConnection")
|
||||
const existingSenders = peerConnection.value.getSenders()
|
||||
|
||||
localStream.value.getTracks().forEach((track) => {
|
||||
const existingSender = existingSenders.find((sender) => sender.track === track)
|
||||
if (!existingSender) {
|
||||
console.log("添加轨道到PeerConnection:", track.kind, track.enabled)
|
||||
peerConnection.value.addTrack(track, localStream.value)
|
||||
} else {
|
||||
console.log("轨道已存在,跳过添加:", track.kind)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn("本地流不存在,无法添加到PeerConnection")
|
||||
}
|
||||
|
||||
console.log("开始创建Answer")
|
||||
await peerConnection.value.setRemoteDescription(offer)
|
||||
const answer = await peerConnection.value.createAnswer()
|
||||
|
||||
console.log("设置本地Answer描述")
|
||||
await peerConnection.value.setLocalDescription(answer)
|
||||
console.log("创建Answer成功:", answer)
|
||||
|
||||
console.log("✅ Answer创建成功:", answer.type)
|
||||
console.log("📋 SDP:", answer.sdp.substring(0, 100) + "...")
|
||||
|
||||
return answer
|
||||
} catch (error) {
|
||||
console.error("创建Answer失败:", error)
|
||||
console.error("❌ 创建Answer失败:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 设置远程Answer
|
||||
const setRemoteAnswer = async (answer) => {
|
||||
console.log("📥 设置远程Answer")
|
||||
try {
|
||||
if (!peerConnection.value) {
|
||||
throw new Error("PeerConnection 未初始化")
|
||||
}
|
||||
console.log("设置远程Answer描述")
|
||||
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(answer))
|
||||
console.log("设置远程Answer成功")
|
||||
await peerConnection.value.setRemoteDescription(answer)
|
||||
console.log("✅ 远程Answer设置成功")
|
||||
} catch (error) {
|
||||
console.error("设置远程Answer失败:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const setRemoteOffer = async (offer) => {
|
||||
try {
|
||||
if (!peerConnection.value) {
|
||||
throw new Error("PeerConnection 未初始化")
|
||||
}
|
||||
console.log("设置远程Offer描述")
|
||||
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer))
|
||||
console.log("设置远程Offer成功")
|
||||
} catch (error) {
|
||||
console.error("设置远程Offer失败:", error)
|
||||
console.error("❌ 设置远程Answer失败:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 添加ICE候选
|
||||
const addIceCandidate = async (candidate) => {
|
||||
if (peerConnection.value && peerConnection.value.remoteDescription) {
|
||||
try {
|
||||
console.log("添加ICE候选:", candidate)
|
||||
await peerConnection.value.addIceCandidate(new RTCIceCandidate(candidate))
|
||||
console.log("添加ICE候选成功")
|
||||
await peerConnection.value.addIceCandidate(candidate)
|
||||
console.log("✅ ICE候选添加成功")
|
||||
} catch (error) {
|
||||
console.error("添加ICE候选失败:", error)
|
||||
console.error("❌ 添加ICE候选失败:", error)
|
||||
}
|
||||
} else {
|
||||
console.warn("无法添加ICE候选,连接未准备好")
|
||||
}
|
||||
}
|
||||
|
||||
// 切换摄像头
|
||||
const toggleCamera = async (enable) => {
|
||||
if (!localStream.value) return
|
||||
|
||||
const videoTrack = localStream.value.getVideoTracks()[0]
|
||||
if (videoTrack) {
|
||||
videoTrack.enabled = enable
|
||||
console.log("📹 摄像头状态:", enable ? "开启" : "关闭")
|
||||
}
|
||||
}
|
||||
|
||||
// 切换麦克风
|
||||
const toggleMicrophone = async (enable) => {
|
||||
if (!localStream.value) return
|
||||
|
||||
const audioTrack = localStream.value.getAudioTracks()[0]
|
||||
if (audioTrack) {
|
||||
audioTrack.enabled = enable
|
||||
console.log("🎤 麦克风状态:", enable ? "开启" : "关闭")
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
const closeConnection = () => {
|
||||
console.log("关闭WebRTC连接")
|
||||
console.log("🔄 关闭WebRTC连接")
|
||||
|
||||
if (localStream.value) {
|
||||
localStream.value.getTracks().forEach((track) => {
|
||||
track.stop()
|
||||
})
|
||||
localStream.value.getTracks().forEach((track) => track.stop())
|
||||
localStream.value = null
|
||||
}
|
||||
|
||||
if (remoteStream.value) {
|
||||
remoteStream.value = null
|
||||
}
|
||||
|
||||
if (peerConnection.value) {
|
||||
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
|
||||
hasLocalVideo.value = false
|
||||
hasLocalAudio.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -426,16 +240,14 @@ export function useWebRTC() {
|
||||
peerConnection,
|
||||
isConnected,
|
||||
isConnecting,
|
||||
hasLocalVideo,
|
||||
hasLocalAudio,
|
||||
createPeerConnection,
|
||||
getLocalMedia,
|
||||
toggleVideoStream,
|
||||
createPeerConnection,
|
||||
createOffer,
|
||||
createAnswer,
|
||||
setRemoteAnswer,
|
||||
setRemoteOffer,
|
||||
addIceCandidate,
|
||||
toggleCamera,
|
||||
toggleMicrophone,
|
||||
closeConnection,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,15 @@ export const useChatStore = defineStore("chat", () => {
|
||||
|
||||
try {
|
||||
const offer = JSON.parse(data.content || data.message_content || "{}")
|
||||
console.log("收到Offer:", offer)
|
||||
|
||||
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")
|
||||
@@ -343,9 +351,18 @@ export const useChatStore = defineStore("chat", () => {
|
||||
|
||||
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成功")
|
||||
console.log("✅ 发送Answer成功")
|
||||
} catch (error) {
|
||||
console.error("处理Offer失败:", error)
|
||||
rejectCall("failed")
|
||||
@@ -363,12 +380,22 @@ export const useChatStore = defineStore("chat", () => {
|
||||
|
||||
try {
|
||||
const answer = JSON.parse(data.content || data.message_content || "{}")
|
||||
console.log("收到Answer:", answer)
|
||||
|
||||
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")
|
||||
@@ -481,6 +508,13 @@ export const useChatStore = defineStore("chat", () => {
|
||||
callConnectionStatus.value = "对方已接听,正在连接..."
|
||||
console.log("通话已被接受,开始交换信令")
|
||||
|
||||
// 显示友好提示
|
||||
setTimeout(() => {
|
||||
if (callStatus.value === "connecting") {
|
||||
callConnectionStatus.value = "对方已接听,正在建立连接..."
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
try {
|
||||
if (!webrtc.localStream.value) {
|
||||
console.log("获取本地媒体用于创建Offer")
|
||||
@@ -492,6 +526,15 @@ export const useChatStore = defineStore("chat", () => {
|
||||
|
||||
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,
|
||||
@@ -500,7 +543,7 @@ export const useChatStore = defineStore("chat", () => {
|
||||
currentCall.value.callType === "video" ? 6 : 7,
|
||||
)
|
||||
|
||||
console.log("发送Offer成功")
|
||||
console.log("✅ 发送Offer成功")
|
||||
} catch (error) {
|
||||
console.error("创建Offer失败:", error)
|
||||
endCall("failed")
|
||||
|
||||
Reference in New Issue
Block a user