【m】状态同步
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新网络质量状态
|
// 更新网络质量状态
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user