From e2815454350366dbc2769c6fe1b457688d98ffe6 Mon Sep 17 00:00:00 2001 From: Song367 <601337784@qq.com> Date: Wed, 6 Aug 2025 20:13:33 +0800 Subject: [PATCH] initaial --- src/index.js | 230 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 184 insertions(+), 46 deletions(-) diff --git a/src/index.js b/src/index.js index 84511c2..30792d1 100644 --- a/src/index.js +++ b/src/index.js @@ -27,6 +27,9 @@ class WebRTCChat { this.currentVideo = null; this.videoStreams = new Map(); // 存储不同视频的MediaStream this.currentVideoStream = null; + this.precreatedStreams = new Map(); // 预创建的视频流 + this.importantVideos = ['d-3s.mp4', 's-1.mp4']; // 重要视频列表 + this.isInitialized = false; // 添加视频相关属性 this.videoSender = null; // WebRTC视频发送器 @@ -75,6 +78,13 @@ class WebRTCChat { }); }, 500); // 延迟2秒开始预加载,避免影响 + // 预创建重要视频流 + setTimeout(() => { + this.precreateImportantVideos().catch(error => { + this.logMessage(`预创建重要视频流失败: ${error.message}`, 'error'); + }); + }, 1000); + window.webrtcApp = this; } @@ -331,6 +341,116 @@ class WebRTCChat { }); } + // 预创建重要视频流 + async precreateImportantVideos() { + if (this.isInitialized) return; + + this.logMessage('开始预创建重要视频流...', 'info'); + + for (const videoFile of [this.interactionVideo, this.defaultVideo]) { + try { + const stream = await this.createVideoStreamOptimized(videoFile); + this.precreatedStreams.set(videoFile, stream); + this.logMessage(`预创建视频流成功: ${videoFile}`, 'success'); + } catch (error) { + this.logMessage(`预创建视频流失败: ${videoFile} - ${error.message}`, 'error'); + } + } + + this.isInitialized = true; + this.logMessage('重要视频流预创建完成', 'success'); + } + + // 优化的视频流创建方法 + async createVideoStreamOptimized(videoFile) { + try { + // 先测试视频文件是否存在 + await this.testVideoFile(videoFile); + + // 创建video元素 + const video = document.createElement('video'); + video.src = `/videos/${videoFile}`; + video.muted = true; + video.loop = true; + video.autoplay = false; + video.crossOrigin = 'anonymous'; + video.playsInline = true; + video.preload = 'auto'; + + // 等待视频加载 + await new Promise((resolve, reject) => { + video.onloadeddata = () => { + video.currentTime = 0; + resolve(); + }; + video.onerror = reject; + }); + + // 创建优化的Canvas + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d', { + alpha: false, // 禁用透明度以提高性能 + willReadFrequently: false // 优化Canvas性能 + }); + + canvas.width = video.videoWidth || 640; + canvas.height = video.videoHeight || 480; + + // 绘制第一帧 + await new Promise((resolve) => { + const drawFirstFrame = () => { + if (video.readyState >= video.HAVE_CURRENT_DATA) { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + resolve(); + } else { + setTimeout(drawFirstFrame, 10); + } + }; + drawFirstFrame(); + }); + + // 启动视频播放 + await video.play(); + + // 优化的帧绘制 - 使用更高效的节流 + let animationId; + let lastFrameTime = 0; + const targetFPS = 30; + const frameInterval = 1000 / targetFPS; + + const drawFrame = (currentTime) => { + if (currentTime - lastFrameTime >= frameInterval) { + if (video.readyState >= video.HAVE_CURRENT_DATA) { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + } + lastFrameTime = currentTime; + } + animationId = requestAnimationFrame(drawFrame); + }; + + animationId = requestAnimationFrame(drawFrame); + + // 创建MediaStream + const stream = canvas.captureStream(30); + + // 存储清理函数 + stream._cleanup = () => { + if (animationId) { + cancelAnimationFrame(animationId); + } + video.pause(); + video.src = ''; + video.load(); + }; + + return stream; + + } catch (error) { + this.logMessage(`优化视频流创建失败 ${videoFile}: ${error.message}`, 'error'); + throw error; + } + } + async createVideoStream(videoFile) { // 检查缓存,但为每个视频创建独立的播放实例 const cacheKey = `${videoFile}_${Date.now()}`; // 添加时间戳确保唯一性 @@ -589,61 +709,64 @@ class WebRTCChat { // 使用replaceTrack方式切换视频 async switchVideoWithReplaceTrack(videoFile, type = '', text = '') { try { - this.logMessage(`开始使用replaceTrack切换视频: ${videoFile}`, 'info'); + this.logMessage(`开始优化切换视频: ${videoFile}`, 'info'); + + let newVideoStream; + let isUsingPrecreated = false; + + // 首先检查预创建的视频流 + if (this.precreatedStreams.has(videoFile)) { + newVideoStream = this.precreatedStreams.get(videoFile); + isUsingPrecreated = true; + this.logMessage(`使用预创建视频流: ${videoFile}`, 'success'); + } else { + // 检查缓存 + if (this.videoStreams.has(videoFile)) { + newVideoStream = this.videoStreams.get(videoFile); + this.logMessage(`使用缓存视频流: ${videoFile}`, 'success'); + } else { + // 创建新的视频流 + newVideoStream = await this.createVideoStream(videoFile); + } + } - // 创建新的视频流 - const newVideoStream = await this.createVideoStream(videoFile); const newVideoTrack = newVideoStream.getVideoTracks()[0]; - if (!newVideoTrack) { throw new Error('新视频流中没有视频轨道'); } - // 如果有WebRTC连接且有视频发送器,使用replaceTrack + // WebRTC轨道替换 if (this.peerConnection && this.videoSender) { await this.videoSender.replaceTrack(newVideoTrack); - this.logMessage('WebRTC视频轨道替换成功', 'success'); } - // 同时更新本地视频显示 + // 更新本地视频显示 if (this.recordedVideo) { - // 停止当前视频流 - if (this.currentVideoStream) { + // 停止当前视频流(但不停止预创建的流) + if (this.currentVideoStream && !this.precreatedStreams.has(this.currentVideo)) { this.currentVideoStream.getTracks().forEach(track => track.stop()); } - // 设置新的视频流 this.recordedVideo.srcObject = newVideoStream; this.currentVideoStream = newVideoStream; + this.currentVideo = videoFile; + + // 使用预创建流时减少等待时间 + const waitTime = isUsingPrecreated ? 50 : 200; + await new Promise(resolve => setTimeout(resolve, waitTime)); - // 确保视频播放 try { await this.recordedVideo.play(); - this.logMessage(`本地视频切换成功: ${videoFile}`, 'success'); } catch (playError) { - this.logMessage(`本地视频播放失败: ${playError.message}`, 'error'); + this.logMessage(`视频播放失败: ${playError.message}`, 'error'); } } - // 记录切换信息 - if (type && text) { - this.logMessage(`视频切换完成 - 类型: ${type}, 文本: ${text}`, 'info'); - } - + this.logMessage(`视频切换完成: ${videoFile} (${type})`, 'success'); return true; } catch (error) { - this.logMessage(`replaceTrack视频切换失败: ${error.message}`, 'error'); - console.error('switchVideoWithReplaceTrack error:', error); - - // 回退到原有的切换方式 - try { - await this.switchVideoStream(videoFile, type, text); - this.logMessage('已回退到原有视频切换方式', 'info'); - } catch (fallbackError) { - this.logMessage(`回退切换也失败: ${fallbackError.message}`, 'error'); - } - + this.logMessage(`视频切换失败: ${error.message}`, 'error'); return false; } } @@ -917,8 +1040,8 @@ class WebRTCChat { // 开始通话按钮 this.startButton.onclick = () => this.startCall(); - // 停止通话按钮 - this.stopButton.onclick = () => this.stopCall(); + // 停止通话按钮 - 改为调用 userDisconnect + this.stopButton.onclick = () => this.userDisconnect(); // 静音按钮 // this.muteButton.onclick = () => this.toggleMute(); @@ -1011,7 +1134,7 @@ class WebRTCChat { this.socket.emit('call-started'); // 开始播放当前场景的默认视频 - await this.startDefaultVideoStream(); + await this.precreateImportantVideos(); // 隐藏等待连接提示 this.hideConnectionWaiting(); @@ -1026,16 +1149,16 @@ class WebRTCChat { } stopCall() { - // 隐藏等待连接提示 this.hideConnectionWaiting(); // 恢复到默认图标 this.switchToDefaultIcon(); - // 发送用户关闭连接事件到后端 - if (this.socket && this.socket.connected) { - this.socket.emit('user-disconnect'); - } + // 只有用户主动点击关闭通话时才发送断开事件 + // 移除自动发送 user-disconnect 事件 + // if (this.socket && this.socket.connected) { + // this.socket.emit('user-disconnect'); + // } // 停止音频处理器 if (this.audioProcessor) { @@ -1059,23 +1182,38 @@ class WebRTCChat { this.stopButton.style.display = 'none'; this.stopButton.disabled = true; + // 显示开始通话按钮 + this.startButton.style.display = 'block'; + this.startButton.disabled = false; + // 移除页面刷新,保持websocket连接 + // setTimeout(() => { + // window.location.reload(); + // }, 300); - // 延迟刷新,确保服务器处理完断开逻辑 - console.log('通话已结束,5秒后刷新页面清除缓存...'); setTimeout(() => { - window.location.reload(); - - this.startButton.style.display = 'block'; - this.startButton.disabled = false; - }, 300); - setTimeout(() => { - // 显示头像,隐藏视频 + // 显示头像,隐藏视频 if (this.videoContainer) { this.videoContainer.classList.remove('calling'); } }, 300); + } + // 新增:用户主动断开连接的方法 + userDisconnect() { + // 发送用户关闭连接事件到后端 + if (this.socket && this.socket.connected) { + this.socket.emit('user-disconnect'); + } + + // 调用停止通话 + this.stopCall(); + + // 延迟刷新,确保服务器处理完断开逻辑 + console.log('用户主动断开,300ms后刷新页面清除缓存...'); + setTimeout(() => { + window.location.reload(); + }, 300); } // 清除视频缓存的方法