diff --git a/WebApp/client/public/onebyone/index.html b/WebApp/client/public/onebyone/index.html
index cf418b7..9dd954a 100644
--- a/WebApp/client/public/onebyone/index.html
+++ b/WebApp/client/public/onebyone/index.html
@@ -155,6 +155,17 @@
data-field="remoteUser.videoStream">
+
+
diff --git a/WebApp/client/public/onebyone/renderer.js b/WebApp/client/public/onebyone/renderer.js
index 7319815..887655c 100644
--- a/WebApp/client/public/onebyone/renderer.js
+++ b/WebApp/client/public/onebyone/renderer.js
@@ -3,7 +3,7 @@
* 负责将状态映射到DOM,与状态管理解耦
*/
import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js';
-import {mockCallSession } from './models.js';
+import { mockCallSession } from './models.js';
class UIRenderer {
constructor(stateManager) {
this.stateManager = stateManager;
@@ -22,6 +22,7 @@ class UIRenderer {
// 远端视频
remoteVideo: document.getElementById('remoteVideo'),
+ remoteVideoPlaceholder: document.getElementById('remoteVideoPlaceholder'),
remoteAvatar: document.getElementById('remoteAvatar'),
remoteName: document.getElementById('remoteName'),
remoteStatus: document.getElementById('remoteStatus'),
@@ -57,6 +58,17 @@ class UIRenderer {
this.unsubscribe = stateManager.subscribe(this.render.bind(this));
// 初始化渲染
this.render(this.stateManager.getState(), { type: 'INIT' });
+
+ window.addEventListener('resize', () => {
+ if (this.elements.remoteVideo && this.elements.remoteVideo.srcObject) {
+ const stream = this.elements.remoteVideo.srcObject;
+ const videoTracks = stream.getVideoTracks();
+ if (videoTracks.length > 0) {
+ const resolution = this.getVideoResolution(videoTracks[0]);
+ this.adjustVideoSize(this.elements.remoteVideo, resolution);
+ }
+ }
+ });
}
// 绑定事件监听器
@@ -75,6 +87,15 @@ class UIRenderer {
this.renderControlButtons(state.session.localUser.mediaState);
this.renderChatMessages(state.messages);
this.renderUserList(state.session.localUser, state.session.remoteUser);
+
+ // 初始化时检查远程流状态,显示或隐藏占位背景
+ if (this.elements.remoteVideoPlaceholder) {
+ if (state.remoteStream) {
+ this.elements.remoteVideoPlaceholder.classList.add('hidden');
+ } else {
+ this.elements.remoteVideoPlaceholder.classList.remove('hidden');
+ }
+ }
break;
case 'DURATION_UPDATE':
this.renderCallDuration(changes.duration);
@@ -215,14 +236,41 @@ class UIRenderer {
if (this.elements.remoteVideo && stream) {
this.elements.remoteVideo.srcObject = stream;
this.elements.remoteVideo.autoplay = true;
+
+ // 关键设置:启用硬件加速和最佳质量渲染
+ this.elements.remoteVideo.style.transform = 'translateZ(0)'; // 启用硬件加速
+ this.elements.remoteVideo.style.imageRendering = 'pixelated'; // 保持像素清晰
+ this.elements.remoteVideo.style.objectFit = 'contain'; // 保持比例
console.log('Remote stream set successfully:', this.elements.remoteVideo.srcObject);
// 隐藏断开连接覆盖层
if (this.elements.disconnectedOverlay) {
this.elements.disconnectedOverlay.classList.add('hidden');
}
+
+ // 隐藏占位背景
+ if (this.elements.remoteVideoPlaceholder) {
+ this.elements.remoteVideoPlaceholder.classList.add('hidden');
+ }
+ // 获取视频轨道并处理分辨率
+ const videoTracks = stream.getVideoTracks();
+ if (videoTracks.length > 0) {
+ const resolution = this.getVideoResolution(videoTracks[0]);
+ this.adjustVideoSize(this.elements.remoteVideo, resolution);
+
+ // 监听轨道变化,处理分辨率调整
+ videoTracks[0].addEventListener('resize', () => {
+ const newResolution = this.getVideoResolution(videoTracks[0]);
+ this.adjustVideoSize(this.elements.remoteVideo, newResolution);
+ });
+ }
} else {
console.error('Either remoteVideo element or stream is missing');
+
+ // 显示占位背景
+ if (this.elements.remoteVideoPlaceholder) {
+ this.elements.remoteVideoPlaceholder.classList.remove('hidden');
+ }
}
}
@@ -287,7 +335,44 @@ class UIRenderer {
}
}
}
+ // 在renderer.js中添加方法
+ // 获取视频流分辨率
+ getVideoResolution(track) {
+ if (track && track.getSettings) {
+ const settings = track.getSettings();
+ return {
+ width: settings.width || 640,
+ height: settings.height || 480
+ };
+ }
+ return { width: 640, height: 480 }; // 默认值
+ }
+ // 调整视频元素大小并居中显示
+ adjustVideoSize(videoElement, resolution) {
+ if (!videoElement) return;
+
+ const { width, height } = resolution;
+ const aspectRatio = width / height;
+
+ // 根据容器大小和视频宽高比调整视频显示
+ const container = videoElement.parentElement;
+ const containerWidth = container.clientWidth;
+ const containerHeight = container.clientHeight;
+ // 启用硬件加速
+ videoElement.style.transform = 'translateZ(0)';
+ videoElement.style.willChange = 'transform';
+ // 设置容器为flex布局,使视频居中
+ container.style.display = 'flex';
+ container.style.alignItems = 'center';
+ container.style.justifyContent = 'center';
+ // 优化图像渲染
+ videoElement.style.imageRendering = 'pixelated';
+ // 确保视频元素在容器内正确显示
+ videoElement.style.maxWidth = '100%';
+ videoElement.style.maxHeight = '100%';
+ videoElement.style.objectFit = 'contain';
+ }
// 渲染控制按钮
renderControlButtons(mediaState) {
if (this.elements.micBtn) {
diff --git a/WebApp/client/public/onebyone/store.js b/WebApp/client/public/onebyone/store.js
index 40159cb..689d7ad 100644
--- a/WebApp/client/public/onebyone/store.js
+++ b/WebApp/client/public/onebyone/store.js
@@ -15,9 +15,9 @@ const defaultStreamHeight = 720;
class CallStateManager {
constructor() {
- const renderstreaming=null; // WebRTC连接管理实例
- const useWebSocket=null; // 是否使用WebSocket信令
- const connectionId=null; // 连接ID
+ const renderstreaming = null; // WebRTC连接管理实例
+ const useWebSocket = null; // 是否使用WebSocket信令
+ const connectionId = null; // 连接ID
// 核心状态
this.state = {
session: {
@@ -194,14 +194,21 @@ class CallStateManager {
// 创建信令实例
const signaling = this.useWebSocket ? new WebSocketSignaling() : new Signaling();
const config = getRTCConfiguration(); // 获取RTC配置
+ // 优化RTC配置,确保支持高分辨率
+ config.peerConnectionOptions = {
+ optional: [
+ { googCpuOveruseDetection: false }, // 禁用CPU过度使用检测
+ { googScreencastMinBitrate: 3000 } // 设置最小比特率
+ ]
+ };
this.renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例
// 连接建立回调
this.renderstreaming.onConnect = () => {
// 连接建立后,更新状态为ongoing
- this.state.session.status = 'ongoing';
- this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' });
-
+ this.state.session.status = 'ongoing';
+ this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' });
+
if (this.state.localStream) {
const tracks = this.state.localStream.getTracks(); // 获取本地媒体轨道
for (const track of tracks) {