/** * UI渲染器 * 负责将状态映射到DOM,与状态管理解耦 */ import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js'; import { mockCallSession } from './models.js'; import chatMessage from './chatmessage.js'; class UIRenderer { constructor(stateManager) { this.stateManager = stateManager; this.unsubscribe = stateManager.subscribe(this.render.bind(this)); // 缓存 DOM 元素 this.elements = { // 头部和底部 header: document.querySelector('header'), footer: document.querySelector('footer'), // 头部内容 headerTitle: document.getElementById('headerTitle'), callDuration: document.getElementById('callDuration'), encryptionBadge: document.getElementById('encryptionBadge'), unreadBadge: document.getElementById('unreadBadge'), remoteNetworkIndicator: document.getElementById('remoteNetworkIndicator'), remoteNetworkQuality: document.getElementById('remoteNetworkQuality'), // 远端视频 remoteVideo: document.getElementById('remoteVideo'), remoteVideoPlaceholder: document.getElementById('remoteVideoPlaceholder'), remoteAvatar: document.getElementById('remoteAvatar'), remoteName: document.getElementById('remoteName'), remoteStatus: document.getElementById('remoteStatus'), remoteSpeakingIndicator: document.getElementById('remoteSpeakingIndicator'), remoteAudioWave: document.getElementById('remoteAudioWave'), remoteInfoOverlay: document.querySelector('.absolute.top-6.left-6.glass'), networkStatus: document.getElementById('networkStatus'), networkStatusText: document.getElementById('networkStatusText'), connectingOverlay: document.getElementById('connectingOverlay'), // 本地视频 localVideo: document.getElementById('localVideo'), localVideoPlaceholder: document.getElementById('localVideoPlaceholder'), localAudioWave: document.getElementById('localAudioWave'), localInitials: document.getElementById('localInitials'), // 侧边栏 sidebar: document.getElementById('sidebar'), chatContent: document.getElementById('chatContent'), userList: document.getElementById('userList'), localMediaStatus: document.getElementById('localMediaStatus'), localMuteIcon: document.querySelector('[data-field="localUser.muteIcon"]'), userCountDisplay: document.getElementById('userCountDisplay'), // 控制按钮 micBtn: document.getElementById('micBtn'), videoBtn: document.getElementById('videoBtn'), recordBtn: document.getElementById('recordBtn'), connectionQuality: document.getElementById('connectionQuality') }; // 绑定事件监听器 this.bindEventListeners(); // 订阅状态变化 this.unsubscribe = stateManager.subscribe(this.render.bind(this)); // 订阅消息状态变化 this.messageUnsubscribe = chatMessage.subscribe(this.renderMessageState.bind(this)); // 初始化渲染 this.render(this.stateManager.getState(), { type: 'INIT' }); window.addEventListener('resize', () => { if (this.elements.remoteVideo && this.elements.remoteVideo.srcObject) { const stream = this.elements.remoteVideo.srcObject; const videoTracks = stream.getVideoTracks(); if (videoTracks.length > 0) { const resolution = this.getVideoResolution(videoTracks[0]); this.adjustVideoSize(this.elements.remoteVideo, resolution); } } }); } // 渲染消息状态变化 renderMessageState(messageState, changes) { switch (changes.type) { case 'NEW_MESSAGE': this.renderChatMessages(messageState.messages); this.renderUnreadCount(changes.unreadCount); break; case 'SIDEBAR_TOGGLE': this.renderSidebar(changes.isOpen); // 当侧边栏打开时,重置未读消息计数 if (changes.isOpen) { this.renderUnreadCount(0); } else { this.renderUnreadCount(changes.unreadCount); } break; } } // 绑定事件监听器 bindEventListeners() { // 事件监听器 } // 渲染状态变化 render(state, changes) { switch (changes.type) { case 'INIT': this.renderHeader(state.session); this.renderRemoteVideo(state.session.remoteUser); this.renderLocalVideo(state.session.localUser, state.localStream); this.renderControlButtons(state.session.localUser.mediaState); this.renderChatMessages(chatMessage.getMessageState().messages); this.renderUserList(state.session.localUser, state.session.remoteUser); // 初始化时检查远程流状态,显示或隐藏占位背景 if (this.elements.remoteVideoPlaceholder) { if (state.remoteStream) { this.elements.remoteVideoPlaceholder.classList.add('hidden'); } else { this.elements.remoteVideoPlaceholder.classList.remove('hidden'); } } break; case 'DURATION_UPDATE': this.renderCallDuration(changes.duration); break; case 'LOCAL_MEDIA_CHANGE': this.renderControlButtons(state.session.localUser.mediaState); this.renderLocalVideo(state.session.localUser, state.localStream); this.renderLocalUserStatus(state.session.localUser); this.renderUserList(state.session.localUser, state.session.remoteUser); break; case 'LOCAL_STREAM_OBTAINED': this.renderLocalStream(state.localStream); this.renderLocalVideo(state.session.localUser, state.localStream); break; case 'REMOTE_STREAM_OBTAINED': this.renderRemoteStream(state.remoteStream); // 当获取到远程流时,隐藏连接中提示 if (this.elements.connectingOverlay) { this.elements.connectingOverlay.classList.add('hidden'); } break; case 'REMOTE_MEDIA_CHANGE': this.renderRemoteVideo(state.session.remoteUser); this.renderUserList(state.session.localUser, state.session.remoteUser); break; case 'USER_LIST_UPDATE': this.renderUserList(changes.localUser, changes.remoteUser); break; case 'NETWORK_CHANGE': this.renderNetworkStatus(changes.quality); break; case 'CALL_STATUS_CHANGE': this.renderCallStatus(changes.status); break; case 'CALL_ENDED': this.renderCallEnded(); break; } } // 渲染通话状态 renderCallStatus(status) { if (this.elements.connectingOverlay) { if (status === 'connecting') { this.elements.connectingOverlay.classList.remove('hidden'); } else { this.elements.connectingOverlay.classList.add('hidden'); } } } // 渲染头部 renderHeader(session) { if (this.elements.headerTitle) { // 未连接时不显示红框部分 if (session.status === 'idle' || session.status === 'connecting') { this.elements.headerTitle.textContent = '通话'; } else { this.elements.headerTitle.textContent = `与 ${session.remoteUser.name} 的通话`; } } if (this.elements.encryptionBadge) { toggleElement(this.elements.encryptionBadge, session.isEncrypted); } // 始终显示网络状态指示器和质量 if (this.elements.remoteNetworkIndicator) { this.elements.remoteNetworkIndicator.classList.remove('hidden'); } if (this.elements.remoteNetworkQuality) { this.elements.remoteNetworkQuality.classList.remove('hidden'); } this.renderCallDuration(session.duration); } // 渲染通话时长 renderCallDuration(duration) { if (this.elements.callDuration) { this.elements.callDuration.textContent = formatTime(duration); } } // 渲染远端视频 renderRemoteVideo(remoteUser) { // 找到远端信息覆盖层元素 const remoteInfoOverlay = this.elements.remoteName?.closest('.absolute.top-6.left-6.glass'); // 未连接时不显示红框部分 if (remoteUser.status === 'offline') { // 隐藏整个远端信息覆盖层 if (remoteInfoOverlay) { remoteInfoOverlay.classList.add('hidden'); } } else { // 显示远端信息覆盖层 if (remoteInfoOverlay) { remoteInfoOverlay.classList.remove('hidden'); } // 连接后更新头像和名称数据 if (this.elements.remoteName) { this.elements.remoteName.textContent = remoteUser.name; } if (this.elements.remoteAvatar) { this.elements.remoteAvatar.src = remoteUser.avatar; this.elements.remoteAvatar.classList.remove('hidden'); } if (this.elements.remoteStatus) { // 显示与黄框中一致的状态 if (!remoteUser.mediaState.audio) { this.elements.remoteStatus.textContent = '静音中'; } else if (!remoteUser.mediaState.video) { this.elements.remoteStatus.textContent = '视频关闭'; } else { this.elements.remoteStatus.textContent = this.getStatusText(remoteUser.status); } } } // 同步更新侧边栏用户列表 this.renderUserList(this.stateManager.getState().session.localUser, remoteUser); // 当远程视频关闭时显示占位符 if (this.elements.remoteVideoPlaceholder) { const shouldShowPlaceholder = !remoteUser.mediaState.video; toggleElement(this.elements.remoteVideoPlaceholder, shouldShowPlaceholder); // 更新占位符文本内容 if (shouldShowPlaceholder) { const placeholderContent = this.elements.remoteVideoPlaceholder.querySelector('.text-center'); if (placeholderContent) { const titleElement = placeholderContent.querySelector('p.text-white.text-lg.font-medium'); if (titleElement) { titleElement.textContent = '对方摄像头已关闭'; } const subtitleElement = placeholderContent.querySelector('p.text-sm.text-gray-400'); if (subtitleElement) { subtitleElement.textContent = '对方暂时关闭了视频'; } } } else { // 恢复默认占位符文本 const placeholderContent = this.elements.remoteVideoPlaceholder.querySelector('.text-center'); if (placeholderContent) { const titleElement = placeholderContent.querySelector('p.text-white.text-lg.font-medium'); if (titleElement) { titleElement.textContent = '等待对方连接...'; } const subtitleElement = placeholderContent.querySelector('p.text-sm.text-gray-400'); if (subtitleElement) { subtitleElement.textContent = '请确保对方已加入通话'; } } } } // 渲染说话状态 if (this.elements.remoteSpeakingIndicator) { toggleElement(this.elements.remoteSpeakingIndicator, remoteUser.mediaState.isSpeaking); } if (this.elements.remoteAudioWave) { toggleElement(this.elements.remoteAudioWave, remoteUser.mediaState.isSpeaking && remoteUser.mediaState.audio); } // 渲染网络状态 this.renderNetworkStatus(remoteUser.networkQuality); // 渲染header中的网络状态 this.renderHeaderNetworkStatus(remoteUser.networkQuality); } // 渲染header中的网络状态 renderHeaderNetworkStatus(networkQuality) { if (this.elements.remoteNetworkQuality) { const textElement = this.elements.remoteNetworkQuality.querySelector('span'); const iconElement = this.elements.remoteNetworkQuality.querySelector('i'); if (textElement && iconElement) { let qualityText = '未知'; let iconClass = 'fas fa-signal text-gray-400'; switch (networkQuality) { case 'excellent': qualityText = '优秀'; iconClass = 'fas fa-signal text-green-400'; break; case 'good': qualityText = '良好'; iconClass = 'fas fa-signal text-green-500'; break; case 'fair': qualityText = '一般'; iconClass = 'fas fa-signal text-yellow-400'; break; case 'poor': qualityText = '较差'; iconClass = 'fas fa-signal text-red-400'; break; } textElement.textContent = qualityText; iconElement.className = iconClass; } } } // 渲染本地视频 renderLocalVideo(localUser, localStream) { if (this.elements.localVideoPlaceholder) { // 当没有视频流或视频关闭时显示占位符 const shouldShowPlaceholder = !localStream || !localUser.mediaState.video; toggleElement(this.elements.localVideoPlaceholder, shouldShowPlaceholder); } if (this.elements.localAudioWave) { toggleElement(this.elements.localAudioWave, localUser.mediaState.isSpeaking); } // 同时渲染本地用户状态 this.renderLocalUserStatus(localUser); } // 渲染本地视频流 renderLocalStream(stream) { if (this.elements.localVideo && stream) { this.elements.localVideo.srcObject = stream; this.elements.localVideo.autoplay = true; this.elements.localVideo.muted = true; // 本地视频静音,避免回声 console.log('srcObject set successfully:', this.elements.localVideo.srcObject); // 隐藏断开连接覆盖层 if (this.elements.disconnectedOverlay) { this.elements.disconnectedOverlay.classList.add('hidden'); } } else { console.error('Either localVideo element or stream is missing'); } } // 渲染远程视频流 renderRemoteStream(stream) { if (this.elements.remoteVideo && stream) { console.log('Rendering remote stream:', stream); // 即使流对象相同,也要重新设置,确保视频元素能够识别轨道变化 this.elements.remoteVideo.srcObject = null; // 延迟设置srcObject,确保视频元素能够正确处理 setTimeout(() => { this.elements.remoteVideo.srcObject = stream; console.log('Remote stream reset successfully:', stream); // 确保视频元素的属性正确设置 this.elements.remoteVideo.autoplay = true; this.elements.remoteVideo.playsinline = true; this.elements.remoteVideo.muted = false; // 不要静音远程视频,否则听不到对方的声音 // 关键设置:启用硬件加速和最佳质量渲染 this.elements.remoteVideo.style.transform = 'translateZ(0)'; // 启用硬件加速 this.elements.remoteVideo.style.imageRendering = 'pixelated'; // 保持像素清晰 this.elements.remoteVideo.style.objectFit = 'contain'; // 保持比例 // 隐藏断开连接覆盖层 if (this.elements.disconnectedOverlay) { this.elements.disconnectedOverlay.classList.add('hidden'); } // 获取视频轨道并处理分辨率 const videoTracks = stream.getVideoTracks(); console.log('Remote video tracks:', videoTracks); // 检查是否有有效的视频轨道 const hasValidVideoTrack = videoTracks.length > 0 && videoTracks.some(track => { // 检查轨道是否已停止或被禁用 return track.readyState === 'live'; }); console.log('Has valid video track:', hasValidVideoTrack); if (hasValidVideoTrack) { console.log('Found valid video tracks, updating resolution'); const activeVideoTrack = videoTracks.find(track => track.readyState === 'live'); if (activeVideoTrack) { const resolution = this.getVideoResolution(activeVideoTrack); this.adjustVideoSize(this.elements.remoteVideo, resolution); // 监听轨道变化,处理分辨率调整 activeVideoTrack.addEventListener('resize', () => { const newResolution = this.getVideoResolution(activeVideoTrack); 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 { console.log('No valid video tracks in remote stream'); // 清空视频元素的源 this.elements.remoteVideo.srcObject = null; // 显示占位背景 if (this.elements.remoteVideoPlaceholder) { this.elements.remoteVideoPlaceholder.classList.remove('hidden'); } } }, 50); // 增加延迟时间,确保视频元素有足够的时间处理 } else { console.error('Either remoteVideo element or stream is missing'); // 清空视频元素的源 if (this.elements.remoteVideo) { this.elements.remoteVideo.srcObject = null; } // 显示占位背景 if (this.elements.remoteVideoPlaceholder) { this.elements.remoteVideoPlaceholder.classList.remove('hidden'); } } } // 渲染本地用户状态 renderLocalUserStatus(localUser) { // 更新本地媒体状态文本 if (this.elements.localMediaStatus) { if (!localUser.mediaState.audio) { this.elements.localMediaStatus.textContent = '静音中'; this.elements.localMediaStatus.className = 'text-xs text-gray-500'; } else if (!localUser.mediaState.video) { this.elements.localMediaStatus.textContent = '视频关闭'; this.elements.localMediaStatus.className = 'text-xs text-gray-500'; } else { this.elements.localMediaStatus.textContent = '在线'; this.elements.localMediaStatus.className = 'text-xs text-green-400'; } } // 更新静音图标 if (this.elements.localMuteIcon) { if (!localUser.mediaState.audio) { this.elements.localMuteIcon.classList.remove('hidden'); this.elements.localMuteIcon.className = 'fas fa-microphone-slash text-gray-500 text-xs'; } else { this.elements.localMuteIcon.classList.add('hidden'); } } } // 渲染侧边栏用户列表 renderUserList(localUser, remoteUser) { if (!this.elements.userList) return; // 计算通话成员总数 let userCount = 1; // 至少有本地用户 if (remoteUser.status !== 'offline') { userCount++; // 如果远程用户在线,增加计数 } // 更新通话成员总数显示 const userCountElement = this.elements.userList.closest('div').querySelector('h3.text-sm.font-medium.text-gray-400'); if (userCountElement) { userCountElement.textContent = `通话成员 (${userCount})`; } // 渲染本地用户 const localUserElement = this.elements.userList.querySelector('[data-user-id="local"]'); if (localUserElement) { // 渲染本地用户头像 const localAvatar = localUserElement.querySelector('img[data-field="localUser.avatar"]'); if (localAvatar) { localAvatar.src = localUser.avatar; } // 渲染本地用户名字 const localName = localUserElement.querySelector('[data-field="localUser.name"]'); if (localName) { localName.textContent = localUser.name; } // 渲染本地用户媒体状态 const localMediaStatus = localUserElement.querySelector('[data-field="localUser.mediaStatus"]'); if (localMediaStatus) { if (!localUser.mediaState.audio) { localMediaStatus.textContent = '静音中'; localMediaStatus.className = 'text-xs text-gray-500'; } else if (!localUser.mediaState.video) { localMediaStatus.textContent = '视频关闭'; localMediaStatus.className = 'text-xs text-gray-500'; } else { localMediaStatus.textContent = '在线'; localMediaStatus.className = 'text-xs text-green-400'; } } // 渲染本地用户静音图标 const localMuteIcon = localUserElement.querySelector('[data-field="localUser.muteIcon"]'); if (localMuteIcon) { if (!localUser.mediaState.audio) { localMuteIcon.classList.remove('hidden'); localMuteIcon.className = 'fas fa-microphone-slash text-gray-500 text-xs'; } else { localMuteIcon.classList.add('hidden'); } } } // 渲染远程用户 const remoteUserElement = this.elements.userList.querySelector('[data-user-id="remote"]'); if (remoteUserElement) { // 未连接时不显示远程用户信息 if (remoteUser.status === 'offline') { remoteUserElement.classList.add('hidden'); } else { // 连接后显示远程用户信息并更新数据 remoteUserElement.classList.remove('hidden'); // 渲染远程用户头像 const remoteAvatar = remoteUserElement.querySelector('img[data-field="remoteUser.avatar"]'); if (remoteAvatar) { remoteAvatar.src = remoteUser.avatar; } // 渲染远程用户名字 const remoteName = remoteUserElement.querySelector('[data-field="remoteUser.name"]'); if (remoteName) { remoteName.textContent = remoteUser.name; } // 渲染远程用户媒体状态 const remoteMediaStatus = remoteUserElement.querySelector('[data-field="remoteUser.mediaStatus"]'); if (remoteMediaStatus) { if (!remoteUser.mediaState.audio) { remoteMediaStatus.textContent = '静音中'; remoteMediaStatus.className = 'text-xs text-gray-500'; } else if (!remoteUser.mediaState.video) { remoteMediaStatus.textContent = '视频关闭'; remoteMediaStatus.className = 'text-xs text-gray-500'; } else { remoteMediaStatus.textContent = '在线'; remoteMediaStatus.className = 'text-xs text-green-400'; } } // 渲染远程用户在线状态指示器 const remoteStatusIndicator = remoteUserElement.querySelector('.absolute.-bottom-1.-right-1.w-3.h-3'); if (remoteStatusIndicator) { 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.className = 'absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-slate-900'; } } else { remoteStatusIndicator.classList.add('hidden'); } } // 渲染远程用户静音图标 const remoteMuteIcon = remoteUserElement.querySelector('[data-field="remoteUser.muteIcon"]'); if (remoteMuteIcon) { if (!remoteUser.mediaState.audio) { remoteMuteIcon.classList.remove('hidden'); remoteMuteIcon.className = 'fas fa-microphone-slash text-gray-500 text-xs'; } else { remoteMuteIcon.classList.add('hidden'); } } // 渲染远程用户说话状态指示器 const remoteSpeakingIndicator = remoteUserElement.querySelector('[data-field="remoteUser.speakingIndicator"]'); if (remoteSpeakingIndicator) { if (remoteUser.mediaState.isSpeaking && remoteUser.mediaState.audio) { remoteSpeakingIndicator.classList.remove('hidden'); } else { remoteSpeakingIndicator.classList.add('hidden'); } } } } } // 在renderer.js中添加方法 // 获取视频流分辨率 getVideoResolution(track) { if (track && track.getSettings) { const settings = track.getSettings(); return { width: settings.width || 640, height: settings.height || 480 }; } return { width: 640, height: 480 }; // 默认值 } // 调整视频元素大小并居中显示 adjustVideoSize(videoElement, resolution) { if (!videoElement) return; const { width, height } = resolution; const aspectRatio = width / height; // 根据容器大小和视频宽高比调整视频显示 const container = videoElement.parentElement; const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; // 启用硬件加速 videoElement.style.transform = 'translateZ(0)'; videoElement.style.willChange = 'transform'; // 设置容器为flex布局,使视频居中 container.style.display = 'flex'; container.style.alignItems = 'center'; container.style.justifyContent = 'center'; // 优化图像渲染 videoElement.style.imageRendering = 'pixelated'; // 确保视频元素在容器内正确显示 videoElement.style.maxWidth = '100%'; videoElement.style.maxHeight = '100%'; videoElement.style.objectFit = 'contain'; // 保持原始比例,不裁剪 } // 渲染控制按钮 renderControlButtons(mediaState) { if (this.elements.micBtn) { toggleButtonState(this.elements.micBtn, !mediaState.audio); } if (this.elements.videoBtn) { toggleButtonState(this.elements.videoBtn, !mediaState.video); } if (this.elements.recordBtn) { toggleButtonState(this.elements.recordBtn, mediaState.recording); } } // 渲染聊天消息 renderChatMessages(messages) { if (!this.elements.chatContent) return; // 清空聊天内容 this.elements.chatContent.innerHTML = ''; // 添加通话开始时间 const startTimeElement = document.createElement('div'); startTimeElement.className = 'text-center text-xs text-gray-500 my-4'; const startTime = messages[0]?.timestamp || new Date().toISOString(); startTimeElement.textContent = `通话开始 ${formatTimestamp(startTime)}`; this.elements.chatContent.appendChild(startTimeElement); // 添加消息 messages.forEach(message => { const messageElement = this.createMessageElement(message); this.elements.chatContent.appendChild(messageElement); }); // 滚动到底部 this.elements.chatContent.scrollTop = this.elements.chatContent.scrollHeight; } // 创建消息元素 createMessageElement(message) { const messageDiv = document.createElement('div'); // 根据消息类型设置不同的CSS类 let messageClass = 'chat-bubble'; if (message.type === 'system') { messageClass += ' message-system'; } else if (message.isSelf) { messageClass += ' message-self'; } else { messageClass += ' message-other'; } messageDiv.className = messageClass; messageDiv.dataset.messageId = message.id; let contentHTML = ''; if (message.type === 'file' && message.content.startsWith('data:image/')) { // 图片消息 contentHTML = `
${message.fileName || '图片'} ${message.fileName ? `
${message.fileName}
` : ''}
`; } else { // 文本消息 contentHTML = `
${message.content}
`; } messageDiv.innerHTML = `
${message.senderName} ${formatTimestamp(message.timestamp)}
${contentHTML}
`; return messageDiv; } // 渲染未读消息数 renderUnreadCount(count) { if (this.elements.unreadBadge) { if (count > 0) { this.elements.unreadBadge.textContent = count; this.elements.unreadBadge.classList.remove('hidden'); } else { this.elements.unreadBadge.classList.add('hidden'); } } } // 渲染侧边栏 renderSidebar(isOpen) { if (this.elements.sidebar) { if (isOpen) { this.elements.sidebar.classList.remove('hidden'); } else { this.elements.sidebar.classList.add('hidden'); } } } // 渲染网络状态 renderNetworkStatus(quality) { if (this.elements.networkStatus && this.elements.networkStatusText) { // 始终显示网络状态 toggleElement(this.elements.networkStatus, true); // 根据网络质量设置不同的图标和颜色 const networkStatus = this.elements.networkStatus; const networkStatusText = this.elements.networkStatusText; // 清除之前的图标 const existingIcon = networkStatus.querySelector('i'); if (existingIcon) { existingIcon.remove(); } // 创建新的图标元素 const icon = document.createElement('i'); // 根据网络质量设置图标和样式 switch (quality) { case 'excellent': icon.className = 'fas fa-check-circle text-green-400'; networkStatusText.textContent = this.getNetworkQualityText(quality); networkStatusText.className = 'text-green-400'; break; case 'good': icon.className = 'fas fa-signal text-blue-400'; networkStatusText.textContent = this.getNetworkQualityText(quality); networkStatusText.className = 'text-blue-400'; break; case 'fair': icon.className = 'fas fa-exclamation-circle text-yellow-500'; networkStatusText.textContent = this.getNetworkQualityText(quality); networkStatusText.className = 'text-yellow-500'; break; case 'poor': icon.className = 'fas fa-exclamation-triangle text-red-500'; networkStatusText.textContent = this.getNetworkQualityText(quality); networkStatusText.className = 'text-red-500'; 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: icon.className = 'fas fa-question-circle text-gray-400'; networkStatusText.textContent = '未知'; networkStatusText.className = 'text-gray-400'; } // 添加图标到网络状态元素 networkStatus.insertBefore(icon, networkStatusText); } if (this.elements.connectionQuality) { const qualityText = this.getNetworkQualityText(quality); let statusClass = ''; // 根据网络质量设置文本颜色 switch (quality) { case 'excellent': statusClass = 'text-green-400'; break; case 'good': statusClass = 'text-blue-400'; break; case 'fair': statusClass = 'text-yellow-500'; break; case 'poor': statusClass = 'text-red-500'; break; case 'no_signal': statusClass = 'text-gray-500'; break; default: statusClass = 'text-gray-400'; } // 更新连接质量文本和样式 this.elements.connectionQuality.textContent = `连接质量: ${qualityText}`; this.elements.connectionQuality.className = `text-xs ${statusClass}`; } // 同步更新头部网络指示器 this.updateHeaderNetworkIndicator(quality); // 同步更新头部网络质量文本 this.renderHeaderNetworkStatus(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() { console.log('Call ended'); // 跳转到结束通话界面 window.location.href = './endcall/endcall.html'; } // 获取状态文本 getStatusText(status) { const statusMap = { 'online': '在线', 'offline': '离线', 'connecting': '连接中' }; return statusMap[status] || status; } // 获取网络质量文本 getNetworkQualityText(quality) { const qualityMap = { 'excellent': '优秀', 'good': '良好', 'fair': '一般', 'poor': '较差', 'no_signal': '无信号' }; return qualityMap[quality] || quality; } // 销毁 destroy() { if (this.unsubscribe) { this.unsubscribe(); } if (this.messageUnsubscribe) { this.messageUnsubscribe(); } } } export default UIRenderer;