initaial
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 34s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 34s
				
			This commit is contained in:
		
							parent
							
								
									5cd134aa29
								
							
						
					
					
						commit
						e281545435
					
				
							
								
								
									
										226
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										226
									
								
								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 { | ||||
|                     // 创建新的视频流
 | ||||
|             const newVideoStream = await this.createVideoStream(videoFile); | ||||
|             const newVideoTrack = newVideoStream.getVideoTracks()[0]; | ||||
|                     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; | ||||
|          | ||||
|          | ||||
|          | ||||
|         // 延迟刷新,确保服务器处理完断开逻辑
 | ||||
|         console.log('通话已结束,5秒后刷新页面清除缓存...'); | ||||
|         setTimeout(() => { | ||||
|             window.location.reload(); | ||||
|              | ||||
|         // 显示开始通话按钮
 | ||||
|         this.startButton.style.display = 'block'; | ||||
|         this.startButton.disabled = false; | ||||
|         }, 300); | ||||
|          | ||||
|         // 移除页面刷新,保持websocket连接
 | ||||
|         // setTimeout(() => {
 | ||||
|         //     window.location.reload();
 | ||||
|         // }, 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); | ||||
|     } | ||||
| 
 | ||||
|     // 清除视频缓存的方法
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Song367
						Song367