确保播放第一帧
This commit is contained in:
		
							parent
							
								
									b600114ed9
								
							
						
					
					
						commit
						a27112d6cb
					
				
							
								
								
									
										144
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								src/index.js
									
									
									
									
									
								
							| @ -25,6 +25,10 @@ class WebRTCChat { | ||||
|         this.videoStreams = new Map(); // 存储不同视频的MediaStream
 | ||||
|         this.currentVideoStream = null; | ||||
| 
 | ||||
|         // 添加视频相关属性
 | ||||
|         this.videoSender = null; // WebRTC视频发送器
 | ||||
|         this.currentVideoStream = null; // 当前视频流
 | ||||
|          | ||||
|         // 初始化音频处理器
 | ||||
|         console.log('开始初始化音频处理器'); | ||||
|         // 初始化音频处理器
 | ||||
| @ -289,11 +293,8 @@ class WebRTCChat { | ||||
|     } | ||||
| 
 | ||||
|     async createVideoStream(videoFile) { | ||||
|         // 如果已经缓存了这个视频流,直接返回
 | ||||
|         if (this.videoStreams.has(videoFile)) { | ||||
|             this.logMessage(`使用缓存的视频流: ${videoFile}`, 'info'); | ||||
|             return this.videoStreams.get(videoFile); | ||||
|         } | ||||
|         // 检查缓存,但为每个视频创建独立的播放实例
 | ||||
|         const cacheKey = `${videoFile}_${Date.now()}`; // 添加时间戳确保唯一性
 | ||||
|          | ||||
|         try { | ||||
|             this.logMessage(`开始创建视频流: ${videoFile}`, 'info'); | ||||
| @ -306,26 +307,25 @@ class WebRTCChat { | ||||
|             video.src = `/videos/${videoFile}`; | ||||
|             video.muted = true; | ||||
|             video.loop = true; | ||||
|             video.autoplay = true; | ||||
|             video.crossOrigin = 'anonymous'; // 添加跨域支持
 | ||||
|             video.playsInline = true; // 添加playsInline属性
 | ||||
|             video.autoplay = false; // 手动控制播放
 | ||||
|             video.crossOrigin = 'anonymous'; | ||||
|             video.playsInline = true; | ||||
|              | ||||
|             // 预加载视频但不播放
 | ||||
|             video.preload = 'auto'; | ||||
|              | ||||
|             // 等待视频加载完成
 | ||||
|             await new Promise((resolve, reject) => { | ||||
|                 video.onloadedmetadata = () => { | ||||
|                     this.logMessage(`视频元数据加载完成: ${videoFile}`, 'info'); | ||||
|                 video.onloadeddata = () => { | ||||
|                     this.logMessage(`视频数据加载完成: ${videoFile}`, 'info'); | ||||
|                     // 确保从第一帧开始
 | ||||
|                     video.currentTime = 0; | ||||
|                     resolve(); | ||||
|                 }; | ||||
|                 video.onerror = (error) => { | ||||
|                     this.logMessage(`视频加载失败: ${videoFile}`, 'error'); | ||||
|                     reject(error); | ||||
|                 }; | ||||
|                 video.onloadstart = () => { | ||||
|                     this.logMessage(`开始加载视频: ${videoFile}`, 'info'); | ||||
|                 }; | ||||
|                 video.oncanplay = () => { | ||||
|                     this.logMessage(`视频可以播放: ${videoFile}`, 'info'); | ||||
|                 }; | ||||
|             }); | ||||
|              | ||||
|             // 创建MediaStream
 | ||||
| @ -338,22 +338,27 @@ class WebRTCChat { | ||||
|              | ||||
|             this.logMessage(`Canvas尺寸: ${canvas.width}x${canvas.height}`, 'info'); | ||||
|              | ||||
|             // 先绘制第一帧到canvas(避免黑屏)
 | ||||
|             if (video.readyState >= video.HAVE_CURRENT_DATA) { | ||||
|                 ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | ||||
|                 this.logMessage('已绘制第一帧到Canvas', 'info'); | ||||
|             } | ||||
|              | ||||
|             // 开始播放视频
 | ||||
|             try { | ||||
|                 await video.play(); | ||||
|                 this.logMessage(`视频开始播放: ${videoFile}`, 'info'); | ||||
|             } catch (playError) { | ||||
|                 this.logMessage(`视频播放失败: ${playError.message}`, 'error'); | ||||
|                 // 即使播放失败也继续创建流
 | ||||
|             } | ||||
|              | ||||
|             // 等待视频开始播放
 | ||||
|             // 等待视频真正开始播放
 | ||||
|             await new Promise(resolve => { | ||||
|                 const checkPlay = () => { | ||||
|                     if (video.readyState >= video.HAVE_CURRENT_DATA) { | ||||
|                     if (video.readyState >= video.HAVE_CURRENT_DATA && !video.paused) { | ||||
|                         resolve(); | ||||
|                     } else { | ||||
|                         setTimeout(checkPlay, 100); | ||||
|                         setTimeout(checkPlay, 50); | ||||
|                     } | ||||
|                 }; | ||||
|                 checkPlay(); | ||||
| @ -364,8 +369,7 @@ class WebRTCChat { | ||||
|             let isDrawing = false; | ||||
|             const drawFrame = () => { | ||||
|                 const now = performance.now(); | ||||
|                 // 限制绘制频率,确保平滑过渡
 | ||||
|                 if (video.readyState >= video.HAVE_CURRENT_DATA && !isDrawing && (now - lastDrawTime > 16)) { // 约60fps
 | ||||
|                 if (video.readyState >= video.HAVE_CURRENT_DATA && !isDrawing && (now - lastDrawTime > 16)) { | ||||
|                     isDrawing = true; | ||||
|                     lastDrawTime = now; | ||||
|                     ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | ||||
| @ -378,16 +382,25 @@ class WebRTCChat { | ||||
|             drawFrame(); | ||||
|              | ||||
|             // 从canvas创建MediaStream
 | ||||
|             const stream = canvas.captureStream(30); // 30fps
 | ||||
|             const stream = canvas.captureStream(30); | ||||
|              | ||||
|             // 等待流创建完成并稳定
 | ||||
|             // 等待流稳定
 | ||||
|             await new Promise(resolve => { | ||||
|                 setTimeout(resolve, 500); // 给更多时间让流稳定
 | ||||
|                 setTimeout(resolve, 200); // 减少等待时间
 | ||||
|             }); | ||||
|              | ||||
|             this.logMessage(`视频流创建成功: ${videoFile}`, 'success'); | ||||
|              | ||||
|             // 缓存这个视频流
 | ||||
|             // 使用有限缓存策略(最多缓存3个视频流)
 | ||||
|             if (this.videoStreams.size >= 3) { | ||||
|                 const firstKey = this.videoStreams.keys().next().value; | ||||
|                 const oldStream = this.videoStreams.get(firstKey); | ||||
|                 if (oldStream) { | ||||
|                     oldStream.getTracks().forEach(track => track.stop()); | ||||
|                 } | ||||
|                 this.videoStreams.delete(firstKey); | ||||
|             } | ||||
|              | ||||
|             this.videoStreams.set(videoFile, stream); | ||||
|              | ||||
|             return stream; | ||||
| @ -504,6 +517,68 @@ class WebRTCChat { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 使用replaceTrack方式切换视频
 | ||||
|     async switchVideoWithReplaceTrack(videoFile, type = '', text = '') { | ||||
|         try { | ||||
|             this.logMessage(`开始使用replaceTrack切换视频: ${videoFile}`, 'info'); | ||||
|              | ||||
|             // 创建新的视频流
 | ||||
|             const newVideoStream = await this.createVideoStream(videoFile); | ||||
|             const newVideoTrack = newVideoStream.getVideoTracks()[0]; | ||||
|              | ||||
|             if (!newVideoTrack) { | ||||
|                 throw new Error('新视频流中没有视频轨道'); | ||||
|             } | ||||
|              | ||||
|             // 如果有WebRTC连接且有视频发送器,使用replaceTrack
 | ||||
|             if (this.peerConnection && this.videoSender) { | ||||
|                 await this.videoSender.replaceTrack(newVideoTrack); | ||||
|                 this.logMessage('WebRTC视频轨道替换成功', 'success'); | ||||
|             } | ||||
|              | ||||
|             // 同时更新本地视频显示
 | ||||
|             if (this.recordedVideo) { | ||||
|                 // 停止当前视频流
 | ||||
|                 if (this.currentVideoStream) { | ||||
|                     this.currentVideoStream.getTracks().forEach(track => track.stop()); | ||||
|                 } | ||||
|                  | ||||
|                 // 设置新的视频流
 | ||||
|                 this.recordedVideo.srcObject = newVideoStream; | ||||
|                 this.currentVideoStream = newVideoStream; | ||||
|                  | ||||
|                 // 确保视频播放
 | ||||
|                 try { | ||||
|                     await this.recordedVideo.play(); | ||||
|                     this.logMessage(`本地视频切换成功: ${videoFile}`, 'success'); | ||||
|                 } catch (playError) { | ||||
|                     this.logMessage(`本地视频播放失败: ${playError.message}`, 'error'); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // 记录切换信息
 | ||||
|             if (type && text) { | ||||
|                 this.logMessage(`视频切换完成 - 类型: ${type}, 文本: ${text}`, 'info'); | ||||
|             } | ||||
|              | ||||
|             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'); | ||||
|             } | ||||
|              | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     bindEvents() { | ||||
|         // 开始通话按钮
 | ||||
|         this.startButton.onclick = () => this.startCall(); | ||||
| @ -542,7 +617,7 @@ class WebRTCChat { | ||||
|                 audio: true | ||||
|             }); | ||||
|              | ||||
|             this.createPeerConnection(); | ||||
|             await this.createPeerConnection(); | ||||
|              | ||||
|             this.startButton.disabled = true; | ||||
|             this.stopButton.disabled = false; | ||||
| @ -593,7 +668,7 @@ class WebRTCChat { | ||||
|         this.logMessage('音频通话已结束', 'info'); | ||||
|     } | ||||
| 
 | ||||
|     createPeerConnection() { | ||||
|     async createPeerConnection() { | ||||
|         const configuration = { | ||||
|             iceServers: [ | ||||
|                 { urls: "stun:stun.qq.com:3478" }, | ||||
| @ -610,6 +685,19 @@ class WebRTCChat { | ||||
|             this.peerConnection.addTrack(track, this.localStream); | ||||
|         }); | ||||
|          | ||||
|         // 添加初始视频流(默认视频)
 | ||||
|         try { | ||||
|             const initialVideoStream = await this.createVideoStream(this.defaultVideo); | ||||
|             const videoTrack = initialVideoStream.getVideoTracks()[0]; | ||||
|             if (videoTrack) { | ||||
|                 this.videoSender = this.peerConnection.addTrack(videoTrack, initialVideoStream); | ||||
|                 this.currentVideoStream = initialVideoStream; | ||||
|                 this.logMessage('初始视频轨道已添加到WebRTC连接', 'success'); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             this.logMessage(`添加初始视频轨道失败: ${error.message}`, 'error'); | ||||
|         } | ||||
| 
 | ||||
|         // 处理远程流
 | ||||
|         this.peerConnection.ontrack = (event) => { | ||||
|             this.remoteVideo.srcObject = event.streams[0]; | ||||
|  | ||||
| @ -70,7 +70,7 @@ async function processAudioQueue() { | ||||
|       if (sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.handleTextInput) { | ||||
|         try { | ||||
|           console.log('--------------触发视频切换:', sayName); | ||||
|           await window.webrtcApp.switchVideoStream(targetVideo, 'audio', 'say-5s-m-sw'); | ||||
|           await window.webrtcApp.switchVideoWithReplaceTrack(targetVideo, 'audio', 'say-5s-m-sw'); | ||||
|           isFirstChunk = false; | ||||
|           window.webrtcApp.currentVideoTag = sayName; | ||||
|         } catch (error) { | ||||
| @ -90,7 +90,7 @@ async function processAudioQueue() { | ||||
|   if (window.webrtcApp.currentVideoTag != text) { | ||||
|      | ||||
|     window.webrtcApp.currentVideoTag = text | ||||
|     await window.webrtcApp.switchVideoStream(window.webrtcApp.defaultVideo, 'audio', text); | ||||
|     await window.webrtcApp.switchVideoWithReplaceTrack(window.webrtcApp.defaultVideo, 'audio', text); | ||||
|   } | ||||
|   console.log('音频队列处理完成'); | ||||
| } | ||||
|  | ||||
| @ -446,3 +446,47 @@ header p { | ||||
| #recordedVideo.playing { | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| #recordedVideo { | ||||
|     transition: opacity 0.2s ease-in-out; | ||||
|     background-color: #1a1a1a; /* 深灰色背景,避免纯黑 */ | ||||
| } | ||||
| 
 | ||||
| #recordedVideo.loading { | ||||
|     opacity: 0.8; /* 加载时稍微降低透明度,但不完全隐藏 */ | ||||
| } | ||||
| 
 | ||||
| #recordedVideo.playing { | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| /* 添加加载指示器 */ | ||||
| .video-container { | ||||
|     position: relative; | ||||
| } | ||||
| 
 | ||||
| .video-container::before { | ||||
|     content: ''; | ||||
|     position: absolute; | ||||
|     top: 50%; | ||||
|     left: 50%; | ||||
|     width: 40px; | ||||
|     height: 40px; | ||||
|     margin: -20px 0 0 -20px; | ||||
|     border: 3px solid #333; | ||||
|     border-top: 3px solid #fff; | ||||
|     border-radius: 50%; | ||||
|     animation: spin 1s linear infinite; | ||||
|     opacity: 0; | ||||
|     z-index: 10; | ||||
|     transition: opacity 0.3s; | ||||
| } | ||||
| 
 | ||||
| .video-container.loading::before { | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| @keyframes spin { | ||||
|     0% { transform: rotate(0deg); } | ||||
|     100% { transform: rotate(360deg); } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user