【m】视频渲染完成,测试没问题
This commit is contained in:
@@ -143,11 +143,8 @@ class UIRenderer {
|
||||
this.renderUserList(state.session.localUser, state.session.remoteUser); // 渲染用户列表
|
||||
break;
|
||||
case 'LOCAL_STREAM_OBTAINED':
|
||||
// 本地流获取成功 - 更新本地视频显示
|
||||
this.renderLocalStream(state.localStream); // 渲染本地流
|
||||
this.renderLocalVideo(state.session.localUser, state.localStream); // 渲染本地视频
|
||||
// 如果host端有participant tile,同步更新它们的视频源
|
||||
this.updateParticipantGridStreams(state.localStream);
|
||||
this.renderLocalStream(state.localStream);
|
||||
this.renderLocalVideo(state.session.localUser, state.localStream);
|
||||
break;
|
||||
case 'REMOTE_STREAM_OBTAINED':
|
||||
// 远程流获取成功 - 更新远程视频显示
|
||||
@@ -402,7 +399,7 @@ class UIRenderer {
|
||||
}
|
||||
|
||||
// 渲染Host端的多Participant视频网格
|
||||
// 所有participant tile显示host的本地视频流(host监控自己广播的画面)
|
||||
// 每个participant tile显示该participant实际的远端视频流
|
||||
renderParticipantStream(stream, connectionId) {
|
||||
const grid = this.elements.participantGrid;
|
||||
if (!grid) return;
|
||||
@@ -413,7 +410,6 @@ class UIRenderer {
|
||||
// 查找或创建该 connectionId 的视频格子
|
||||
let tile = grid.querySelector(`[data-participant-id="${connectionId}"]`);
|
||||
if (!tile) {
|
||||
// 创建新的视频格子
|
||||
tile = document.createElement('div');
|
||||
tile.className = 'relative bg-black/60 rounded-xl overflow-hidden flex items-center justify-center';
|
||||
tile.dataset.participantId = connectionId;
|
||||
@@ -422,27 +418,20 @@ class UIRenderer {
|
||||
video.className = 'w-full h-full object-contain';
|
||||
video.autoplay = true;
|
||||
video.playsinline = true;
|
||||
video.muted = true; // 静音,因为显示的是host自己的画面,不需要播放自己的声音
|
||||
video.muted = false; // 不静音,播放participant的音频
|
||||
video.id = `participantVideo_${connectionId}`;
|
||||
tile.appendChild(video);
|
||||
|
||||
// 添加隐藏的audio元素播放远端音频(participant的声音)
|
||||
const audio = document.createElement('audio');
|
||||
audio.autoplay = true;
|
||||
audio.id = `participantAudio_${connectionId}`;
|
||||
audio.style.display = 'none';
|
||||
tile.appendChild(audio);
|
||||
|
||||
// 添加参与者名称标签
|
||||
// 参与者名称标签
|
||||
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.innerHTML = `<i class="fas fa-user text-indigo-400"></i><span>Participant ${connectionId.slice(-4)}</span>`;
|
||||
tile.appendChild(label);
|
||||
|
||||
// 添加"直播中"标识
|
||||
// 在线标识
|
||||
const liveTag = document.createElement('div');
|
||||
liveTag.className = 'absolute top-3 right-3 bg-red-500/80 px-2 py-0.5 rounded-full text-xs flex items-center gap-1';
|
||||
liveTag.innerHTML = `<span class="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></span><span>直播</span>`;
|
||||
liveTag.className = 'absolute top-3 right-3 bg-green-500/80 px-2 py-0.5 rounded-full text-xs flex items-center gap-1';
|
||||
liveTag.innerHTML = `<span class="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></span><span>在线</span>`;
|
||||
tile.appendChild(liveTag);
|
||||
|
||||
grid.appendChild(tile);
|
||||
@@ -450,23 +439,11 @@ class UIRenderer {
|
||||
}
|
||||
|
||||
if (tile) {
|
||||
// 视频元素显示host的本地流(监控自己广播的画面)
|
||||
// 视频元素显示participant的远端视频流
|
||||
const video = tile.querySelector('video');
|
||||
if (video) {
|
||||
const localStream = this.stateManager.getState().localStream;
|
||||
if (localStream) {
|
||||
video.srcObject = localStream;
|
||||
console.log(`Set host local stream for participant tile ${connectionId}`);
|
||||
} else {
|
||||
console.warn('Host local stream not available for participant tile');
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏audio元素播放远端流(participant的音频)
|
||||
const audio = tile.querySelector('audio');
|
||||
if (audio && stream) {
|
||||
audio.srcObject = stream;
|
||||
console.log(`Set remote audio for participant tile ${connectionId}`);
|
||||
if (video && stream) {
|
||||
video.srcObject = stream;
|
||||
console.log(`Set remote stream for participant tile ${connectionId}`);
|
||||
}
|
||||
|
||||
// 隐藏单路远端视频和占位符
|
||||
@@ -497,19 +474,6 @@ class UIRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新participant网格中所有tile的视频源(host本地流变化时调用)
|
||||
updateParticipantGridStreams(localStream) {
|
||||
const grid = this.elements.participantGrid;
|
||||
if (!grid || grid.classList.contains('hidden')) return;
|
||||
|
||||
grid.querySelectorAll('[data-participant-id]').forEach(tile => {
|
||||
const video = tile.querySelector('video');
|
||||
if (video && localStream) {
|
||||
video.srcObject = localStream;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染Participant端的单一远端视频(Host画面)
|
||||
renderSingleRemoteStream(stream) {
|
||||
if (this.elements.remoteVideo && stream) {
|
||||
@@ -1023,8 +987,6 @@ class UIRenderer {
|
||||
grid.querySelectorAll('[data-participant-id]').forEach(tile => {
|
||||
const video = tile.querySelector('video');
|
||||
if (video) video.srcObject = null;
|
||||
const audio = tile.querySelector('audio');
|
||||
if (audio) audio.srcObject = null;
|
||||
tile.remove();
|
||||
});
|
||||
grid.classList.add('hidden');
|
||||
@@ -1038,40 +1000,27 @@ class UIRenderer {
|
||||
renderParticipantLeft(connectionId) {
|
||||
console.log(`Participant left: ${connectionId}, updating UI`);
|
||||
|
||||
// 移除该 participant 的视频格子
|
||||
const grid = this.elements.participantGrid;
|
||||
if (grid) {
|
||||
const tile = grid.querySelector(`[data-participant-id="${connectionId}"]`);
|
||||
if (tile) {
|
||||
// 清理video元素
|
||||
const video = tile.querySelector('video');
|
||||
if (video) {
|
||||
video.srcObject = null;
|
||||
}
|
||||
// 清理audio元素
|
||||
const audio = tile.querySelector('audio');
|
||||
if (audio) {
|
||||
audio.srcObject = null;
|
||||
}
|
||||
if (video) video.srcObject = null;
|
||||
tile.remove();
|
||||
console.log(`Removed participant video tile for ${connectionId}`);
|
||||
}
|
||||
|
||||
// 如果没有 participant 了,隐藏网格,显示单路远端视频
|
||||
const remainingTiles = grid.querySelectorAll('[data-participant-id]');
|
||||
if (remainingTiles.length === 0) {
|
||||
grid.classList.add('hidden');
|
||||
// 显示单路远端视频区域(恢复默认)
|
||||
const remoteVideoDiv = this.elements.remoteVideo?.closest('.absolute.inset-0.video-fade-in');
|
||||
if (remoteVideoDiv) {
|
||||
remoteVideoDiv.classList.remove('hidden');
|
||||
}
|
||||
// 显示远端视频占位符
|
||||
if (this.elements.remoteVideoPlaceholder) {
|
||||
this.elements.remoteVideoPlaceholder.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
// 调整网格列数
|
||||
if (remainingTiles.length <= 1) {
|
||||
grid.style.gridTemplateColumns = '1fr';
|
||||
} else if (remainingTiles.length <= 4) {
|
||||
@@ -1082,7 +1031,6 @@ class UIRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新远程用户状态显示为离线
|
||||
if (this.elements.remoteNetworkIndicator) {
|
||||
this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-gray-500 rounded-full';
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ function onDisconnect(ws: WebSocket, connectionId: string): void {
|
||||
} else {
|
||||
// participant断开连接,从组中移除并通知host(使用participant-left类型,host不会关闭房间)
|
||||
group.participants.delete(ws);
|
||||
group.host.send(JSON.stringify({ type: "participant-left", connectionId: connectionId }));
|
||||
group.host.send(JSON.stringify({ type: "participant-left", connectionId: connectionId, participantId: (ws as any).participantId }));
|
||||
console.log(`Participant left connectionId: ${connectionId}, remaining participants: ${group.participants.size}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,46 +74,40 @@ export default class WSSignaling {
|
||||
// 根据消息类型处理
|
||||
switch (msg.type) {
|
||||
case "connect":
|
||||
// 处理连接请求
|
||||
handler.onConnect(ws, msg.connectionId);
|
||||
break;
|
||||
case "disconnect":
|
||||
// 处理断开连接请求
|
||||
handler.onDisconnect(ws, msg.connectionId);
|
||||
break;
|
||||
case "offer":
|
||||
// 处理offer信令
|
||||
if (msg.participantId !== undefined) msg.data.participantId = msg.participantId;
|
||||
handler.onOffer(ws, msg.data);
|
||||
break;
|
||||
case "answer":
|
||||
// 处理answer信令
|
||||
if (msg.participantId !== undefined) msg.data.participantId = msg.participantId;
|
||||
handler.onAnswer(ws, msg.data);
|
||||
break;
|
||||
case "candidate":
|
||||
// 处理candidate信令
|
||||
if (msg.participantId !== undefined) msg.data.participantId = msg.participantId;
|
||||
handler.onCandidate(ws, msg.data);
|
||||
break;
|
||||
case "ping":
|
||||
// 处理心跳请求,回复pong
|
||||
ws.send(JSON.stringify({ type: "pong" }));
|
||||
break;
|
||||
case "pong":
|
||||
// 处理心跳响应,更新最后活动时间
|
||||
(ws as any).lastActivity = Date.now();
|
||||
break;
|
||||
case "broadcast":
|
||||
handler.onBroadcast(ws, msg.data);
|
||||
break;
|
||||
case 'call-request'://接受连接ConnectionId
|
||||
// 处理callConnectionId信令
|
||||
case 'call-request':
|
||||
handler.onCallConnectionId(ws, msg.data);
|
||||
break;
|
||||
case 'on-message'://接受连接ConnectionId
|
||||
// 处理chat-message信令
|
||||
case 'on-message':
|
||||
if (msg.from) msg.data.connectionId = msg.from;
|
||||
handler.onMessage(ws, msg.data);
|
||||
break;
|
||||
default:
|
||||
// 忽略未知消息类型
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user