切换增加视频过渡效果,避免黑屏。UI大整改
This commit is contained in:
		
							parent
							
								
									1ebfd472c4
								
							
						
					
					
						commit
						65f17b4a66
					
				| @ -88,15 +88,15 @@ const connectedClients = new Map(); | |||||||
| // 视频映射配置
 | // 视频映射配置
 | ||||||
| const videoMapping = { | const videoMapping = { | ||||||
|   // 'say-6s-m-e': '1-m.mp4',
 |   // 'say-6s-m-e': '1-m.mp4',
 | ||||||
|   'default': '0-2.mp4', |   'default': 'bd-1.mp4', | ||||||
|   // 'say-5s-amplitude': '2.mp4',
 |   // 'say-5s-amplitude': '2.mp4',
 | ||||||
|   // 'say-5s-m-e': '4.mp4',
 |   // 'say-5s-m-e': '4.mp4',
 | ||||||
|   'say-5s-m-sw': '5.mp4', |   'say-5s-m-sw': 'd-0.mp4', | ||||||
|   // 'say-3s-m-sw': '6.mp4',
 |   // 'say-3s-m-sw': '6.mp4',
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // 默认视频流配置
 | // 默认视频流配置
 | ||||||
| const DEFAULT_VIDEO = '0-2.mp4'; | const DEFAULT_VIDEO = 'bd-1.mp4'; | ||||||
| const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
 | const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
 | ||||||
| 
 | 
 | ||||||
| // 获取视频列表
 | // 获取视频列表
 | ||||||
|  | |||||||
| @ -69,22 +69,30 @@ function updateHistoryMessage(userInput, assistantResponse) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // 保存消息到服务端
 | ||||||
| // 保存消息到服务端
 | // 保存消息到服务端
 | ||||||
| async function saveMessage(userInput, assistantResponse) { | async function saveMessage(userInput, assistantResponse) { | ||||||
|     try { |     try { | ||||||
|  |         // 验证参数是否有效
 | ||||||
|  |         if (!userInput || !userInput.trim() || !assistantResponse || !assistantResponse.trim()) { | ||||||
|  |             console.warn('跳过保存消息:用户输入或助手回复为空'); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|         const response = await fetch('/api/messages/save', { |         const response = await fetch('/api/messages/save', { | ||||||
|             method: 'POST', |             method: 'POST', | ||||||
|             headers: { |             headers: { | ||||||
|                 'Content-Type': 'application/json' |                 'Content-Type': 'application/json' | ||||||
|             }, |             }, | ||||||
|             body: JSON.stringify({ |             body: JSON.stringify({ | ||||||
|                 userInput, |                 userInput: userInput.trim(), | ||||||
|                 assistantResponse |                 assistantResponse: assistantResponse.trim() | ||||||
|             }) |             }) | ||||||
|         }); |         }); | ||||||
|          |          | ||||||
|         if (!response.ok) { |         if (!response.ok) { | ||||||
|             throw new Error('保存消息失败'); |             const errorData = await response.json().catch(() => ({})); | ||||||
|  |             throw new Error(`保存消息失败: ${response.status} ${errorData.error || response.statusText}`); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         console.log('消息已保存到服务端'); |         console.log('消息已保存到服务端'); | ||||||
|  | |||||||
							
								
								
									
										243
									
								
								src/index.html
									
									
									
									
									
								
							
							
						
						
									
										243
									
								
								src/index.html
									
									
									
									
									
								
							| @ -2,69 +2,260 @@ | |||||||
| <html lang="zh-CN"> | <html lang="zh-CN"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | ||||||
|     <title>WebRTC 音频通话</title> |     <title>WebRTC 音频通话</title> | ||||||
|     <link rel="stylesheet" href="styles.css"> |     <link rel="stylesheet" href="styles.css"> | ||||||
|  |     <style> | ||||||
|  |         /* 全屏视频样式 */ | ||||||
|  |         * { | ||||||
|  |             margin: 0; | ||||||
|  |             padding: 0; | ||||||
|  |             box-sizing: border-box; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         html, body { | ||||||
|  |             height: 100%; | ||||||
|  |             overflow: hidden; | ||||||
|  |             background: #000; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .container { | ||||||
|  |             width: 100vw; | ||||||
|  |             height: 100vh; | ||||||
|  |             margin: 0; | ||||||
|  |             padding: 0; | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |             position: relative; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .main-content { | ||||||
|  |             flex: 1; | ||||||
|  |             background: transparent; | ||||||
|  |             border-radius: 0; | ||||||
|  |             padding: 0; | ||||||
|  |             box-shadow: none; | ||||||
|  |             width: 100%; | ||||||
|  |             height: 100%; | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .recorded-video-section { | ||||||
|  |             flex: 1; | ||||||
|  |             display: flex; | ||||||
|  |             align-items: center; | ||||||
|  |             justify-content: center; | ||||||
|  |             width: 100%; | ||||||
|  |             height: 100%; | ||||||
|  |             position: relative; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 视频容器样式 - 支持双缓冲 */ | ||||||
|  |         .video-container { | ||||||
|  |             position: relative; | ||||||
|  |             width: 100vw; | ||||||
|  |             height: 100vh; | ||||||
|  |             overflow: hidden; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #recordedVideo, #recordedVideoBuffer { | ||||||
|  |             position: absolute; | ||||||
|  |             top: 0; | ||||||
|  |             left: 0; | ||||||
|  |             width: 100%; | ||||||
|  |             height: 100%; | ||||||
|  |             object-fit: cover; | ||||||
|  |             border-radius: 0; | ||||||
|  |             box-shadow: none; | ||||||
|  |             transition: opacity 0.5s ease-in-out; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 主视频默认显示 */ | ||||||
|  |         #recordedVideo { | ||||||
|  |             opacity: 1; | ||||||
|  |             z-index: 2; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 缓冲视频默认隐藏 */ | ||||||
|  |         #recordedVideoBuffer { | ||||||
|  |             opacity: 0; | ||||||
|  |             z-index: 1; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 切换状态 */ | ||||||
|  |         #recordedVideo.switching { | ||||||
|  |             opacity: 0; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #recordedVideoBuffer.switching { | ||||||
|  |             opacity: 1; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 加载状态 */ | ||||||
|  |         .video-loading { | ||||||
|  |             position: absolute; | ||||||
|  |             top: 50%; | ||||||
|  |             left: 50%; | ||||||
|  |             transform: translate(-50%, -50%); | ||||||
|  |             z-index: 10; | ||||||
|  |             color: white; | ||||||
|  |             font-size: 18px; | ||||||
|  |             opacity: 0; | ||||||
|  |             transition: opacity 0.3s ease; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .video-loading.show { | ||||||
|  |             opacity: 1; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 加载动画 */ | ||||||
|  |         .loading-spinner { | ||||||
|  |             width: 40px; | ||||||
|  |             height: 40px; | ||||||
|  |             border: 3px solid rgba(255, 255, 255, 0.3); | ||||||
|  |             border-top: 3px solid white; | ||||||
|  |             border-radius: 50%; | ||||||
|  |             animation: spin 1s linear infinite; | ||||||
|  |             margin: 0 auto 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         @keyframes spin { | ||||||
|  |             0% { transform: rotate(0deg); } | ||||||
|  |             100% { transform: rotate(360deg); } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .controls { | ||||||
|  |             position: absolute; | ||||||
|  |             bottom: 50px; | ||||||
|  |             left: 50%; | ||||||
|  |             transform: translateX(-50%); | ||||||
|  |             z-index: 10; | ||||||
|  |             display: flex; | ||||||
|  |             justify-content: center; | ||||||
|  |             gap: 20px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #startButton { | ||||||
|  |             padding: 15px 30px; | ||||||
|  |             font-size: 1.1rem; | ||||||
|  |             border-radius: 25px; | ||||||
|  |             min-width: 200px; | ||||||
|  |             background: rgba(0, 123, 255, 0.9); | ||||||
|  |             backdrop-filter: blur(10px); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #stopButton { | ||||||
|  |             width: 60px; | ||||||
|  |             height: 60px; | ||||||
|  |             border-radius: 50%; | ||||||
|  |             background: rgba(220, 53, 69, 0.9); | ||||||
|  |             backdrop-filter: blur(10px); | ||||||
|  |             border: none; | ||||||
|  |             cursor: pointer; | ||||||
|  |             display: none; | ||||||
|  |             align-items: center; | ||||||
|  |             justify-content: center; | ||||||
|  |             transition: all 0.3s ease; | ||||||
|  |             box-shadow: 0 4px 15px rgba(220, 53, 69, 0.3); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #stopButton.show { | ||||||
|  |             display: flex; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #stopButton:hover:not(:disabled) { | ||||||
|  |             background: rgba(200, 35, 51, 0.95); | ||||||
|  |             transform: scale(1.1); | ||||||
|  |             box-shadow: 0 6px 20px rgba(220, 53, 69, 0.5); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #stopButton svg { | ||||||
|  |             width: 24px; | ||||||
|  |             height: 24px; | ||||||
|  |             fill: white; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #stopButton:disabled { | ||||||
|  |             opacity: 0.5; | ||||||
|  |             cursor: not-allowed; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
|     <div class="container"> |     <div class="container"> | ||||||
|         <header> |         <!-- 隐藏的header --> | ||||||
|  |         <header style="display: none;"> | ||||||
|             <h1>WebRTC 音频通话</h1> |             <h1>WebRTC 音频通话</h1> | ||||||
|             <p>实时播放录制视频,支持文本和语音输入</p> |             <p>实时播放录制视频,支持文本和语音输入</p> | ||||||
|         </header> |         </header> | ||||||
| 
 | 
 | ||||||
|         <div class="main-content"> |         <div class="main-content"> | ||||||
|             <!-- 音频状态显示 --> |             <!-- 隐藏的音频状态显示 --> | ||||||
|             <div class="audio-status"> |             <div class="audio-status" style="display: none;"> | ||||||
|                 <div class="status-indicator"> |                 <div class="status-indicator"> | ||||||
|                     <span id="audioStatus">未连接</span> |                     <span id="audioStatus">未连接</span> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <!-- 录制视频播放区域 --> |             <!-- 录制视频播放区域 - 全屏显示 --> | ||||||
|             <div class="recorded-video-section"> |             <div class="recorded-video-section"> | ||||||
|  |                 <div class="video-container"> | ||||||
|  |                     <!-- 主视频元素 --> | ||||||
|                     <video id="recordedVideo" autoplay muted> |                     <video id="recordedVideo" autoplay muted> | ||||||
|                         <source src="" type="video/mp4"> |                         <source src="" type="video/mp4"> | ||||||
|                         您的浏览器不支持视频播放 |                         您的浏览器不支持视频播放 | ||||||
|                     </video> |                     </video> | ||||||
|                 <div class="video-info"> |                      | ||||||
|  |                     <!-- 缓冲视频元素 --> | ||||||
|  |                     <video id="recordedVideoBuffer" autoplay muted> | ||||||
|  |                         <source src="" type="video/mp4"> | ||||||
|  |                         您的浏览器不支持视频播放 | ||||||
|  |                     </video> | ||||||
|  |                      | ||||||
|  |                     <!-- 加载指示器 --> | ||||||
|  |                     <div class="video-loading" id="videoLoading"> | ||||||
|  |                         <div class="loading-spinner"></div> | ||||||
|  |                         <div>正在切换视频...</div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                  | ||||||
|  |                 <div class="video-info" style="display: none;"> | ||||||
|                     <span id="currentVideoName">未选择视频</span> |                     <span id="currentVideoName">未选择视频</span> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <!-- 控制按钮 --> |             <!-- 控制按钮 - 悬浮在视频上方 --> | ||||||
|             <div class="controls"> |             <div class="controls"> | ||||||
|                 <button id="startButton" class="btn btn-primary">开始音频通话</button> |                 <button id="startButton" class="btn btn-primary">开始音频通话</button> | ||||||
|                 <button id="stopButton" class="btn btn-danger" disabled>停止通话</button> |                 <button id="stopButton" class="btn btn-danger" disabled title="结束通话"> | ||||||
|                 <!-- <button id="muteButton" class="btn btn-secondary">静音</button> |                     <svg viewBox="0 0 24 24"> | ||||||
|                 <button id="defaultVideoButton" class="btn btn-info">回到默认视频</button> |                         <path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/> | ||||||
|                 <button id="testVideoButton" class="btn btn-warning">测试视频文件</button> --> |                         <path d="M19 12h2c0-4.97-4.03-9-9-9v2c3.87 0 7 3.13 7 7z"/> | ||||||
|  |                         <path d="M15 12h2c0-2.76-2.24-5-5-5v2c1.66 0 3 1.34 3 3z"/> | ||||||
|  |                         <line x1="18" y1="6" x2="6" y2="18" stroke="white" stroke-width="2"/> | ||||||
|  |                     </svg> | ||||||
|  |                 </button> | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <!-- 输入区域 --> |             <!-- 隐藏的输入区域 --> | ||||||
|             <div class="input-section"> |             <div class="input-section" style="display: none;"> | ||||||
|                 <div class="text-input-group"> |                 <div class="text-input-group"> | ||||||
|                     <input type="text" id="textInput" placeholder="输入文本内容..." /> |                     <input type="text" id="textInput" placeholder="输入文本内容..." /> | ||||||
|                     <button id="sendTextButton" class="btn btn-primary">发送文本</button> |                     <button id="sendTextButton" class="btn btn-primary">发送文本</button> | ||||||
|                 </div> |                 </div> | ||||||
|                  |  | ||||||
|                 <div class="voice-input-group"> |  | ||||||
|                     <button id="startVoiceButton" class="btn btn-success">开始语音输入</button> |  | ||||||
|                     <button id="stopVoiceButton" class="btn btn-warning" disabled>停止语音输入</button> |  | ||||||
|                     <span id="voiceStatus">点击开始语音输入</span> |  | ||||||
|                 </div> |  | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <!-- 视频选择 --> |             <!-- 隐藏的视频选择 --> | ||||||
|             <!-- <div class="video-selection"> |             <div class="video-selection" style="display: none;"> | ||||||
|                 <h3>选择要播放的视频</h3> |                 <h3>选择要播放的视频</h3> | ||||||
|                 <div id="videoList" class="video-list"> |                 <div id="videoList" class="video-list"> | ||||||
|                     视频列表将在这里动态生成 --> |                     <!-- 视频列表将在这里动态生成 --> | ||||||
|                 <!-- </div> |                 </div> | ||||||
|             </div> --> |             </div> | ||||||
| 
 | 
 | ||||||
|             <!-- 状态显示 --> |             <!-- 隐藏的状态显示 --> | ||||||
|             <div class="status-section"> |             <div class="status-section" style="display: none;"> | ||||||
|                 <div id="connectionStatus" class="status">未连接</div> |                 <div id="connectionStatus" class="status">未连接</div> | ||||||
|                 <div id="messageLog" class="message-log"></div> |                 <div id="messageLog" class="message-log"></div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
							
								
								
									
										403
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										403
									
								
								src/index.js
									
									
									
									
									
								
							| @ -19,7 +19,7 @@ class WebRTCChat { | |||||||
|         this.mediaRecorder = null; |         this.mediaRecorder = null; | ||||||
|         this.audioChunks = []; |         this.audioChunks = []; | ||||||
|         this.videoMapping = {}; |         this.videoMapping = {}; | ||||||
|         this.defaultVideo = '0-2.mp4'; |         this.defaultVideo = 'bd-1.mp4'; | ||||||
|         this.currentVideoTag = 'default'; |         this.currentVideoTag = 'default'; | ||||||
|         this.currentVideo = null; |         this.currentVideo = null; | ||||||
|         this.videoStreams = new Map(); // 存储不同视频的MediaStream
 |         this.videoStreams = new Map(); // 存储不同视频的MediaStream
 | ||||||
| @ -34,7 +34,7 @@ class WebRTCChat { | |||||||
|         // 初始化音频处理器
 |         // 初始化音频处理器
 | ||||||
|         this.audioProcessor = new AudioProcessor({ |         this.audioProcessor = new AudioProcessor({ | ||||||
|             onSpeechStart: () => { |             onSpeechStart: () => { | ||||||
|                 this.voiceStatus.textContent = '检测到语音,开始录音...'; |                 // this.voiceStatus.textContent = '检测到语音,开始录音...';
 | ||||||
|                 this.logMessage('检测到语音,开始录音...', 'info'); |                 this.logMessage('检测到语音,开始录音...', 'info'); | ||||||
|             }, |             }, | ||||||
|             onSpeechEnd: () => { |             onSpeechEnd: () => { | ||||||
| @ -42,16 +42,16 @@ class WebRTCChat { | |||||||
|             }, |             }, | ||||||
|             onRecognitionResult: (text) => { |             onRecognitionResult: (text) => { | ||||||
|                 // ASRTEXT = text;
 |                 // ASRTEXT = text;
 | ||||||
|                 this.voiceStatus.textContent = '识别完成'; |                 // this.voiceStatus.textContent = '识别完成';
 | ||||||
|                 this.logMessage(`语音识别结果: ${text}`, 'success'); |                 this.logMessage(`语音识别结果: ${text}`, 'success'); | ||||||
|                 this.handleVoiceInput(text); |                 this.handleVoiceInput(text); | ||||||
|             }, |             }, | ||||||
|             onError: (error) => { |             onError: (error) => { | ||||||
|                 this.voiceStatus.textContent = '识别失败'; |                 // this.voiceStatus.textContent = '识别失败';
 | ||||||
|                 this.logMessage(error, 'error'); |                 this.logMessage(error, 'error'); | ||||||
|             }, |             }, | ||||||
|             onStatusUpdate: (message, status) => { |             onStatusUpdate: (message, status) => { | ||||||
|                 this.voiceStatus.textContent = message; |                 // this.voiceStatus.textContent = message;
 | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
| @ -80,6 +80,11 @@ class WebRTCChat { | |||||||
|         this.localVideo = document.getElementById('localVideo'); |         this.localVideo = document.getElementById('localVideo'); | ||||||
|         this.remoteVideo = document.getElementById('remoteVideo'); |         this.remoteVideo = document.getElementById('remoteVideo'); | ||||||
|         this.recordedVideo = document.getElementById('recordedVideo'); |         this.recordedVideo = document.getElementById('recordedVideo'); | ||||||
|  |         this.recordedVideoBuffer = document.getElementById('recordedVideoBuffer'); // 新增缓冲视频元素
 | ||||||
|  |         this.videoLoading = document.getElementById('videoLoading'); // 加载指示器
 | ||||||
|  |          | ||||||
|  |         // 当前活跃的视频元素标识
 | ||||||
|  |         this.activeVideoElement = 'main'; // 'main' 或 'buffer'
 | ||||||
|          |          | ||||||
|         // 音频状态元素
 |         // 音频状态元素
 | ||||||
|         this.audioStatus = document.getElementById('audioStatus'); |         this.audioStatus = document.getElementById('audioStatus'); | ||||||
| @ -89,14 +94,14 @@ class WebRTCChat { | |||||||
|         this.stopButton = document.getElementById('stopButton'); |         this.stopButton = document.getElementById('stopButton'); | ||||||
|         this.muteButton = document.getElementById('muteButton'); |         this.muteButton = document.getElementById('muteButton'); | ||||||
|         this.sendTextButton = document.getElementById('sendTextButton'); |         this.sendTextButton = document.getElementById('sendTextButton'); | ||||||
|         this.startVoiceButton = document.getElementById('startVoiceButton'); |         // this.startVoiceButton = document.getElementById('startVoiceButton');
 | ||||||
|         this.stopVoiceButton = document.getElementById('stopVoiceButton'); |         // this.stopVoiceButton = document.getElementById('stopVoiceButton');
 | ||||||
|         this.defaultVideoButton = document.getElementById('defaultVideoButton'); |         // this.defaultVideoButton = document.getElementById('defaultVideoButton');
 | ||||||
|         // this.testVideoButton = document.getElementById('testVideoButton'); // 新增测试按钮
 |         // this.testVideoButton = document.getElementById('testVideoButton'); // 新增测试按钮
 | ||||||
|          |          | ||||||
|         // 输入元素
 |         // 输入元素
 | ||||||
|         this.textInput = document.getElementById('textInput'); |         this.textInput = document.getElementById('textInput'); | ||||||
|         this.voiceStatus = document.getElementById('voiceStatus'); |         // this.voiceStatus = document.getElementById('voiceStatus');
 | ||||||
|          |          | ||||||
|         // 状态元素
 |         // 状态元素
 | ||||||
|         this.connectionStatus = document.getElementById('connectionStatus'); |         this.connectionStatus = document.getElementById('connectionStatus'); | ||||||
| @ -424,7 +429,7 @@ class WebRTCChat { | |||||||
|     // 在应用初始化时预加载常用视频
 |     // 在应用初始化时预加载常用视频
 | ||||||
|     async preloadCommonVideos() { |     async preloadCommonVideos() { | ||||||
|         // 获取所有可能需要的视频
 |         // 获取所有可能需要的视频
 | ||||||
|         const videosToPreload = new Set(['0-2.mp4']); |         const videosToPreload = new Set(['bd-1.mp4']); | ||||||
|          |          | ||||||
|         // 添加视频映射中的所有视频
 |         // 添加视频映射中的所有视频
 | ||||||
|         // Object.values(this.videoMapping).forEach(video => {
 |         // Object.values(this.videoMapping).forEach(video => {
 | ||||||
| @ -432,7 +437,7 @@ class WebRTCChat { | |||||||
|         // });
 |         // });
 | ||||||
|          |          | ||||||
|         // 特别确保添加了5.mp4(从日志看这是常用视频)
 |         // 特别确保添加了5.mp4(从日志看这是常用视频)
 | ||||||
|         videosToPreload.add('5.mp4'); |         videosToPreload.add('d-0.mp4'); | ||||||
|          |          | ||||||
|         // 并行预加载,提高效率
 |         // 并行预加载,提高效率
 | ||||||
|         const preloadPromises = Array.from(videosToPreload).map(async (videoFile) => { |         const preloadPromises = Array.from(videosToPreload).map(async (videoFile) => { | ||||||
| @ -448,99 +453,105 @@ class WebRTCChat { | |||||||
|         await Promise.allSettled(preloadPromises); |         await Promise.allSettled(preloadPromises); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // async switchVideoStream(videoFile, type = '', text = '') {
 | ||||||
|  |     //     try {
 | ||||||
|  |     //         this.logMessage(`开始切换视频流: ${videoFile} (${type})`, 'info');
 | ||||||
|  | 
 | ||||||
|  |     //         // 检查是否已缓存
 | ||||||
|  |     //         const isCached = this.videoStreams.has(videoFile);
 | ||||||
|  |              | ||||||
|  |     //         // 如果已缓存,直接使用,避免loading状态
 | ||||||
|  |     //         if (isCached) {
 | ||||||
|  |     //             const cachedStream = this.videoStreams.get(videoFile);
 | ||||||
|  |     //             if (cachedStream && cachedStream.getTracks().length > 0) {
 | ||||||
|  |     //                 // 直接切换到缓存的流
 | ||||||
|  |     //                 this.currentVideoStream = cachedStream;
 | ||||||
|  |     //                 this.recordedVideo.srcObject = cachedStream;
 | ||||||
|  |     //                 this.currentVideo = videoFile;
 | ||||||
|  |                      | ||||||
|  |     //                 // 立即播放,无需loading状态
 | ||||||
|  |     //                 await this.recordedVideo.play();
 | ||||||
|  |     //                 this.recordedVideo.classList.add('playing');
 | ||||||
|  |                      | ||||||
|  |     //                 this.logMessage(`使用缓存视频流: ${videoFile}`, 'success');
 | ||||||
|  |     //                 return;
 | ||||||
|  |     //             }
 | ||||||
|  |     //         }
 | ||||||
|  |              | ||||||
|  |     //         // 未缓存的视频才显示loading状态
 | ||||||
|  |     //         this.recordedVideo.classList.add('loading');
 | ||||||
|  |              | ||||||
|  |     //         // 先创建新的视频流
 | ||||||
|  |     //         const newStream = await this.createVideoStream(videoFile);
 | ||||||
|  |              | ||||||
|  |     //         // 减少等待时间
 | ||||||
|  |     //         await new Promise(resolve => setTimeout(resolve, 100));
 | ||||||
|  |              | ||||||
|  |     //         // 检查流是否有效
 | ||||||
|  |     //         if (!newStream || newStream.getTracks().length === 0) {
 | ||||||
|  |     //             throw new Error('创建的视频流无效');
 | ||||||
|  |     //         }
 | ||||||
|  |              | ||||||
|  |     //         // 设置新的视频流
 | ||||||
|  |     //         this.currentVideoStream = newStream;
 | ||||||
|  |     //         this.recordedVideo.srcObject = newStream;
 | ||||||
|  |     //         this.currentVideo = videoFile;
 | ||||||
|  |              | ||||||
|  |     //         // 确保视频开始播放
 | ||||||
|  |     //         try {
 | ||||||
|  |     //             await this.recordedVideo.play();
 | ||||||
|  |     //             this.logMessage('视频元素开始播放', 'info');
 | ||||||
|  |                  | ||||||
|  |     //             // 移除加载状态,添加播放状态
 | ||||||
|  |     //             this.recordedVideo.classList.remove('loading');
 | ||||||
|  |     //             this.recordedVideo.classList.add('playing');
 | ||||||
|  |     //         } catch (playError) {
 | ||||||
|  |     //             this.logMessage(`视频播放失败: ${playError.message}`, 'error');
 | ||||||
|  |     //             this.recordedVideo.classList.remove('loading');
 | ||||||
|  |     //         }
 | ||||||
|  |              | ||||||
|  |     //         // 现在停止旧的视频流
 | ||||||
|  |     //         if (this.currentVideoStream !== newStream) {
 | ||||||
|  |     //             const oldStream = this.currentVideoStream;
 | ||||||
|  |     //             setTimeout(() => {
 | ||||||
|  |     //                 if (oldStream) {
 | ||||||
|  |     //                     oldStream.getTracks().forEach(track => {
 | ||||||
|  |     //                         track.stop();
 | ||||||
|  |     //                         this.logMessage(`已停止旧轨道: ${track.kind}`, 'info');
 | ||||||
|  |     //                     });
 | ||||||
|  |     //                 }
 | ||||||
|  |     //             }, 1000); // 延迟1秒停止旧流,确保新流已经稳定
 | ||||||
|  |     //         }
 | ||||||
|  |              | ||||||
|  |     //         if (text) {
 | ||||||
|  |     //             this.currentVideoName.textContent = `交互视频: ${videoFile} (${type}: ${text})`;
 | ||||||
|  |     //             this.logMessage(`成功切换到交互视频流: ${videoFile} (${type}: ${text})`, 'success');
 | ||||||
|  |     //         } else {
 | ||||||
|  |     //             this.currentVideoName.textContent = `视频流: ${videoFile}`;
 | ||||||
|  |     //             this.logMessage(`成功切换到视频流: ${videoFile}`, 'success');
 | ||||||
|  |     //         }
 | ||||||
|  |              | ||||||
|  |     //         // 检查切换后的状态
 | ||||||
|  |     //         setTimeout(() => {
 | ||||||
|  |     //             this.checkVideoStreamStatus();
 | ||||||
|  |     //         }, 1000);
 | ||||||
|  |              | ||||||
|  |     //     } catch (error) {
 | ||||||
|  |     //         this.logMessage(`切换视频流失败: ${error.message}`, 'error');
 | ||||||
|  |     //         this.recordedVideo.classList.remove('loading');
 | ||||||
|  |              | ||||||
|  |     //         // 如果切换失败,尝试回到默认视频
 | ||||||
|  |     //         if (videoFile !== this.defaultVideo) {
 | ||||||
|  |     //             this.logMessage('尝试回到默认视频', 'info');
 | ||||||
|  |     //             await this.switchVideoStream(this.defaultVideo, 'fallback');
 | ||||||
|  |     //         }
 | ||||||
|  |     //     }
 | ||||||
|  |     // }
 | ||||||
|  | 
 | ||||||
|  |     // 修改原有的switchVideoStream方法,使用新的平滑切换
 | ||||||
|     async switchVideoStream(videoFile, type = '', text = '') { |     async switchVideoStream(videoFile, type = '', text = '') { | ||||||
|         try { |         // 使用平滑切换方法
 | ||||||
|             this.logMessage(`开始切换视频流: ${videoFile} (${type})`, 'info'); |         return await this.switchVideoStreamSmooth(videoFile, type, text); | ||||||
| 
 |  | ||||||
|             // 检查是否已缓存
 |  | ||||||
|             const isCached = this.videoStreams.has(videoFile); |  | ||||||
|              |  | ||||||
|             // 如果已缓存,直接使用,避免loading状态
 |  | ||||||
|             if (isCached) { |  | ||||||
|                 const cachedStream = this.videoStreams.get(videoFile); |  | ||||||
|                 if (cachedStream && cachedStream.getTracks().length > 0) { |  | ||||||
|                     // 直接切换到缓存的流
 |  | ||||||
|                     this.currentVideoStream = cachedStream; |  | ||||||
|                     this.recordedVideo.srcObject = cachedStream; |  | ||||||
|                     this.currentVideo = videoFile; |  | ||||||
|                      |  | ||||||
|                     // 立即播放,无需loading状态
 |  | ||||||
|                     await this.recordedVideo.play(); |  | ||||||
|                     this.recordedVideo.classList.add('playing'); |  | ||||||
|                      |  | ||||||
|                     this.logMessage(`使用缓存视频流: ${videoFile}`, 'success'); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // 未缓存的视频才显示loading状态
 |  | ||||||
|             this.recordedVideo.classList.add('loading'); |  | ||||||
|              |  | ||||||
|             // 先创建新的视频流
 |  | ||||||
|             const newStream = await this.createVideoStream(videoFile); |  | ||||||
|              |  | ||||||
|             // 减少等待时间
 |  | ||||||
|             await new Promise(resolve => setTimeout(resolve, 100)); |  | ||||||
|              |  | ||||||
|             // 检查流是否有效
 |  | ||||||
|             if (!newStream || newStream.getTracks().length === 0) { |  | ||||||
|                 throw new Error('创建的视频流无效'); |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // 设置新的视频流
 |  | ||||||
|             this.currentVideoStream = newStream; |  | ||||||
|             this.recordedVideo.srcObject = newStream; |  | ||||||
|             this.currentVideo = videoFile; |  | ||||||
|              |  | ||||||
|             // 确保视频开始播放
 |  | ||||||
|             try { |  | ||||||
|                 await this.recordedVideo.play(); |  | ||||||
|                 this.logMessage('视频元素开始播放', 'info'); |  | ||||||
|                  |  | ||||||
|                 // 移除加载状态,添加播放状态
 |  | ||||||
|                 this.recordedVideo.classList.remove('loading'); |  | ||||||
|                 this.recordedVideo.classList.add('playing'); |  | ||||||
|             } catch (playError) { |  | ||||||
|                 this.logMessage(`视频播放失败: ${playError.message}`, 'error'); |  | ||||||
|                 this.recordedVideo.classList.remove('loading'); |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // 现在停止旧的视频流
 |  | ||||||
|             if (this.currentVideoStream !== newStream) { |  | ||||||
|                 const oldStream = this.currentVideoStream; |  | ||||||
|                 setTimeout(() => { |  | ||||||
|                     if (oldStream) { |  | ||||||
|                         oldStream.getTracks().forEach(track => { |  | ||||||
|                             track.stop(); |  | ||||||
|                             this.logMessage(`已停止旧轨道: ${track.kind}`, 'info'); |  | ||||||
|                         }); |  | ||||||
|                     } |  | ||||||
|                 }, 1000); // 延迟1秒停止旧流,确保新流已经稳定
 |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             if (text) { |  | ||||||
|                 this.currentVideoName.textContent = `交互视频: ${videoFile} (${type}: ${text})`; |  | ||||||
|                 this.logMessage(`成功切换到交互视频流: ${videoFile} (${type}: ${text})`, 'success'); |  | ||||||
|             } else { |  | ||||||
|                 this.currentVideoName.textContent = `视频流: ${videoFile}`; |  | ||||||
|                 this.logMessage(`成功切换到视频流: ${videoFile}`, 'success'); |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // 检查切换后的状态
 |  | ||||||
|             setTimeout(() => { |  | ||||||
|                 this.checkVideoStreamStatus(); |  | ||||||
|             }, 1000); |  | ||||||
|              |  | ||||||
|         } catch (error) { |  | ||||||
|             this.logMessage(`切换视频流失败: ${error.message}`, 'error'); |  | ||||||
|             this.recordedVideo.classList.remove('loading'); |  | ||||||
|              |  | ||||||
|             // 如果切换失败,尝试回到默认视频
 |  | ||||||
|             if (videoFile !== this.defaultVideo) { |  | ||||||
|                 this.logMessage('尝试回到默认视频', 'info'); |  | ||||||
|                 await this.switchVideoStream(this.defaultVideo, 'fallback'); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 使用replaceTrack方式切换视频
 |     // 使用replaceTrack方式切换视频
 | ||||||
| @ -605,6 +616,153 @@ class WebRTCChat { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // 新增平滑视频切换方法
 | ||||||
|  |     async switchVideoStreamSmooth(videoFile, type = '', text = '') { | ||||||
|  |         try { | ||||||
|  |             this.logMessage(`开始平滑切换视频流: ${videoFile} (${type})`, 'info'); | ||||||
|  |              | ||||||
|  |             // 显示加载指示器
 | ||||||
|  |             this.showVideoLoading(); | ||||||
|  |              | ||||||
|  |             // 确定当前活跃的视频元素和缓冲元素
 | ||||||
|  |             const currentVideo = this.activeVideoElement === 'main' ? this.recordedVideo : this.recordedVideoBuffer; | ||||||
|  |             const bufferVideo = this.activeVideoElement === 'main' ? this.recordedVideoBuffer : this.recordedVideo; | ||||||
|  |              | ||||||
|  |             // 检查是否已缓存
 | ||||||
|  |             const isCached = this.videoStreams.has(videoFile); | ||||||
|  |             let newStream; | ||||||
|  |              | ||||||
|  |             if (isCached) { | ||||||
|  |                 const cachedStream = this.videoStreams.get(videoFile); | ||||||
|  |                 if (cachedStream && cachedStream.getTracks().length > 0) { | ||||||
|  |                     newStream = cachedStream; | ||||||
|  |                     this.logMessage(`使用缓存视频流: ${videoFile}`, 'success'); | ||||||
|  |                 } else { | ||||||
|  |                     newStream = await this.createVideoStream(videoFile); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 newStream = await this.createVideoStream(videoFile); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // 在缓冲视频元素中预加载新视频
 | ||||||
|  |             bufferVideo.srcObject = newStream; | ||||||
|  |              | ||||||
|  |             // 等待缓冲视频准备就绪
 | ||||||
|  |             await new Promise((resolve, reject) => { | ||||||
|  |                 const timeout = setTimeout(() => { | ||||||
|  |                     reject(new Error('视频加载超时')); | ||||||
|  |                 }, 5000); | ||||||
|  |                  | ||||||
|  |                 const onCanPlay = () => { | ||||||
|  |                     clearTimeout(timeout); | ||||||
|  |                     bufferVideo.removeEventListener('canplay', onCanPlay); | ||||||
|  |                     bufferVideo.removeEventListener('error', onError); | ||||||
|  |                     resolve(); | ||||||
|  |                 }; | ||||||
|  |                  | ||||||
|  |                 const onError = (error) => { | ||||||
|  |                     clearTimeout(timeout); | ||||||
|  |                     bufferVideo.removeEventListener('canplay', onCanPlay); | ||||||
|  |                     bufferVideo.removeEventListener('error', onError); | ||||||
|  |                     reject(error); | ||||||
|  |                 }; | ||||||
|  |                  | ||||||
|  |                 bufferVideo.addEventListener('canplay', onCanPlay); | ||||||
|  |                 bufferVideo.addEventListener('error', onError); | ||||||
|  |                  | ||||||
|  |                 // 开始播放缓冲视频
 | ||||||
|  |                 bufferVideo.play().catch(onError); | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             // 隐藏加载指示器
 | ||||||
|  |             this.hideVideoLoading(); | ||||||
|  |              | ||||||
|  |             // 执行淡入淡出切换
 | ||||||
|  |             await this.performVideoTransition(currentVideo, bufferVideo); | ||||||
|  |              | ||||||
|  |             // 更新当前视频流和活跃元素
 | ||||||
|  |             this.currentVideoStream = newStream; | ||||||
|  |             this.currentVideo = videoFile; | ||||||
|  |             this.activeVideoElement = this.activeVideoElement === 'main' ? 'buffer' : 'main'; | ||||||
|  |              | ||||||
|  |             // 停止旧视频流(延迟停止避免闪烁)
 | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 if (currentVideo.srcObject && currentVideo.srcObject !== newStream) { | ||||||
|  |                     currentVideo.srcObject.getTracks().forEach(track => track.stop()); | ||||||
|  |                     currentVideo.srcObject = null; | ||||||
|  |                 } | ||||||
|  |             }, 1000); | ||||||
|  |              | ||||||
|  |             // 更新WebRTC连接中的视频轨道
 | ||||||
|  |             if (this.peerConnection && this.videoSender) { | ||||||
|  |                 const newVideoTrack = newStream.getVideoTracks()[0]; | ||||||
|  |                 if (newVideoTrack) { | ||||||
|  |                     await this.videoSender.replaceTrack(newVideoTrack); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // 更新显示信息
 | ||||||
|  |             if (text) { | ||||||
|  |                 this.currentVideoName.textContent = `交互视频: ${videoFile} (${type}: ${text})`; | ||||||
|  |                 this.logMessage(`成功切换到交互视频流: ${videoFile} (${type}: ${text})`, 'success'); | ||||||
|  |             } else { | ||||||
|  |                 this.currentVideoName.textContent = `视频流: ${videoFile}`; | ||||||
|  |                 this.logMessage(`成功切换到视频流: ${videoFile}`, 'success'); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |         } catch (error) { | ||||||
|  |             this.logMessage(`平滑切换视频流失败: ${error.message}`, 'error'); | ||||||
|  |             this.hideVideoLoading(); | ||||||
|  |              | ||||||
|  |             // 如果切换失败,尝试回到默认视频
 | ||||||
|  |             if (videoFile !== this.defaultVideo) { | ||||||
|  |                 this.logMessage('尝试回到默认视频', 'info'); | ||||||
|  |                 await this.switchVideoStreamSmooth(this.defaultVideo, 'fallback'); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 执行视频过渡动画
 | ||||||
|  |     async performVideoTransition(currentVideo, bufferVideo) { | ||||||
|  |         return new Promise((resolve) => { | ||||||
|  |             // 添加切换类
 | ||||||
|  |             currentVideo.classList.add('switching'); | ||||||
|  |             bufferVideo.classList.add('switching'); | ||||||
|  |              | ||||||
|  |             // 等待CSS过渡完成
 | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 // 移除切换类
 | ||||||
|  |                 currentVideo.classList.remove('switching'); | ||||||
|  |                 bufferVideo.classList.remove('switching'); | ||||||
|  |                  | ||||||
|  |                 // 交换z-index
 | ||||||
|  |                 if (currentVideo.style.zIndex === '2' || !currentVideo.style.zIndex) { | ||||||
|  |                     currentVideo.style.zIndex = '1'; | ||||||
|  |                     bufferVideo.style.zIndex = '2'; | ||||||
|  |                 } else { | ||||||
|  |                     currentVideo.style.zIndex = '2'; | ||||||
|  |                     bufferVideo.style.zIndex = '1'; | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 resolve(); | ||||||
|  |             }, 500); // 与CSS过渡时间一致
 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 显示加载指示器
 | ||||||
|  |     showVideoLoading() { | ||||||
|  |         if (this.videoLoading) { | ||||||
|  |             this.videoLoading.classList.add('show'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 隐藏加载指示器
 | ||||||
|  |     hideVideoLoading() { | ||||||
|  |         if (this.videoLoading) { | ||||||
|  |             this.videoLoading.classList.remove('show'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     bindEvents() { |     bindEvents() { | ||||||
|         // 开始通话按钮
 |         // 开始通话按钮
 | ||||||
|         this.startButton.onclick = () => this.startCall(); |         this.startButton.onclick = () => this.startCall(); | ||||||
| @ -632,8 +790,8 @@ class WebRTCChat { | |||||||
|         }; |         }; | ||||||
|          |          | ||||||
|         // 语音输入按钮
 |         // 语音输入按钮
 | ||||||
|         this.startVoiceButton.onclick = () => this.startVoiceRecording(); |         // this.startVoiceButton.onclick = () => this.startVoiceRecording();
 | ||||||
|         this.stopVoiceButton.onclick = () => this.stopVoiceRecording(); |         // this.stopVoiceButton.onclick = () => this.stopVoiceRecording();
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async startCall() { |     async startCall() { | ||||||
| @ -644,10 +802,14 @@ class WebRTCChat { | |||||||
|             }); |             }); | ||||||
|              |              | ||||||
|             await this.createPeerConnection(); |             await this.createPeerConnection(); | ||||||
|              |             this.startVoiceRecording() | ||||||
|  |             // this.audioProcessor.startRecording()
 | ||||||
|             this.startButton.disabled = true; |             this.startButton.disabled = true; | ||||||
|         this.stopButton.disabled = false; |         this.stopButton.disabled = false; | ||||||
|          |          | ||||||
|  |         // 显示结束通话按钮
 | ||||||
|  |         this.stopButton.classList.add('show'); | ||||||
|  |          | ||||||
|         this.updateAudioStatus('已连接', 'connected'); |         this.updateAudioStatus('已连接', 'connected'); | ||||||
|             this.logMessage('音频通话已开始', 'success'); |             this.logMessage('音频通话已开始', 'success'); | ||||||
|              |              | ||||||
| @ -690,8 +852,14 @@ class WebRTCChat { | |||||||
|         this.startButton.disabled = false; |         this.startButton.disabled = false; | ||||||
|         this.stopButton.disabled = true; |         this.stopButton.disabled = true; | ||||||
|          |          | ||||||
|  |         // 隐藏结束通话按钮
 | ||||||
|  |         this.stopButton.classList.remove('show'); | ||||||
|  |          | ||||||
|  |         this.stopVoiceRecording() | ||||||
|         this.updateAudioStatus('未连接', 'disconnected'); |         this.updateAudioStatus('未连接', 'disconnected'); | ||||||
|         this.logMessage('音频通话已结束', 'info'); |         this.logMessage('音频通话已结束', 'info'); | ||||||
|  | 
 | ||||||
|  |          | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async createPeerConnection() { |     async createPeerConnection() { | ||||||
| @ -864,13 +1032,13 @@ class WebRTCChat { | |||||||
|         const success = await this.audioProcessor.startRecording(); |         const success = await this.audioProcessor.startRecording(); | ||||||
|          |          | ||||||
|         if (success) { |         if (success) { | ||||||
|             this.startVoiceButton.disabled = true; |             // this.startVoiceButton.disabled = true;
 | ||||||
|             this.stopVoiceButton.disabled = false; |             // this.stopVoiceButton.disabled = false;
 | ||||||
|             this.startVoiceButton.classList.add('recording'); |             // this.startVoiceButton.classList.add('recording');
 | ||||||
|             this.voiceStatus.textContent = '等待语音输入...'; |             // this.voiceStatus.textContent = '等待语音输入...';
 | ||||||
|             this.logMessage('高级语音录制已启动', 'success'); |             this.logMessage('高级语音录制已启动', 'success'); | ||||||
|         } else { |         } else { | ||||||
|             this.voiceStatus.textContent = '录音启动失败'; |             // this.voiceStatus.textContent = '录音启动失败';
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -878,16 +1046,21 @@ class WebRTCChat { | |||||||
|     stopVoiceRecording() { |     stopVoiceRecording() { | ||||||
|         this.audioProcessor.stopRecording(); |         this.audioProcessor.stopRecording(); | ||||||
|          |          | ||||||
|         this.startVoiceButton.disabled = false; |         // this.startVoiceButton.disabled = false;
 | ||||||
|         this.stopVoiceButton.disabled = true; |         // this.stopVoiceButton.disabled = true;
 | ||||||
|         this.startVoiceButton.classList.remove('recording'); |         // this.startVoiceButton.classList.remove('recording');
 | ||||||
|         this.voiceStatus.textContent = '点击开始语音输入'; |         // this.voiceStatus.textContent = '点击开始语音输入';
 | ||||||
|          |          | ||||||
|         this.logMessage('语音录制已停止', 'info'); |         this.logMessage('语音录制已停止', 'info'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 处理语音输入结果
 |     // 处理语音输入结果
 | ||||||
|     async handleVoiceInput(text) { |     async handleVoiceInput(text) { | ||||||
|  |         if(text == ""){ | ||||||
|  |             console.log("识别到用户未说话") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // 根据文本查找对应视频
 |         // 根据文本查找对应视频
 | ||||||
|         let videoFile = this.videoMapping['default'] || this.defaultVideo; |         let videoFile = this.videoMapping['default'] || this.defaultVideo; | ||||||
|         for (const [key, value] of Object.entries(this.videoMapping)) { |         for (const [key, value] of Object.entries(this.videoMapping)) { | ||||||
|  | |||||||
| @ -87,7 +87,7 @@ async function requestLLMStream({ apiKey, model, messages, onSegment }) { | |||||||
|                     } |                     } | ||||||
|                      |                      | ||||||
|                     // 如果累积文本长度大于5个字,处理它
 |                     // 如果累积文本长度大于5个字,处理它
 | ||||||
|                     if (accumulatedText.length > 8 && onSegment) { |                     if (accumulatedText.length > 6 && onSegment) { | ||||||
|                       console.log('检测到完整段落:', accumulatedText); |                       console.log('检测到完整段落:', accumulatedText); | ||||||
|                       await onSegment(accumulatedText, false); |                       await onSegment(accumulatedText, false); | ||||||
|                       hasProcessed = true; |                       hasProcessed = true; | ||||||
|  | |||||||
| @ -66,7 +66,7 @@ async function processAudioQueue() { | |||||||
|     if (!isPlaying && audioQueue.length > 0) { |     if (!isPlaying && audioQueue.length > 0) { | ||||||
|       const audioItem = audioQueue.shift(); |       const audioItem = audioQueue.shift(); | ||||||
|       const sayName = 'say-5s-m-sw' |       const sayName = 'say-5s-m-sw' | ||||||
|       const targetVideo = '5.mp4' |       const targetVideo = 'd-0.mp4' | ||||||
|       // 如果是第一个音频片段,触发视频切换
 |       // 如果是第一个音频片段,触发视频切换
 | ||||||
|       if (isFirstChunk && sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.switchVideoWithReplaceTrack) { |       if (isFirstChunk && sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.switchVideoWithReplaceTrack) { | ||||||
|         try { |         try { | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								videos/bd-1.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/bd-1.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/d-0.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/d-0.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Song367
						Song367