【m】状态同步

This commit is contained in:
zhangzheng
2026-03-12 17:53:34 +08:00
parent 7b92f69d6a
commit 4ce99ae140
5 changed files with 93 additions and 23 deletions

View File

@@ -112,7 +112,7 @@
与 Sarah 的通话 与 Sarah 的通话
</h1> </h1>
<div class="flex items-center gap-3 text-xs text-gray-400"> <div class="flex items-center gap-3 text-xs text-gray-400">
<span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span> <span id="remoteNetworkIndicator" class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
<span id="remoteNetworkQuality" class="flex items-center gap-1"> <span id="remoteNetworkQuality" class="flex items-center gap-1">
<i class="fas fa-signal"></i> <i class="fas fa-signal"></i>
<span>优秀</span> <span>优秀</span>

View File

@@ -83,8 +83,8 @@ const mockCallSession = {
id: "user-remote-002", id: "user-remote-002",
name: "Unity", name: "Unity",
avatar: "/images/p2.png", avatar: "/images/p2.png",
status: "online", // online | offline | connecting status: "offline", // online | offline | connecting
networkQuality: "excellent", // excellent | good | fair | poor networkQuality: "no_signal", // excellent | good | fair | poor | no_signal
mediaState: { mediaState: {
audio: true, audio: true,
video: true, video: true,

View File

@@ -20,6 +20,8 @@ class UIRenderer {
callDuration: document.getElementById('callDuration'), callDuration: document.getElementById('callDuration'),
encryptionBadge: document.getElementById('encryptionBadge'), encryptionBadge: document.getElementById('encryptionBadge'),
unreadBadge: document.getElementById('unreadBadge'), unreadBadge: document.getElementById('unreadBadge'),
remoteNetworkIndicator: document.getElementById('remoteNetworkIndicator'),
remoteNetworkQuality: document.getElementById('remoteNetworkQuality'),
// 远端视频 // 远端视频
remoteVideo: document.getElementById('remoteVideo'), remoteVideo: document.getElementById('remoteVideo'),
@@ -255,10 +257,9 @@ class UIRenderer {
// 渲染header中的网络状态 // 渲染header中的网络状态
renderHeaderNetworkStatus(networkQuality) { renderHeaderNetworkStatus(networkQuality) {
const networkQualityElement = document.getElementById('remoteNetworkQuality'); if (this.elements.remoteNetworkQuality) {
if (networkQualityElement) { const textElement = this.elements.remoteNetworkQuality.querySelector('span');
const textElement = networkQualityElement.querySelector('span'); const iconElement = this.elements.remoteNetworkQuality.querySelector('i');
const iconElement = networkQualityElement.querySelector('i');
if (textElement && iconElement) { if (textElement && iconElement) {
let qualityText = '未知'; let qualityText = '未知';
@@ -350,10 +351,7 @@ class UIRenderer {
this.elements.disconnectedOverlay.classList.add('hidden'); this.elements.disconnectedOverlay.classList.add('hidden');
} }
// 隐藏占位背景
if (this.elements.remoteVideoPlaceholder) {
this.elements.remoteVideoPlaceholder.classList.add('hidden');
}
// 获取视频轨道并处理分辨率 // 获取视频轨道并处理分辨率
const videoTracks = stream.getVideoTracks(); const videoTracks = stream.getVideoTracks();
@@ -380,6 +378,15 @@ class UIRenderer {
this.adjustVideoSize(this.elements.remoteVideo, newResolution); this.adjustVideoSize(this.elements.remoteVideo, newResolution);
}); });
} }
// 隐藏连接中提示
if (this.elements.connectingOverlay) {
this.elements.connectingOverlay.classList.add('hidden');
}
// 隐藏占位背景
if (this.elements.remoteVideoPlaceholder) {
this.elements.remoteVideoPlaceholder.classList.add('hidden');
}
} else { } else {
console.log('No valid video tracks in remote stream'); console.log('No valid video tracks in remote stream');
// 清空视频元素的源 // 清空视频元素的源
@@ -507,8 +514,14 @@ class UIRenderer {
const remoteStatusIndicator = remoteUserElement.querySelector('.absolute.-bottom-1.-right-1.w-3.h-3'); const remoteStatusIndicator = remoteUserElement.querySelector('.absolute.-bottom-1.-right-1.w-3.h-3');
if (remoteStatusIndicator) { if (remoteStatusIndicator) {
if (remoteUser.status === 'online') { if (remoteUser.status === 'online') {
// 根据网络质量设置状态指示器颜色
if (remoteUser.networkQuality === 'no_signal') {
remoteStatusIndicator.classList.remove('hidden');
remoteStatusIndicator.className = 'absolute -bottom-1 -right-1 w-3 h-3 bg-gray-500 rounded-full border-2 border-slate-900';
} else {
remoteStatusIndicator.classList.remove('hidden'); remoteStatusIndicator.classList.remove('hidden');
remoteStatusIndicator.className = 'absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-slate-900'; remoteStatusIndicator.className = 'absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-slate-900';
}
} else { } else {
remoteStatusIndicator.classList.add('hidden'); remoteStatusIndicator.classList.add('hidden');
} }
@@ -726,6 +739,11 @@ class UIRenderer {
networkStatusText.textContent = this.getNetworkQualityText(quality); networkStatusText.textContent = this.getNetworkQualityText(quality);
networkStatusText.className = 'text-red-500'; networkStatusText.className = 'text-red-500';
break; break;
case 'no_signal':
icon.className = 'fas fa-times-circle text-gray-500';
networkStatusText.textContent = this.getNetworkQualityText(quality);
networkStatusText.className = 'text-gray-500';
break;
default: default:
icon.className = 'fas fa-question-circle text-gray-400'; icon.className = 'fas fa-question-circle text-gray-400';
networkStatusText.textContent = '未知'; networkStatusText.textContent = '未知';
@@ -754,6 +772,9 @@ class UIRenderer {
case 'poor': case 'poor':
statusClass = 'text-red-500'; statusClass = 'text-red-500';
break; break;
case 'no_signal':
statusClass = 'text-gray-500';
break;
default: default:
statusClass = 'text-gray-400'; statusClass = 'text-gray-400';
} }
@@ -762,8 +783,26 @@ class UIRenderer {
this.elements.connectionQuality.textContent = `连接质量: ${qualityText}`; this.elements.connectionQuality.textContent = `连接质量: ${qualityText}`;
this.elements.connectionQuality.className = `text-xs ${statusClass}`; this.elements.connectionQuality.className = `text-xs ${statusClass}`;
} }
// 同步更新头部网络指示器
this.updateHeaderNetworkIndicator(quality);
} }
// 更新头部网络指示器
updateHeaderNetworkIndicator(networkQuality) {
if (!this.elements.remoteNetworkIndicator) return;
// 根据网络质量设置指示器颜色
if (networkQuality === 'no_signal') {
// 无信号时显示灰色点,取消动画
this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-gray-500 rounded-full';
} else {
// 有信号时显示绿色点,保持动画
this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-green-500 rounded-full animate-pulse';
}
}
// 渲染通话结束 // 渲染通话结束
renderCallEnded() { renderCallEnded() {
console.log('Call ended'); console.log('Call ended');
@@ -787,7 +826,8 @@ class UIRenderer {
'excellent': '优秀', 'excellent': '优秀',
'good': '良好', 'good': '良好',
'fair': '一般', 'fair': '一般',
'poor': '较差' 'poor': '较差',
'no_signal': '无信号'
}; };
return qualityMap[quality] || quality; return qualityMap[quality] || quality;
} }

View File

@@ -47,11 +47,6 @@ class CallStateManager {
// 初始化 // 初始化
async init() { async init() {
// 启动通话时长计时器
this.durationInterval = setInterval(() => {
this.state.session.duration++;
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
}, 1000);
// 初始化配置 // 初始化配置
await this.setupConfig(); await this.setupConfig();
// 获取本地摄像头视频流 // 获取本地摄像头视频流
@@ -356,7 +351,17 @@ class CallStateManager {
// 通知UI远程流已更新 // 通知UI远程流已更新
this.notify({ type: 'REMOTE_STREAM_OBTAINED', stream: this.state.remoteStream }); this.notify({ type: 'REMOTE_STREAM_OBTAINED', stream: this.state.remoteStream });
console.log('Notified UI about remote stream update'); console.log('Notified UI about remote stream update');
// 只有当收到远程流时才更新远程用户状态为在线
if (this.state.session.remoteUser.status !== 'online') {
this.updateRemoteUserStatus('online');
// 更新远程用户网络质量为好
this.updateRemoteUserNetworkQuality('good');
// 启动通话时长计时器
this.durationInterval = setInterval(() => {
this.state.session.duration++;
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
}, 1000);
}
// 如果是音频轨道,启动远程音频活动检测 // 如果是音频轨道,启动远程音频活动检测
if (data.track.kind === 'audio') { if (data.track.kind === 'audio') {
this.startRemoteActivityDetection(); this.startRemoteActivityDetection();
@@ -389,6 +394,7 @@ class CallStateManager {
await this.renderstreaming.start(); await this.renderstreaming.start();
await this.renderstreaming.createConnection(connectionId); await this.renderstreaming.createConnection(connectionId);
// 启动网络质量检测 // 启动网络质量检测
this.startNetworkQualityDetection(); this.startNetworkQualityDetection();
@@ -408,6 +414,11 @@ class CallStateManager {
async hangUp() { async hangUp() {
this.clearStatsMessage(); // 清除统计信息 this.clearStatsMessage(); // 清除统计信息
this.stopNetworkQualityDetection(); // 停止网络质量检测 this.stopNetworkQualityDetection(); // 停止网络质量检测
// 停止通话时长计时器
if (this.durationInterval) {
clearInterval(this.durationInterval);
this.durationInterval = null;
}
console.log(`Disconnect peer on ${this.connectionId}.`); console.log(`Disconnect peer on ${this.connectionId}.`);
// 删除连接并停止WebRTC // 删除连接并停止WebRTC
@@ -421,6 +432,9 @@ class CallStateManager {
this.renderstreaming = null; this.renderstreaming = null;
} }
// 更新远程用户状态为离线
this.updateRemoteUserStatus('offline');
this.updateRemoteUserNetworkQuality('no_signal');
this.connectionId = null; this.connectionId = null;
this.state.session.status = 'ended'; this.state.session.status = 'ended';
this.notify({ type: 'CALL_ENDED' }); this.notify({ type: 'CALL_ENDED' });
@@ -466,9 +480,22 @@ class CallStateManager {
// 通知UI更新用户列表 // 通知UI更新用户列表
this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser });
} }
// 更新远端用户状态
updateRemoteUserStatus(status) {
this.state.session.remoteUser.status = status;
this.notify({ type: 'REMOTE_MEDIA_CHANGE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser });
}
updateRemoteUserNetworkQuality(networkQuality) {
this.state.session.remoteUser.networkQuality = networkQuality;
this.notify({ type: 'REMOTE_MEDIA_CHANGE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser });
}
// 结束通话 // 结束通话
endCall() { endCall() {
if (this.durationInterval) {
clearInterval(this.durationInterval); clearInterval(this.durationInterval);
this.durationInterval = null;
}
this.state.session.status = 'ended'; this.state.session.status = 'ended';
this.notify({ type: 'CALL_ENDED' }); this.notify({ type: 'CALL_ENDED' });
@@ -512,7 +539,7 @@ class CallStateManager {
simulateNetworkChange() { simulateNetworkChange() {
// 模拟网络质量变化 // 模拟网络质量变化
const qualities = ['good', 'fair', 'excellent', 'poor']; const qualities = ['good', 'fair', 'excellent', 'poor', 'no_signal'];
setInterval(() => { setInterval(() => {
if (Math.random() > 0.8) { if (Math.random() > 0.8) {
const quality = qualities[Math.floor(Math.random() * qualities.length)]; const quality = qualities[Math.floor(Math.random() * qualities.length)];
@@ -585,6 +612,9 @@ class CallStateManager {
} else { } else {
quality = 'excellent'; quality = 'excellent';
} }
} else {
// 没有收到任何RTP包设置为无信号状态
quality = 'no_signal';
} }
// 更新网络质量状态 // 更新网络质量状态

View File

@@ -336,7 +336,7 @@ function onCandidate(ws: WebSocket, message: any): void {
onDisconnect(ws, connectionId); onDisconnect(ws, connectionId);
} else { } else {
// 发送ping消息 // 发送ping消息
ws.send(JSON.stringify({ type: "ping" })); ws.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: { type: "ping"} }));
console.log('WebSocket connection heartbeat, lastActivity: ', (ws as any).lastActivity); console.log('WebSocket connection heartbeat, lastActivity: ', (ws as any).lastActivity);
} }
}, 3000); }, 3000);