【m】远端占位背景添加
This commit is contained in:
@@ -155,6 +155,17 @@
|
|||||||
data-field="remoteUser.videoStream">
|
data-field="remoteUser.videoStream">
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
<!-- 远端未连接时的占位背景 -->
|
||||||
|
<div id="remoteVideoPlaceholder" class="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-indigo-900/80 to-purple-900/80">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-32 h-32 rounded-full bg-indigo-700/50 flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-user text-4xl text-white/70"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-white text-lg font-medium">等待对方连接...</p>
|
||||||
|
<p class="text-sm text-gray-400 mt-2">请确保对方已加入通话</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 远端信息覆盖层 -->
|
<!-- 远端信息覆盖层 -->
|
||||||
<div class="absolute top-6 left-6 glass px-4 py-2 rounded-full flex items-center gap-3">
|
<div class="absolute top-6 left-6 glass px-4 py-2 rounded-full flex items-center gap-3">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* 负责将状态映射到DOM,与状态管理解耦
|
* 负责将状态映射到DOM,与状态管理解耦
|
||||||
*/
|
*/
|
||||||
import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js';
|
import { formatTime, formatTimestamp, toggleElement, toggleButtonState } from './utils.js';
|
||||||
import {mockCallSession } from './models.js';
|
import { mockCallSession } from './models.js';
|
||||||
class UIRenderer {
|
class UIRenderer {
|
||||||
constructor(stateManager) {
|
constructor(stateManager) {
|
||||||
this.stateManager = stateManager;
|
this.stateManager = stateManager;
|
||||||
@@ -22,6 +22,7 @@ class UIRenderer {
|
|||||||
|
|
||||||
// 远端视频
|
// 远端视频
|
||||||
remoteVideo: document.getElementById('remoteVideo'),
|
remoteVideo: document.getElementById('remoteVideo'),
|
||||||
|
remoteVideoPlaceholder: document.getElementById('remoteVideoPlaceholder'),
|
||||||
remoteAvatar: document.getElementById('remoteAvatar'),
|
remoteAvatar: document.getElementById('remoteAvatar'),
|
||||||
remoteName: document.getElementById('remoteName'),
|
remoteName: document.getElementById('remoteName'),
|
||||||
remoteStatus: document.getElementById('remoteStatus'),
|
remoteStatus: document.getElementById('remoteStatus'),
|
||||||
@@ -57,6 +58,17 @@ class UIRenderer {
|
|||||||
this.unsubscribe = stateManager.subscribe(this.render.bind(this));
|
this.unsubscribe = stateManager.subscribe(this.render.bind(this));
|
||||||
// 初始化渲染
|
// 初始化渲染
|
||||||
this.render(this.stateManager.getState(), { type: 'INIT' });
|
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.renderControlButtons(state.session.localUser.mediaState);
|
||||||
this.renderChatMessages(state.messages);
|
this.renderChatMessages(state.messages);
|
||||||
this.renderUserList(state.session.localUser, state.session.remoteUser);
|
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;
|
break;
|
||||||
case 'DURATION_UPDATE':
|
case 'DURATION_UPDATE':
|
||||||
this.renderCallDuration(changes.duration);
|
this.renderCallDuration(changes.duration);
|
||||||
@@ -215,14 +236,41 @@ class UIRenderer {
|
|||||||
if (this.elements.remoteVideo && stream) {
|
if (this.elements.remoteVideo && stream) {
|
||||||
this.elements.remoteVideo.srcObject = stream;
|
this.elements.remoteVideo.srcObject = stream;
|
||||||
this.elements.remoteVideo.autoplay = true;
|
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);
|
console.log('Remote stream set successfully:', this.elements.remoteVideo.srcObject);
|
||||||
|
|
||||||
// 隐藏断开连接覆盖层
|
// 隐藏断开连接覆盖层
|
||||||
if (this.elements.disconnectedOverlay) {
|
if (this.elements.disconnectedOverlay) {
|
||||||
this.elements.disconnectedOverlay.classList.add('hidden');
|
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 {
|
} else {
|
||||||
console.error('Either remoteVideo element or stream is missing');
|
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) {
|
renderControlButtons(mediaState) {
|
||||||
if (this.elements.micBtn) {
|
if (this.elements.micBtn) {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ const defaultStreamHeight = 720;
|
|||||||
|
|
||||||
class CallStateManager {
|
class CallStateManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
const renderstreaming=null; // WebRTC连接管理实例
|
const renderstreaming = null; // WebRTC连接管理实例
|
||||||
const useWebSocket=null; // 是否使用WebSocket信令
|
const useWebSocket = null; // 是否使用WebSocket信令
|
||||||
const connectionId=null; // 连接ID
|
const connectionId = null; // 连接ID
|
||||||
// 核心状态
|
// 核心状态
|
||||||
this.state = {
|
this.state = {
|
||||||
session: {
|
session: {
|
||||||
@@ -194,14 +194,21 @@ class CallStateManager {
|
|||||||
// 创建信令实例
|
// 创建信令实例
|
||||||
const signaling = this.useWebSocket ? new WebSocketSignaling() : new Signaling();
|
const signaling = this.useWebSocket ? new WebSocketSignaling() : new Signaling();
|
||||||
const config = getRTCConfiguration(); // 获取RTC配置
|
const config = getRTCConfiguration(); // 获取RTC配置
|
||||||
|
// 优化RTC配置,确保支持高分辨率
|
||||||
|
config.peerConnectionOptions = {
|
||||||
|
optional: [
|
||||||
|
{ googCpuOveruseDetection: false }, // 禁用CPU过度使用检测
|
||||||
|
{ googScreencastMinBitrate: 3000 } // 设置最小比特率
|
||||||
|
]
|
||||||
|
};
|
||||||
this.renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例
|
this.renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例
|
||||||
|
|
||||||
// 连接建立回调
|
// 连接建立回调
|
||||||
this.renderstreaming.onConnect = () => {
|
this.renderstreaming.onConnect = () => {
|
||||||
// 连接建立后,更新状态为ongoing
|
// 连接建立后,更新状态为ongoing
|
||||||
this.state.session.status = 'ongoing';
|
this.state.session.status = 'ongoing';
|
||||||
this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' });
|
this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' });
|
||||||
|
|
||||||
if (this.state.localStream) {
|
if (this.state.localStream) {
|
||||||
const tracks = this.state.localStream.getTracks(); // 获取本地媒体轨道
|
const tracks = this.state.localStream.getTracks(); // 获取本地媒体轨道
|
||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
|
|||||||
Reference in New Issue
Block a user