完成
This commit is contained in:
@@ -29,12 +29,6 @@ class UIRenderer {
|
||||
// 远端视频
|
||||
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'),
|
||||
@@ -243,43 +237,6 @@ class UIRenderer {
|
||||
|
||||
// 渲染远端视频
|
||||
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, this.stateManager.getState().participants);
|
||||
|
||||
@@ -317,15 +274,6 @@ class UIRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染说话状态
|
||||
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);
|
||||
|
||||
@@ -471,8 +419,15 @@ class UIRenderer {
|
||||
// 视频元素显示participant的远端视频流
|
||||
const video = tile.querySelector('video');
|
||||
if (video && stream) {
|
||||
video.srcObject = stream;
|
||||
console.log(`Set remote stream for participant tile ${connectionId}`);
|
||||
// 避免重复设置同一流对象(音频先到、视频后到时流对象相同)
|
||||
if (video.srcObject === stream) {
|
||||
console.log(`Same stream for participant ${connectionId}, ensuring playback`);
|
||||
video.play().catch(e => console.log('Auto-play prevented:', e.message));
|
||||
} else {
|
||||
video.srcObject = stream;
|
||||
video.play().catch(e => console.log('Auto-play prevented:', e.message));
|
||||
console.log(`Set remote stream for participant tile ${connectionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏单路远端视频和占位符
|
||||
@@ -546,91 +501,66 @@ class UIRenderer {
|
||||
|
||||
// 渲染Participant端的单一远端视频(Host画面)
|
||||
renderSingleRemoteStream(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 {
|
||||
if (!this.elements.remoteVideo || !stream) {
|
||||
console.error('Either remoteVideo element or stream is missing');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空视频元素的源
|
||||
if (this.elements.remoteVideo) {
|
||||
this.elements.remoteVideo.srcObject = null;
|
||||
}
|
||||
console.log('Rendering remote stream:', stream, 'tracks:', stream.getTracks().map(t => `${t.kind}(${t.readyState})`));
|
||||
|
||||
// 显示占位背景
|
||||
// 关键修复:避免 srcObject = null 的重置模式
|
||||
// 如果 srcObject 已经是同一个 stream 对象,说明是同一流的轨道更新(如音频先到,视频后到)
|
||||
// 浏览器会自动识别新添加的轨道,无需重置 srcObject
|
||||
if (this.elements.remoteVideo.srcObject === stream) {
|
||||
console.log('Same stream object, track added - ensuring playback');
|
||||
this.elements.remoteVideo.play().catch(e => console.log('Auto-play prevented:', e.message));
|
||||
return;
|
||||
}
|
||||
|
||||
// 首次设置或流对象变化:直接设置 srcObject(不使用 null 重置模式)
|
||||
this.elements.remoteVideo.srcObject = stream;
|
||||
this.elements.remoteVideo.autoplay = true;
|
||||
this.elements.remoteVideo.playsinline = true;
|
||||
this.elements.remoteVideo.muted = false;
|
||||
|
||||
// 确保视频开始播放
|
||||
this.elements.remoteVideo.play().catch(e => {
|
||||
console.log('Auto-play prevented, will retry on interaction:', e.message);
|
||||
});
|
||||
|
||||
// 隐藏断开连接覆盖层
|
||||
if (this.elements.disconnectedOverlay) {
|
||||
this.elements.disconnectedOverlay.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 监听视频轨道变化
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
const audioTracks = stream.getAudioTracks();
|
||||
console.log(`Stream has ${videoTracks.length} video tracks, ${audioTracks.length} audio tracks`);
|
||||
|
||||
if (videoTracks.length > 0) {
|
||||
// 有视频轨道:隐藏占位符
|
||||
if (this.elements.remoteVideoPlaceholder) {
|
||||
this.elements.remoteVideoPlaceholder.classList.remove('hidden');
|
||||
this.elements.remoteVideoPlaceholder.classList.add('hidden');
|
||||
}
|
||||
if (this.elements.connectingOverlay) {
|
||||
this.elements.connectingOverlay.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 监听视频轨道分辨率变化
|
||||
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);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 只有音频轨道(视频轨道尚未到达):不显示占位符,等待视频轨道到达
|
||||
// 不设置 srcObject = null,保持音频播放
|
||||
console.log('Audio-only stream, waiting for video track...');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user