聊天界面优化
This commit is contained in:
@@ -120,7 +120,7 @@ class UIRenderer {
|
|||||||
this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频
|
this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频
|
||||||
this.renderControlButtons(state.session.localUser.mediaState); // 渲染控制按钮
|
this.renderControlButtons(state.session.localUser.mediaState); // 渲染控制按钮
|
||||||
this.renderChatMessages(chatMessage.getMessageState().messages); // 渲染聊天消息
|
this.renderChatMessages(chatMessage.getMessageState().messages); // 渲染聊天消息
|
||||||
this.renderUserList(state.session.localUser, state.session.remoteUser); // 渲染用户列表
|
this.renderUserList(state.session.localUser, state.session.remoteUser, state.participants); // 渲染用户列表
|
||||||
this.renderHeader(state.session); // 渲染头部信息
|
this.renderHeader(state.session); // 渲染头部信息
|
||||||
// 初始化时检查远程流状态,显示或隐藏占位背景
|
// 初始化时检查远程流状态,显示或隐藏占位背景
|
||||||
if (this.elements.remoteVideoPlaceholder) {
|
if (this.elements.remoteVideoPlaceholder) {
|
||||||
@@ -140,7 +140,7 @@ class UIRenderer {
|
|||||||
this.renderControlButtons(state.session.localUser.mediaState); // 渲染控制按钮
|
this.renderControlButtons(state.session.localUser.mediaState); // 渲染控制按钮
|
||||||
this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频
|
this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频
|
||||||
this.renderLocalUserStatus(state.session.localUser); // 渲染本地用户状态
|
this.renderLocalUserStatus(state.session.localUser); // 渲染本地用户状态
|
||||||
this.renderUserList(state.session.localUser, state.session.remoteUser); // 渲染用户列表
|
this.renderUserList(state.session.localUser, state.session.remoteUser, state.participants); // 渲染用户列表
|
||||||
break;
|
break;
|
||||||
case 'LOCAL_STREAM_OBTAINED':
|
case 'LOCAL_STREAM_OBTAINED':
|
||||||
this.renderLocalStream(state.localStream);
|
this.renderLocalStream(state.localStream);
|
||||||
@@ -157,7 +157,7 @@ class UIRenderer {
|
|||||||
case 'REMOTE_MEDIA_CHANGE':
|
case 'REMOTE_MEDIA_CHANGE':
|
||||||
// 远程媒体状态变化 - 更新远程视频和用户列表
|
// 远程媒体状态变化 - 更新远程视频和用户列表
|
||||||
this.renderRemoteVideo(state.session.remoteUser); // 渲染远程视频
|
this.renderRemoteVideo(state.session.remoteUser); // 渲染远程视频
|
||||||
this.renderUserList(state.session.localUser, state.session.remoteUser); // 渲染用户列表
|
this.renderUserList(state.session.localUser, state.session.remoteUser, state.participants); // 渲染用户列表
|
||||||
// Host端:精准更新发送者participant tile的占位背景
|
// Host端:精准更新发送者participant tile的占位背景
|
||||||
if (changes.participantId) {
|
if (changes.participantId) {
|
||||||
this.renderParticipantVideoPlaceholder(changes.participantId, !state.session.remoteUser.mediaState.video);
|
this.renderParticipantVideoPlaceholder(changes.participantId, !state.session.remoteUser.mediaState.video);
|
||||||
@@ -165,7 +165,13 @@ class UIRenderer {
|
|||||||
break;
|
break;
|
||||||
case 'USER_LIST_UPDATE':
|
case 'USER_LIST_UPDATE':
|
||||||
// 用户列表更新 - 重新渲染用户列表
|
// 用户列表更新 - 重新渲染用户列表
|
||||||
this.renderUserList(changes.localUser, changes.remoteUser);
|
this.renderUserList(changes.localUser, changes.remoteUser, state.participants);
|
||||||
|
break;
|
||||||
|
case 'PARTICIPANTS_UPDATE':
|
||||||
|
// Participants信息变化 - 重新渲染用户列表并同步tile名称
|
||||||
|
this.renderUserList(state.session.localUser, state.session.remoteUser, changes.participants || state.participants);
|
||||||
|
// 同步更新participant tile的名称标签
|
||||||
|
this.syncParticipantTileNames(changes.participants || state.participants);
|
||||||
break;
|
break;
|
||||||
case 'NETWORK_CHANGE':
|
case 'NETWORK_CHANGE':
|
||||||
// 网络状态变化 - 渲染网络状态
|
// 网络状态变化 - 渲染网络状态
|
||||||
@@ -271,7 +277,7 @@ class UIRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 同步更新侧边栏用户列表
|
// 同步更新侧边栏用户列表
|
||||||
this.renderUserList(this.stateManager.getState().session.localUser, remoteUser);
|
this.renderUserList(this.stateManager.getState().session.localUser, remoteUser, this.stateManager.getState().participants);
|
||||||
|
|
||||||
// 当远程视频关闭时显示占位符
|
// 当远程视频关闭时显示占位符
|
||||||
if (this.elements.remoteVideoPlaceholder) {
|
if (this.elements.remoteVideoPlaceholder) {
|
||||||
@@ -439,10 +445,12 @@ class UIRenderer {
|
|||||||
`;
|
`;
|
||||||
tile.appendChild(placeholder);
|
tile.appendChild(placeholder);
|
||||||
|
|
||||||
// 参与者名称标签
|
// 参与者名称标签(优先使用participants中的真实姓名)
|
||||||
|
const pInfo = this.stateManager.getState().participants[connectionId];
|
||||||
|
const displayName = pInfo?.name || '参与者';
|
||||||
const label = document.createElement('div');
|
const label = document.createElement('div');
|
||||||
label.className = 'absolute bottom-3 left-3 glass px-3 py-1 rounded-full text-xs flex items-center gap-2';
|
label.className = 'absolute bottom-3 left-3 glass px-3 py-1 rounded-full text-xs flex items-center gap-2';
|
||||||
label.innerHTML = `<i class="fas fa-user text-indigo-400"></i><span>Participant ${connectionId.slice(-4)}</span>`;
|
label.innerHTML = `<i class="fas fa-user text-purple-400"></i><span>${displayName}</span>`;
|
||||||
tile.appendChild(label);
|
tile.appendChild(label);
|
||||||
|
|
||||||
// 在线标识
|
// 在线标识
|
||||||
@@ -506,6 +514,32 @@ class UIRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步更新所有participant tile的名称标签
|
||||||
|
syncParticipantTileNames(participants) {
|
||||||
|
if (!participants) return;
|
||||||
|
const grid = this.elements.participantGrid;
|
||||||
|
if (!grid) return;
|
||||||
|
for (const [participantId, pInfo] of Object.entries(participants)) {
|
||||||
|
this.updateParticipantTileName(participantId, pInfo.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新指定participant tile的名称标签
|
||||||
|
updateParticipantTileName(participantId, name) {
|
||||||
|
const grid = this.elements.participantGrid;
|
||||||
|
if (!grid) return;
|
||||||
|
const tile = grid.querySelector(`[data-participant-id="${participantId}"]`);
|
||||||
|
if (!tile) return;
|
||||||
|
const label = tile.querySelector('.absolute.bottom-3');
|
||||||
|
if (label) {
|
||||||
|
const nameSpan = label.querySelector('span');
|
||||||
|
if (nameSpan && name) {
|
||||||
|
nameSpan.textContent = name;
|
||||||
|
console.log(`Updated tile name for participant ${participantId}: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染Participant端的单一远端视频(Host画面)
|
// 渲染Participant端的单一远端视频(Host画面)
|
||||||
renderSingleRemoteStream(stream) {
|
renderSingleRemoteStream(stream) {
|
||||||
if (this.elements.remoteVideo && stream) {
|
if (this.elements.remoteVideo && stream) {
|
||||||
@@ -623,15 +657,15 @@ class UIRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染侧边栏用户列表
|
// 渲染侧边栏用户列表(支持多Participant动态渲染)
|
||||||
renderUserList(localUser, remoteUser) {
|
renderUserList(localUser, remoteUser, participants) {
|
||||||
if (!this.elements.userList) return;
|
if (!this.elements.userList) return;
|
||||||
|
|
||||||
// 计算通话成员总数
|
const participantsMap = participants || {};
|
||||||
let userCount = 1; // 至少有本地用户
|
const participantCount = Object.keys(participantsMap).length;
|
||||||
if (remoteUser.status !== 'offline') {
|
// Host端使用participants计数,Participant端使用remoteUser状态
|
||||||
userCount++; // 如果远程用户在线,增加计数
|
const isHost = participantCount > 0;
|
||||||
}
|
const userCount = isHost ? (1 + participantCount) : (remoteUser.status !== 'offline' ? 2 : 1);
|
||||||
|
|
||||||
// 更新通话成员总数显示
|
// 更新通话成员总数显示
|
||||||
const userCountElement = this.elements.userList.closest('div').querySelector('h3.text-sm.font-medium.text-gray-400');
|
const userCountElement = this.elements.userList.closest('div').querySelector('h3.text-sm.font-medium.text-gray-400');
|
||||||
@@ -639,116 +673,113 @@ class UIRenderer {
|
|||||||
userCountElement.textContent = `通话成员 (${userCount})`;
|
userCountElement.textContent = `通话成员 (${userCount})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染本地用户
|
// 清空列表并重新渲染
|
||||||
const localUserElement = this.elements.userList.querySelector('[data-user-id="local"]');
|
this.elements.userList.innerHTML = '';
|
||||||
if (localUserElement) {
|
|
||||||
// 渲染本地用户头像
|
// 1. 渲染本地用户(主持人)
|
||||||
const localAvatar = localUserElement.querySelector('img[data-field="localUser.avatar"]');
|
this.elements.userList.appendChild(this.createLocalUserEntry(localUser));
|
||||||
if (localAvatar) {
|
|
||||||
localAvatar.src = localUser.avatar;
|
// 2. Host端:渲染所有participants;Participant端:渲染单一remoteUser
|
||||||
}
|
if (isHost) {
|
||||||
// 渲染本地用户名字
|
for (const [pid, p] of Object.entries(participantsMap)) {
|
||||||
const localName = localUserElement.querySelector('[data-field="localUser.name"]');
|
this.elements.userList.appendChild(this.createParticipantEntry(pid, p));
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
} else if (remoteUser.status !== 'offline') {
|
||||||
|
this.elements.userList.appendChild(this.createRemoteUserEntry(remoteUser));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染远程用户
|
// 创建本地用户条目(主持人)
|
||||||
const remoteUserElement = this.elements.userList.querySelector('[data-user-id="remote"]');
|
createLocalUserEntry(localUser) {
|
||||||
if (remoteUserElement) {
|
const div = document.createElement('div');
|
||||||
// 未连接时不显示远程用户信息
|
div.className = 'flex items-center gap-3 p-2 rounded-lg hover:bg-white/5';
|
||||||
if (remoteUser.status === 'offline') {
|
div.dataset.userId = 'local';
|
||||||
remoteUserElement.classList.add('hidden');
|
|
||||||
} else {
|
|
||||||
// 连接后显示远程用户信息并更新数据
|
|
||||||
remoteUserElement.classList.remove('hidden');
|
|
||||||
|
|
||||||
// 渲染远程用户头像
|
const mediaStatusText = !localUser.mediaState.audio ? '静音中' : (!localUser.mediaState.video ? '视频关闭' : '在线');
|
||||||
const remoteAvatar = remoteUserElement.querySelector('img[data-field="remoteUser.avatar"]');
|
const mediaStatusClass = (!localUser.mediaState.audio || !localUser.mediaState.video) ? 'text-xs text-gray-500' : 'text-xs text-green-400';
|
||||||
if (remoteAvatar) {
|
const muteIconHtml = !localUser.mediaState.audio
|
||||||
remoteAvatar.src = remoteUser.avatar;
|
? '<i class="fas fa-microphone-slash text-gray-500 text-xs"></i>'
|
||||||
}
|
: '';
|
||||||
// 渲染远程用户名字
|
|
||||||
const remoteName = remoteUserElement.querySelector('[data-field="remoteUser.name"]');
|
div.innerHTML = `
|
||||||
if (remoteName) {
|
<img src="${localUser.avatar}" class="w-10 h-10 rounded-full object-cover">
|
||||||
remoteName.textContent = remoteUser.name;
|
<div class="flex-1">
|
||||||
}
|
<div class="text-sm font-medium">
|
||||||
// 渲染远程用户媒体状态
|
${localUser.name}
|
||||||
const remoteMediaStatus = remoteUserElement.querySelector('[data-field="remoteUser.mediaStatus"]');
|
<span class="text-xs bg-indigo-500 px-1.5 rounded ml-1">主持人</span>
|
||||||
if (remoteMediaStatus) {
|
</div>
|
||||||
if (!remoteUser.mediaState.audio) {
|
<div class="${mediaStatusClass}" data-field="localUser.mediaStatus">${mediaStatusText}</div>
|
||||||
remoteMediaStatus.textContent = '静音中';
|
</div>
|
||||||
remoteMediaStatus.className = 'text-xs text-gray-500';
|
${muteIconHtml}
|
||||||
} else if (!remoteUser.mediaState.video) {
|
`;
|
||||||
remoteMediaStatus.textContent = '视频关闭';
|
return div;
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建远程用户条目(Participant端显示Host用)
|
||||||
|
createRemoteUserEntry(remoteUser) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'flex items-center gap-3 p-2 rounded-lg bg-white/5';
|
||||||
|
div.dataset.userId = 'remote';
|
||||||
|
|
||||||
|
const mediaStatusText = !remoteUser.mediaState.audio ? '静音中' : (!remoteUser.mediaState.video ? '视频关闭' : '在线');
|
||||||
|
const mediaStatusClass = (!remoteUser.mediaState.audio || !remoteUser.mediaState.video) ? 'text-xs text-gray-500' : 'text-xs text-green-400';
|
||||||
|
const muteIconHtml = !remoteUser.mediaState.audio
|
||||||
|
? '<i class="fas fa-microphone-slash text-gray-500 text-xs"></i>'
|
||||||
|
: '';
|
||||||
|
const speakingHtml = (remoteUser.mediaState.isSpeaking && remoteUser.mediaState.audio)
|
||||||
|
? '<div class="audio-wave w-6"><span></span><span></span><span></span><span></span><span></span></div>'
|
||||||
|
: '';
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<div class="relative">
|
||||||
|
<img src="${remoteUser.avatar}" class="w-10 h-10 rounded-full object-cover">
|
||||||
|
<div class="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-slate-900"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium">${remoteUser.name}</div>
|
||||||
|
<div class="${mediaStatusClass}">${mediaStatusText}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
${muteIconHtml}
|
||||||
|
${speakingHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建Participant条目(Host端显示每个Participant)
|
||||||
|
createParticipantEntry(participantId, participant) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'flex items-center gap-3 p-2 rounded-lg bg-white/5';
|
||||||
|
div.dataset.userId = `participant_${participantId}`;
|
||||||
|
|
||||||
|
const mediaStatusText = !participant.mediaState.audio ? '静音中' : (!participant.mediaState.video ? '视频关闭' : '在线');
|
||||||
|
const mediaStatusClass = (!participant.mediaState.audio || !participant.mediaState.video) ? 'text-xs text-gray-500' : 'text-xs text-green-400';
|
||||||
|
const muteIconHtml = !participant.mediaState.audio
|
||||||
|
? '<i class="fas fa-microphone-slash text-gray-500 text-xs"></i>'
|
||||||
|
: '';
|
||||||
|
const speakingHtml = (participant.mediaState.isSpeaking && participant.mediaState.audio)
|
||||||
|
? '<div class="audio-wave w-6"><span></span><span></span><span></span><span></span><span></span></div>'
|
||||||
|
: '';
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<div class="relative">
|
||||||
|
<img src="${participant.avatar}" class="w-10 h-10 rounded-full object-cover">
|
||||||
|
<div class="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-slate-900"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium">
|
||||||
|
${participant.name}
|
||||||
|
<span class="text-xs bg-purple-500 px-1.5 rounded ml-1">参与者</span>
|
||||||
|
</div>
|
||||||
|
<div class="${mediaStatusClass}">${mediaStatusText}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
${muteIconHtml}
|
||||||
|
${speakingHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return div;
|
||||||
}
|
}
|
||||||
// 在renderer.js中添加方法
|
// 在renderer.js中添加方法
|
||||||
// 获取视频流分辨率
|
// 获取视频流分辨率
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ class CallStateManager {
|
|||||||
},
|
},
|
||||||
localStream: null, // MediaStream 对象
|
localStream: null, // MediaStream 对象
|
||||||
remoteStream: null, // 单路远端流(兼容旧逻辑,participant端使用)
|
remoteStream: null, // 单路远端流(兼容旧逻辑,participant端使用)
|
||||||
remoteStreams: {} // 多路远端流 Map: { connectionId: MediaStream }(host端使用)
|
remoteStreams: {}, // 多路远端流 Map: { connectionId: MediaStream }(host端使用)
|
||||||
|
participants: {} // 多Participant用户信息 Map: { participantId: { id, name, avatar, mediaState, status } }(host端使用)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听器数组
|
// 监听器数组
|
||||||
@@ -372,6 +373,21 @@ class CallStateManager {
|
|||||||
this.hangUp(); // 房间已关闭,挂断连接
|
this.hangUp(); // 房间已关闭,挂断连接
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// participant加入回调(host收到,新participant加入房间)
|
||||||
|
this.renderstreaming.onParticipantJoined = (participantId) => {
|
||||||
|
console.log(`Participant joined: ${participantId}`);
|
||||||
|
if (!this.state.participants[participantId]) {
|
||||||
|
this.state.participants[participantId] = {
|
||||||
|
id: '',
|
||||||
|
name: '参与者',
|
||||||
|
avatar: '/images/p2.png',
|
||||||
|
mediaState: { audio: false, video: false, isSpeaking: false },
|
||||||
|
status: 'online'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
|
};
|
||||||
|
|
||||||
// participant离开回调(host收到,房间仍然存在)
|
// participant离开回调(host收到,房间仍然存在)
|
||||||
this.renderstreaming.onParticipantLeft = (participantId) => {
|
this.renderstreaming.onParticipantLeft = (participantId) => {
|
||||||
console.log(`Participant left: ${participantId}, room still active`);
|
console.log(`Participant left: ${participantId}, room still active`);
|
||||||
@@ -387,8 +403,11 @@ class CallStateManager {
|
|||||||
this.state.remoteStream.getTracks().forEach(track => track.stop());
|
this.state.remoteStream.getTracks().forEach(track => track.stop());
|
||||||
this.state.remoteStream = null;
|
this.state.remoteStream = null;
|
||||||
}
|
}
|
||||||
|
// 清理该 participant 的用户信息
|
||||||
|
delete this.state.participants[participantId];
|
||||||
// 通知UI更新,用participantId作为connectionId传给renderer
|
// 通知UI更新,用participantId作为connectionId传给renderer
|
||||||
this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId });
|
this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId });
|
||||||
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 轨道事件回调
|
// 轨道事件回调
|
||||||
@@ -424,6 +443,18 @@ class CallStateManager {
|
|||||||
targetStream.addTrack(data.track);
|
targetStream.addTrack(data.track);
|
||||||
console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId);
|
console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId);
|
||||||
|
|
||||||
|
// Host端兜底:确保participants中有该participant条目
|
||||||
|
if (isHost && !this.state.participants[trackParticipantId]) {
|
||||||
|
this.state.participants[trackParticipantId] = {
|
||||||
|
id: '',
|
||||||
|
name: '参与者',
|
||||||
|
avatar: '/images/p2.png',
|
||||||
|
mediaState: { audio: false, video: false, isSpeaking: false },
|
||||||
|
status: 'online'
|
||||||
|
};
|
||||||
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
|
}
|
||||||
|
|
||||||
// 通知UI远程流已更新
|
// 通知UI远程流已更新
|
||||||
this.notify({
|
this.notify({
|
||||||
type: 'REMOTE_STREAM_OBTAINED',
|
type: 'REMOTE_STREAM_OBTAINED',
|
||||||
@@ -467,7 +498,19 @@ class CallStateManager {
|
|||||||
// 处理聊天
|
// 处理聊天
|
||||||
// 添加到列表并更新UI
|
// 添加到列表并更新UI
|
||||||
chatMessage.handleChatMessage(data.message);
|
chatMessage.handleChatMessage(data.message);
|
||||||
// 从消息中提取用户信息并更新remoteUser
|
// Host端:按participantId更新对应用户信息
|
||||||
|
if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) {
|
||||||
|
this.state.participants[data.participantId].id = data.message.senderId;
|
||||||
|
if (data.message.senderName) {
|
||||||
|
this.state.participants[data.participantId].name = data.message.senderName;
|
||||||
|
}
|
||||||
|
if (data.message.senderAvatar) {
|
||||||
|
this.state.participants[data.participantId].avatar = data.message.senderAvatar;
|
||||||
|
}
|
||||||
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
|
}
|
||||||
|
// Participant端:从消息中提取Host用户信息并更新remoteUser
|
||||||
|
if (!this.role || this.role !== 'host') {
|
||||||
if (data.message && data.message.senderId !== this.state.session.localUser.id) {
|
if (data.message && data.message.senderId !== this.state.session.localUser.id) {
|
||||||
this.state.session.remoteUser = {
|
this.state.session.remoteUser = {
|
||||||
...this.state.session.remoteUser,
|
...this.state.session.remoteUser,
|
||||||
@@ -477,15 +520,42 @@ class CallStateManager {
|
|||||||
};
|
};
|
||||||
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (data.type === 'media-state-changed') {
|
} else if (data.type === 'media-state-changed') {
|
||||||
// 处理媒体状态变化
|
// 处理媒体状态变化
|
||||||
console.log('收到媒体状态变化:', data.data, 'from participant:', data.participantId);
|
console.log('收到媒体状态变化:', data.data, 'from participant:', data.participantId);
|
||||||
// 更新远程用户的媒体状态,传递participantId以便精准定位
|
// Host端:同步更新participants中对应participant的mediaState
|
||||||
|
if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) {
|
||||||
|
this.state.participants[data.participantId].mediaState = {
|
||||||
|
...this.state.participants[data.participantId].mediaState,
|
||||||
|
...data.data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 更新远端媒体状态(兼容Participant端)
|
||||||
this.updateRemoteMedia(data.data, data.participantId);
|
this.updateRemoteMedia(data.data, data.participantId);
|
||||||
|
// 通知UI更新participants
|
||||||
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
} else if (data.type === 'user-info') {
|
} else if (data.type === 'user-info') {
|
||||||
// 处理用户信息更新
|
// 处理用户信息更新
|
||||||
console.log('收到用户信息:', data.data);
|
console.log('收到用户信息:', data.data, 'from participant:', data.participantId);
|
||||||
if (data.data) {
|
if (data.data) {
|
||||||
|
if (data.participantId && this.role === 'host') {
|
||||||
|
// Host端:按participantId存储到participants Map
|
||||||
|
if (!this.state.participants[data.participantId]) {
|
||||||
|
this.state.participants[data.participantId] = {
|
||||||
|
id: '',
|
||||||
|
name: '参与者',
|
||||||
|
avatar: '/images/p2.png',
|
||||||
|
mediaState: { audio: false, video: false, isSpeaking: false },
|
||||||
|
status: 'online'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.state.participants[data.participantId].id = data.data.id || '';
|
||||||
|
this.state.participants[data.participantId].name = data.data.name || '参与者';
|
||||||
|
this.state.participants[data.participantId].avatar = data.data.avatar || '/images/p2.png';
|
||||||
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
|
} else {
|
||||||
|
// Participant端:更新单一remoteUser(Host的信息)
|
||||||
this.state.session.remoteUser = {
|
this.state.session.remoteUser = {
|
||||||
...this.state.session.remoteUser,
|
...this.state.session.remoteUser,
|
||||||
id: data.data.id || this.state.session.remoteUser.id,
|
id: data.data.id || this.state.session.remoteUser.id,
|
||||||
@@ -495,6 +565,7 @@ class CallStateManager {
|
|||||||
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -552,6 +623,8 @@ class CallStateManager {
|
|||||||
// 更新远程用户状态为离线
|
// 更新远程用户状态为离线
|
||||||
this.updateRemoteUserStatus('offline');
|
this.updateRemoteUserStatus('offline');
|
||||||
this.updateRemoteUserNetworkQuality('no_signal');
|
this.updateRemoteUserNetworkQuality('no_signal');
|
||||||
|
// 清理participants
|
||||||
|
this.state.participants = {};
|
||||||
this.connectionId = null;
|
this.connectionId = null;
|
||||||
this.role = null;
|
this.role = null;
|
||||||
this.state.session.status = 'ended';
|
this.state.session.status = 'ended';
|
||||||
|
|||||||
Reference in New Issue
Block a user