From bcd55f9dac7ceb1b1c9c2f0c1fad808cc0443411 Mon Sep 17 00:00:00 2001 From: stary <834207172@qq.COM> Date: Sat, 25 Apr 2026 19:26:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebApp/client/public/onebyone/index.html | 29 ---- WebApp/client/public/onebyone/renderer.js | 198 +++++++--------------- WebApp/client/public/onebyone/store.js | 31 +++- 3 files changed, 88 insertions(+), 170 deletions(-) diff --git a/WebApp/client/public/onebyone/index.html b/WebApp/client/public/onebyone/index.html index 1dad8eb..2a0c5a0 100644 --- a/WebApp/client/public/onebyone/index.html +++ b/WebApp/client/public/onebyone/index.html @@ -180,35 +180,6 @@ - -
-
- - - - - -
-
- -
Sarah Chen
-
- - 正在通话 - - - -
-
-
-
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...'); } } diff --git a/WebApp/client/public/onebyone/store.js b/WebApp/client/public/onebyone/store.js index b84dd3b..e1288b7 100644 --- a/WebApp/client/public/onebyone/store.js +++ b/WebApp/client/public/onebyone/store.js @@ -480,13 +480,30 @@ class CallStateManager { } // 通知UI远程流已更新 - this.notify({ - type: 'REMOTE_STREAM_OBTAINED', - stream: targetStream, - connectionId: trackParticipantId, - isHost: isHost - }); - console.log('Notified UI about remote stream update'); + // 关键优化:如果是音频轨道先到达且流中尚无视频轨道, + // 延迟通知UI等待视频轨道到达,避免音频先触发的UI更新导致黑屏 + const notifyStreamUpdate = () => { + this.notify({ + type: 'REMOTE_STREAM_OBTAINED', + stream: targetStream, + connectionId: trackParticipantId, + isHost: isHost + }); + console.log('Notified UI about remote stream update'); + }; + + if (data.track.kind === 'audio' && targetStream.getVideoTracks().length === 0) { + // 音频先到,视频尚未到达:延迟200ms通知,给视频轨道到达的机会 + console.log('Audio track arrived first, delaying stream notification for video track...'); + setTimeout(() => { + const nowHasVideo = targetStream.getVideoTracks().length > 0; + console.log(`After delay, stream has video: ${nowHasVideo}`); + notifyStreamUpdate(); + }, 200); + } else { + // 视频轨道到达,或音频视频同时存在:立即通知 + notifyStreamUpdate(); + } // 只有当收到远程流时才更新远程用户状态为在线 if (this.state.session.remoteUser.status !== 'online') { this.updateRemoteUserStatus('online');