diff --git a/WebApp/client/public/onebyone/index.html b/WebApp/client/public/onebyone/index.html index d1f34ef..d63811f 100644 --- a/WebApp/client/public/onebyone/index.html +++ b/WebApp/client/public/onebyone/index.html @@ -288,12 +288,16 @@
Sarah Chen
- -
在线
+ +
在线
- -
- +
+ + + +
diff --git a/WebApp/client/public/onebyone/renderer.js b/WebApp/client/public/onebyone/renderer.js index 7a8e7c5..9c14a59 100644 --- a/WebApp/client/public/onebyone/renderer.js +++ b/WebApp/client/public/onebyone/renderer.js @@ -143,6 +143,9 @@ class UIRenderer { 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; @@ -374,6 +377,30 @@ class UIRenderer { 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'); + } + } } // 渲染远程用户 @@ -389,6 +416,49 @@ class UIRenderer { 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') { + 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中添加方法 diff --git a/WebApp/client/public/onebyone/store.js b/WebApp/client/public/onebyone/store.js index d161382..7d4b17f 100644 --- a/WebApp/client/public/onebyone/store.js +++ b/WebApp/client/public/onebyone/store.js @@ -233,23 +233,31 @@ class CallStateManager { // 如果是关闭视频,释放摄像头资源 if (mediaType === 'video' && !value && this.state.localStream) { + this.state.session.localUser.mediaState.video = false; this.state.localStream.getTracks().forEach(track => { if (track.kind === 'video') { track.stop(); } }); + // 发送媒体状态到服务器 + this.emitMediaStateChange(); } // 如果是音频状态变化,控制本地音频轨道 if (mediaType === 'audio' && this.state.localStream) { + this.state.session.localUser.mediaState.audio = value; this.state.localStream.getTracks().forEach(track => { if (track.kind === 'audio') { track.enabled = value; } }); + // 发送媒体状态到服务器 + this.emitMediaStateChange(); } + // 通知UI更新用户列表 + this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); } @@ -362,12 +370,17 @@ class CallStateManager { }; // 初始化 RenderStreaming 实例后 this.renderstreaming.onMessage = (data) => { - console.log('收到聊天:', data); + console.log('收到消息:', data); if (data.type === 'chat-message') { // 处理聊天 // 添加到列表并更新UI chatMessage.handleChatMessage(data.message); + } else if (data.type === 'media-state-changed') { + // 处理媒体状态变化 + console.log('收到媒体状态变化:', data.data); + // 更新远程用户的媒体状态 + this.updateRemoteMedia(data.data); } else if (data.type === 'on-message') { } @@ -450,23 +463,9 @@ class CallStateManager { ...mediaState }; this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState }); + // 通知UI更新用户列表 + this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser }); } - - // 添加消息 - addMessage(message) { - chatMessage.addMessage(message); - } - - // 发送聊天消息 - sendChatMessage(message) { - chatMessage.sendChatMessage(message, this.renderstreaming); - } - - // 切换侧边栏 - toggleSidebar() { - chatMessage.toggleSidebar(); - } - // 结束通话 endCall() { clearInterval(this.durationInterval); @@ -801,7 +800,13 @@ class CallStateManager { ...this.state.session.localUser.mediaState }; console.log('[WebSocket Emit] media-state-changed:', payload); - // socket.emit('media-state-changed', payload); + // 使用WebRTC发送媒体状态变化 + if (this.renderstreaming) { + this.renderstreaming.sendMessage({ + type: 'media-state-changed', + data: payload + }); + } } // 显示统计信息