/** * UI渲染器 * 负责将状态映射到DOM,与状态管理解耦 */ import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js'; import {mockCallSession } from './models.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'), // 远端视频 remoteVideo: document.getElementById('remoteVideo'), remoteAvatar: document.getElementById('remoteAvatar'), remoteName: document.getElementById('remoteName'), remoteStatus: document.getElementById('remoteStatus'), remoteSpeakingIndicator: document.getElementById('remoteSpeakingIndicator'), remoteAudioWave: document.getElementById('remoteAudioWave'), 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"]'), // 控制按钮 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.render(this.stateManager.getState(), { type: 'INIT' }); } // 绑定事件监听器 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(state.messages); this.renderUserList(state.session.localUser, state.session.remoteUser); 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_MEDIA_CHANGE': this.renderRemoteVideo(state.session.remoteUser); this.renderUserList(state.session.localUser, state.session.remoteUser); break; case 'NEW_MESSAGE': this.renderChatMessages(state.messages); this.renderUnreadCount(changes.unreadCount); break; case 'SIDEBAR_TOGGLE': this.renderSidebar(changes.isOpen); break; case 'NETWORK_CHANGE': this.renderNetworkStatus(changes.quality); break; case 'CALL_ENDED': this.renderCallEnded(); break; } } // 渲染头部 renderHeader(session) { if (this.elements.headerTitle) { this.elements.headerTitle.textContent = `与 ${session.remoteUser.name} 的通话`; } if (this.elements.encryptionBadge) { toggleElement(this.elements.encryptionBadge, session.isEncrypted); } this.renderCallDuration(session.duration); } // 渲染通话时长 renderCallDuration(duration) { if (this.elements.callDuration) { this.elements.callDuration.textContent = formatTime(duration); } } // 渲染远端视频 renderRemoteVideo(remoteUser) { if (this.elements.remoteName) { this.elements.remoteName.textContent = remoteUser.name; } if (this.elements.remoteAvatar) { this.elements.remoteAvatar.src = remoteUser.avatar; } if (this.elements.remoteStatus) { this.elements.remoteStatus.textContent = this.getStatusText(remoteUser.status); } // 渲染说话状态 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); } // 渲染本地视频 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'); } } // 渲染本地用户状态 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; // 渲染本地用户 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 remoteUserElement = this.elements.userList.querySelector('[data-user-id="remote"]'); if (remoteUserElement) { // 渲染远程用户头像 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; } } } // 渲染控制按钮 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 = `
`; } else { // 文本消息 contentHTML = ` `; } messageDiv.innerHTML = ` `; 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) { const showNetworkStatus = quality !== 'excellent'; toggleElement(this.elements.networkStatus, showNetworkStatus); if (showNetworkStatus) { this.elements.networkStatusText.textContent = this.getNetworkQualityText(quality); } } if (this.elements.connectionQuality) { this.elements.connectionQuality.textContent = `连接质量: ${this.getNetworkQualityText(quality)}`; } } // 渲染通话结束 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': '较差' }; return qualityMap[quality] || quality; } // 销毁 destroy() { if (this.unsubscribe) { this.unsubscribe(); } } } export default UIRenderer;