视频切换,切换三个场景,未实现场景人设切换
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2m18s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2m18s
				
			This commit is contained in:
		
							parent
							
								
									f176818155
								
							
						
					
					
						commit
						c96c49ff3f
					
				
							
								
								
									
										104
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								server.js
									
									
									
									
									
								
							| @ -85,10 +85,64 @@ app.delete('/api/messages/clear', async (req, res) => { | |||||||
| // 存储连接的客户端和他们的视频流状态
 | // 存储连接的客户端和他们的视频流状态
 | ||||||
| const connectedClients = new Map(); | const connectedClients = new Map(); | ||||||
| 
 | 
 | ||||||
|  | // 场景轮询系统
 | ||||||
|  | let currentSceneIndex = 0; | ||||||
|  | const scenes = [ | ||||||
|  |   { | ||||||
|  |     name: '起床', | ||||||
|  |     defaultVideo: '8-4-bd-2.mp4', | ||||||
|  |     interactionVideo: '8-4-sh.mp4', | ||||||
|  |     tag: 'wakeup' | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '开车', | ||||||
|  |     defaultVideo: '8-4-kc-bd.mp4', // 根据您的视频文件调整
 | ||||||
|  |     interactionVideo: '8-4-kc-sh.mp4', | ||||||
|  |     tag: 'driving' | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '喝茶', | ||||||
|  |     defaultVideo: '8-4-hc-bd.mp4', | ||||||
|  |     interactionVideo: '8-4-hc-sh.mp4', | ||||||
|  |     tag: 'tea' | ||||||
|  |   } | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | // 获取当前场景
 | ||||||
|  | function getCurrentScene() { | ||||||
|  |   return scenes[currentSceneIndex]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 切换到下一个场景
 | ||||||
|  | function switchToNextScene() { | ||||||
|  |   currentSceneIndex = (currentSceneIndex + 1) % scenes.length; | ||||||
|  |   console.log(`切换到场景: ${getCurrentScene().name}`); | ||||||
|  |   return getCurrentScene(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 视频映射配置 - 动态更新
 | ||||||
|  | function getVideoMapping() { | ||||||
|  |   const currentScene = getCurrentScene(); | ||||||
|  |   return { | ||||||
|  |     'defaultVideo': currentScene.defaultVideo, | ||||||
|  |     'interactionVideo': currentScene.interactionVideo, | ||||||
|  |     'tag': currentScene.tag | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 默认视频流配置 - 动态获取
 | ||||||
|  | function getDefaultVideo() { | ||||||
|  |   return getCurrentScene().defaultVideo; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | let currentScene = getCurrentScene(); | ||||||
|  | 
 | ||||||
| // 视频映射配置
 | // 视频映射配置
 | ||||||
| const videoMapping = { | const videoMapping = { | ||||||
|   // 'say-6s-m-e': '1-m.mp4',
 |   // 'say-6s-m-e': '1-m.mp4',
 | ||||||
|   'default': 'chang.mp4', |   'default': currentScene.defaultVideo, | ||||||
|  |   '8-4-sh': currentScene.interactionVideo, | ||||||
|  |   'tag': currentScene.tag | ||||||
|   // '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': 'd-0.mp4',
 |   // 'say-5s-m-sw': 'd-0.mp4',
 | ||||||
| @ -96,7 +150,7 @@ const videoMapping = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // 默认视频流配置
 | // 默认视频流配置
 | ||||||
| const DEFAULT_VIDEO = 'chang.mp4'; | const DEFAULT_VIDEO = currentScene.defaultVideo; | ||||||
| const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
 | const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
 | ||||||
| 
 | 
 | ||||||
| // 获取视频列表
 | // 获取视频列表
 | ||||||
| @ -115,13 +169,14 @@ app.get('/api/videos', (req, res) => { | |||||||
| 
 | 
 | ||||||
| // 获取视频映射
 | // 获取视频映射
 | ||||||
| app.get('/api/video-mapping', (req, res) => { | app.get('/api/video-mapping', (req, res) => { | ||||||
|  |   // videoMapping = getCurrentScene()
 | ||||||
|   res.json({ mapping: videoMapping }); |   res.json({ mapping: videoMapping }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| // 获取默认视频
 | // 获取默认视频
 | ||||||
| app.get('/api/default-video', (req, res) => { | app.get('/api/default-video', (req, res) => { | ||||||
|   res.json({  |   res.json({  | ||||||
|     defaultVideo: DEFAULT_VIDEO, |     defaultVideo: getDefaultVideo(), | ||||||
|     autoLoop: true |     autoLoop: true | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| @ -131,7 +186,7 @@ io.on('connection', (socket) => { | |||||||
|   console.log('用户连接:', socket.id); |   console.log('用户连接:', socket.id); | ||||||
|   connectedClients.set(socket.id, { |   connectedClients.set(socket.id, { | ||||||
|     socket: socket, |     socket: socket, | ||||||
|     currentVideo: DEFAULT_VIDEO, |     currentVideo: getDefaultVideo(), | ||||||
|     isInInteraction: false |     isInInteraction: false | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| @ -185,12 +240,12 @@ io.on('connection', (socket) => { | |||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         console.log(`交互超时,用户 ${socket.id} 回到默认视频`); |         console.log(`交互超时,用户 ${socket.id} 回到默认视频`); | ||||||
|         if (client) { |         if (client) { | ||||||
|           client.currentVideo = DEFAULT_VIDEO; |           client.currentVideo = getDefaultVideo(); | ||||||
|           client.isInInteraction = false; |           client.isInInteraction = false; | ||||||
|         } |         } | ||||||
|         // 广播回到默认视频的指令
 |         // 广播回到默认视频的指令
 | ||||||
|         io.emit('video-stream-switched', {  |         io.emit('video-stream-switched', {  | ||||||
|           videoFile: DEFAULT_VIDEO,  |           videoFile: getDefaultVideo(),  | ||||||
|           type: 'default', |           type: 'default', | ||||||
|           from: socket.id  |           from: socket.id  | ||||||
|         }); |         }); | ||||||
| @ -203,7 +258,7 @@ io.on('connection', (socket) => { | |||||||
|     console.log('通话开始,用户:', socket.id); |     console.log('通话开始,用户:', socket.id); | ||||||
|     const client = connectedClients.get(socket.id); |     const client = connectedClients.get(socket.id); | ||||||
|     if (client) { |     if (client) { | ||||||
|       client.currentVideo = DEFAULT_VIDEO; |       client.currentVideo = getDefaultVideo(); | ||||||
|       client.isInInteraction = false; |       client.isInInteraction = false; | ||||||
|     } |     } | ||||||
|     io.emit('call-started', { from: socket.id }); |     io.emit('call-started', { from: socket.id }); | ||||||
| @ -262,15 +317,46 @@ io.on('connection', (socket) => { | |||||||
|     console.log('用户请求回到默认视频:', socket.id); |     console.log('用户请求回到默认视频:', socket.id); | ||||||
|     const client = connectedClients.get(socket.id); |     const client = connectedClients.get(socket.id); | ||||||
|     if (client) { |     if (client) { | ||||||
|       client.currentVideo = DEFAULT_VIDEO; |       client.currentVideo = getDefaultVideo(); | ||||||
|       client.isInInteraction = false; |       client.isInInteraction = false; | ||||||
|     } |     } | ||||||
|     socket.emit('switch-video-stream', {  |     socket.emit('switch-video-stream', {  | ||||||
|       videoFile: DEFAULT_VIDEO,  |       videoFile: getDefaultVideo(),  | ||||||
|       type: 'default'  |       type: 'default'  | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   // 处理用户关闭连接事件
 | ||||||
|  |   socket.on('user-disconnect', () => { | ||||||
|  |     console.log('用户主动关闭连接:', socket.id); | ||||||
|  |      | ||||||
|  |     // 切换到下一个场景
 | ||||||
|  |     const newScene = switchToNextScene(); | ||||||
|  |     console.log(`场景已切换到: ${newScene.name}`); | ||||||
|  |      | ||||||
|  |     // 更新videoMapping
 | ||||||
|  |     const newMapping = getVideoMapping(); | ||||||
|  |     videoMapping['default'] = newMapping.defaultVideo; | ||||||
|  |     videoMapping['8-4-sh'] = newMapping.interactionVideo; | ||||||
|  |     videoMapping['tag'] = newMapping.tag; | ||||||
|  |      | ||||||
|  |     console.log('videoMapping已更新:', videoMapping); | ||||||
|  |      | ||||||
|  |     // 清理客户端状态
 | ||||||
|  |     const client = connectedClients.get(socket.id); | ||||||
|  |     if (client) { | ||||||
|  |       client.currentVideo = newMapping.defaultVideo; | ||||||
|  |       client.isInInteraction = false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // 广播场景切换事件给所有客户端
 | ||||||
|  |     io.emit('scene-switched', { | ||||||
|  |       scene: newScene, | ||||||
|  |       mapping: newMapping, | ||||||
|  |       from: socket.id | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   // 断开连接
 |   // 断开连接
 | ||||||
|   socket.on('disconnect', () => { |   socket.on('disconnect', () => { | ||||||
|     console.log('用户断开连接:', socket.id); |     console.log('用户断开连接:', socket.id); | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| class AudioProcessor { | class AudioProcessor { | ||||||
|     constructor(options = {}) { |     constructor(options = {}) { | ||||||
|         this.audioContext = null; |         this.audioContext = null; | ||||||
|  |         this.stream = null; // 添加这一行
 | ||||||
|         this.isRecording = false; |         this.isRecording = false; | ||||||
|         this.audioChunks = []; |         this.audioChunks = []; | ||||||
|          |          | ||||||
| @ -311,22 +312,29 @@ class AudioProcessor { | |||||||
|     } |     } | ||||||
|      |      | ||||||
|     // 开始录音
 |     // 开始录音
 | ||||||
|     async startRecording() { |     async startRecording(existingStream = null) { | ||||||
|         try { |         try { | ||||||
|             const stream = await navigator.mediaDevices.getUserMedia({ |             // 如果有外部提供的音频流,使用它;否则获取新的
 | ||||||
|                 audio: { |             if (existingStream) { | ||||||
|                     sampleRate: 16000, |                 this.stream = existingStream; | ||||||
|                     channelCount: 1, |                 console.log('使用外部提供的音频流'); | ||||||
|                     echoCancellation: true, |             } else { | ||||||
|                     noiseSuppression: true |                 this.stream = await navigator.mediaDevices.getUserMedia({ | ||||||
|                 } |                     audio: { | ||||||
|             }); |                         sampleRate: 16000, | ||||||
|  |                         channelCount: 1, | ||||||
|  |                         echoCancellation: true, | ||||||
|  |                         noiseSuppression: true | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |                 console.log('获取新的音频流'); | ||||||
|  |             } | ||||||
|              |              | ||||||
|             this.audioContext = new (window.AudioContext || window.webkitAudioContext)({ |             this.audioContext = new (window.AudioContext || window.webkitAudioContext)({ | ||||||
|                 sampleRate: 16000 |                 sampleRate: 16000 | ||||||
|             }); |             }); | ||||||
|              |              | ||||||
|             const source = this.audioContext.createMediaStreamSource(stream); |             const source = this.audioContext.createMediaStreamSource(this.stream); | ||||||
|             const processor = this.audioContext.createScriptProcessor(4096, 1, 1); |             const processor = this.audioContext.createScriptProcessor(4096, 1, 1); | ||||||
|              |              | ||||||
|             processor.onaudioprocess = (event) => { |             processor.onaudioprocess = (event) => { | ||||||
| @ -343,6 +351,10 @@ class AudioProcessor { | |||||||
|             source.connect(processor); |             source.connect(processor); | ||||||
|             processor.connect(this.audioContext.destination); |             processor.connect(this.audioContext.destination); | ||||||
|              |              | ||||||
|  |             // 保存处理器引用以便后续清理
 | ||||||
|  |             this.processor = processor; | ||||||
|  |             this.source = source; | ||||||
|  |              | ||||||
|             this.isRecording = true; |             this.isRecording = true; | ||||||
|             this.onStatusUpdate('等待语音输入...', 'ready'); |             this.onStatusUpdate('等待语音输入...', 'ready'); | ||||||
|          |          | ||||||
| @ -364,6 +376,17 @@ class AudioProcessor { | |||||||
|     stopRecording() { |     stopRecording() { | ||||||
|         console.log('开始停止录音...'); |         console.log('开始停止录音...'); | ||||||
|          |          | ||||||
|  |         // 断开音频节点连接
 | ||||||
|  |         if (this.source) { | ||||||
|  |             this.source.disconnect(); | ||||||
|  |             this.source = null; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (this.processor) { | ||||||
|  |             this.processor.disconnect(); | ||||||
|  |             this.processor = null; | ||||||
|  |         } | ||||||
|  |          | ||||||
|         // 停止所有音频轨道
 |         // 停止所有音频轨道
 | ||||||
|         if (this.stream) { |         if (this.stream) { | ||||||
|             this.stream.getTracks().forEach(track => { |             this.stream.getTracks().forEach(track => { | ||||||
| @ -400,6 +423,10 @@ class AudioProcessor { | |||||||
|         this.consecutiveFramesCount = 0; |         this.consecutiveFramesCount = 0; | ||||||
|         this.frameBuffer = []; |         this.frameBuffer = []; | ||||||
|          |          | ||||||
|  |         // 重置校准状态,确保下次启动时重新校准
 | ||||||
|  |         this.noiseCalibrationSamples = []; | ||||||
|  |         this.isCalibrated = false; | ||||||
|  |          | ||||||
|         this.onStatusUpdate('录音已完全停止', 'stopped'); |         this.onStatusUpdate('录音已完全停止', 'stopped'); | ||||||
|         console.log('录音已完全停止,所有资源已释放'); |         console.log('录音已完全停止,所有资源已释放'); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -219,6 +219,67 @@ | |||||||
|             box-shadow: 0 6px 20px rgba(34, 197, 94, 0.5); |             box-shadow: 0 6px 20px rgba(34, 197, 94, 0.5); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|  |         #startButton.connecting { | ||||||
|  |             background: rgba(255, 193, 7, 0.9); | ||||||
|  |             cursor: not-allowed; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #startButton.connecting:hover { | ||||||
|  |             background: rgba(255, 193, 7, 0.9); | ||||||
|  |             transform: none; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #startButton.calling { | ||||||
|  |             background: rgba(255, 193, 7, 0.9); | ||||||
|  |             animation: pulse 2s infinite; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #startButton.calling:hover { | ||||||
|  |             background: rgba(255, 193, 7, 0.95); | ||||||
|  |             transform: scale(1.05); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         @keyframes pulse { | ||||||
|  |             0% { | ||||||
|  |                 box-shadow: 0 4px 15px rgba(255, 193, 7, 0.3); | ||||||
|  |             } | ||||||
|  |             50% { | ||||||
|  |                 box-shadow: 0 6px 25px rgba(255, 193, 7, 0.6); | ||||||
|  |             } | ||||||
|  |             100% { | ||||||
|  |                 box-shadow: 0 4px 15px rgba(255, 193, 7, 0.3); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .audio-status { | ||||||
|  |             position: absolute; | ||||||
|  |             top: 20px; | ||||||
|  |             left: 50%; | ||||||
|  |             transform: translateX(-50%); | ||||||
|  |             background: rgba(0, 0, 0, 0.7); | ||||||
|  |             color: white; | ||||||
|  |             padding: 8px 16px; | ||||||
|  |             border-radius: 20px; | ||||||
|  |             font-size: 14px; | ||||||
|  |             z-index: 1000; | ||||||
|  |             transition: all 0.3s ease; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .audio-status.connecting { | ||||||
|  |             background: rgba(255, 193, 7, 0.9); | ||||||
|  |             color: #000; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .audio-status.connected { | ||||||
|  |             background: rgba(40, 167, 69, 0.9); | ||||||
|  |             color: white; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .audio-status.error { | ||||||
|  |             background: rgba(220, 53, 69, 0.9); | ||||||
|  |             color: white; | ||||||
|  |         } | ||||||
|  |          | ||||||
|         #startButton svg { |         #startButton svg { | ||||||
|             width: 24px; |             width: 24px; | ||||||
|             height: 24px; |             height: 24px; | ||||||
| @ -251,8 +312,8 @@ | |||||||
|         </header> |         </header> | ||||||
| 
 | 
 | ||||||
|         <div class="main-content"> |         <div class="main-content"> | ||||||
|             <!-- 隐藏的音频状态显示 --> |             <!-- 音频状态显示 - 显示状态文本 --> | ||||||
|             <div class="audio-status" style="display: none;"> |             <div class="audio-status"> | ||||||
|                 <div class="status-indicator"> |                 <div class="status-indicator"> | ||||||
|                     <span id="audioStatus">未连接</span> |                     <span id="audioStatus">未连接</span> | ||||||
|                 </div> |                 </div> | ||||||
| @ -288,9 +349,22 @@ | |||||||
|             <!-- 控制按钮 - 悬浮在视频上方 --> |             <!-- 控制按钮 - 悬浮在视频上方 --> | ||||||
|             <div class="controls"> |             <div class="controls"> | ||||||
|                 <button id="startButton" class="btn btn-primary" title="开始通话"> |                 <button id="startButton" class="btn btn-primary" title="开始通话"> | ||||||
|                     <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |                     <!-- 默认通话图标 --> | ||||||
|  |                     <svg id="callIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|                         <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" fill="white"/> |                         <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" fill="white"/> | ||||||
|                     </svg> |                     </svg> | ||||||
|  |                     <!-- 通话中图标(初始隐藏) --> | ||||||
|  |                     <svg id="callingIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: none;"> | ||||||
|  |                         <circle cx="12" cy="12" r="3" fill="white"> | ||||||
|  |                             <animate attributeName="r" values="3;5;3" dur="1.5s" repeatCount="indefinite"/> | ||||||
|  |                             <animate attributeName="opacity" values="1;0.5;1" dur="1.5s" repeatCount="indefinite"/> | ||||||
|  |                         </circle> | ||||||
|  |                         <circle cx="12" cy="12" r="8" stroke="white" stroke-width="2" fill="none" opacity="0.6"> | ||||||
|  |                             <animate attributeName="r" values="8;12;8" dur="2s" repeatCount="indefinite"/> | ||||||
|  |                             <animate attributeName="opacity" values="0.6;0.2;0.6" dur="2s" repeatCount="indefinite"/> | ||||||
|  |                         </circle> | ||||||
|  |                         <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" fill="white" opacity="0.8"/> | ||||||
|  |                     </svg> | ||||||
|                 </button> |                 </button> | ||||||
|                 <button id="stopButton" class="btn btn-danger" disabled title="结束通话"> |                 <button id="stopButton" class="btn btn-danger" disabled title="结束通话"> | ||||||
|                     <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |                     <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  | |||||||
							
								
								
									
										123
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								src/index.js
									
									
									
									
									
								
							| @ -9,6 +9,8 @@ class WebRTCChat { | |||||||
|     constructor() { |     constructor() { | ||||||
|         console.log('WebRTCChat 构造函数开始执行'); |         console.log('WebRTCChat 构造函数开始执行'); | ||||||
|          |          | ||||||
|  | 
 | ||||||
|  |          | ||||||
|         // 初始化历史消息(异步)
 |         // 初始化历史消息(异步)
 | ||||||
|         this.initializeHistory(); |         this.initializeHistory(); | ||||||
|          |          | ||||||
| @ -20,6 +22,7 @@ class WebRTCChat { | |||||||
|         this.audioChunks = []; |         this.audioChunks = []; | ||||||
|         this.videoMapping = {}; |         this.videoMapping = {}; | ||||||
|         this.defaultVideo = 'chang.mp4'; |         this.defaultVideo = 'chang.mp4'; | ||||||
|  |         this.interactionVideo = 'chang.mp4'; | ||||||
|         this.currentVideoTag = 'default'; |         this.currentVideoTag = 'default'; | ||||||
|         this.currentVideo = null; |         this.currentVideo = null; | ||||||
|         this.videoStreams = new Map(); // 存储不同视频的MediaStream
 |         this.videoStreams = new Map(); // 存储不同视频的MediaStream
 | ||||||
| @ -147,6 +150,13 @@ class WebRTCChat { | |||||||
|             this.logMessage('通话已开始', 'success'); |             this.logMessage('通话已开始', 'success'); | ||||||
|             this.startDefaultVideoStream(); |             this.startDefaultVideoStream(); | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|  |         // 场景切换处理
 | ||||||
|  |         this.socket.on('scene-switched', (data) => { | ||||||
|  |             this.logMessage(`场景已切换到: ${data.currentScene}`, 'info'); | ||||||
|  |             // 移除自动清除缓存和播放视频的逻辑
 | ||||||
|  |             // 现在依赖页面刷新来处理缓存清除
 | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async initializeHistory() { |     async initializeHistory() { | ||||||
| @ -163,6 +173,8 @@ class WebRTCChat { | |||||||
|             const response = await fetch('/api/video-mapping'); |             const response = await fetch('/api/video-mapping'); | ||||||
|             const data = await response.json(); |             const data = await response.json(); | ||||||
|             this.videoMapping = data.mapping; |             this.videoMapping = data.mapping; | ||||||
|  |             this.interactionVideo = data.mapping['8-4-sh']; | ||||||
|  |             this.defaultVideo = data.mapping["default"]; | ||||||
|             this.logMessage('视频映射加载成功', 'success'); |             this.logMessage('视频映射加载成功', 'success'); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             this.logMessage('加载视频映射失败: ' + error.message, 'error'); |             this.logMessage('加载视频映射失败: ' + error.message, 'error'); | ||||||
| @ -429,7 +441,8 @@ class WebRTCChat { | |||||||
|     // 在应用初始化时预加载常用视频
 |     // 在应用初始化时预加载常用视频
 | ||||||
|     async preloadCommonVideos() { |     async preloadCommonVideos() { | ||||||
|         // 获取所有可能需要的视频
 |         // 获取所有可能需要的视频
 | ||||||
|         const videosToPreload = new Set(['chang.mp4']); |         console.log("default video, interaction video", [this.defaultVideo, this.interactionVideo]) | ||||||
|  |         const videosToPreload = new Set([this.defaultVideo, this.interactionVideo]); | ||||||
|          |          | ||||||
|         // 添加视频映射中的所有视频
 |         // 添加视频映射中的所有视频
 | ||||||
|         // Object.values(this.videoMapping).forEach(video => {
 |         // Object.values(this.videoMapping).forEach(video => {
 | ||||||
| @ -801,6 +814,9 @@ class WebRTCChat { | |||||||
| 
 | 
 | ||||||
|     async startCall() { |     async startCall() { | ||||||
|         try { |         try { | ||||||
|  |             // 切换到通话中图标
 | ||||||
|  |             this.switchToCallingIcon(); | ||||||
|  |              | ||||||
|             // 添加更详细的错误处理
 |             // 添加更详细的错误处理
 | ||||||
|             console.log('开始请求麦克风权限...'); |             console.log('开始请求麦克风权限...'); | ||||||
|             this.localStream = await navigator.mediaDevices.getUserMedia({ |             this.localStream = await navigator.mediaDevices.getUserMedia({ | ||||||
| @ -810,8 +826,8 @@ class WebRTCChat { | |||||||
|             console.log('麦克风权限获取成功'); |             console.log('麦克风权限获取成功'); | ||||||
|              |              | ||||||
|             await this.createPeerConnection(); |             await this.createPeerConnection(); | ||||||
|             this.startVoiceRecording() |             await this.startVoiceRecording(); | ||||||
|             // this.audioProcessor.startRecording()
 |              | ||||||
|             this.startButton.disabled = true; |             this.startButton.disabled = true; | ||||||
|             this.stopButton.disabled = false; |             this.stopButton.disabled = false; | ||||||
|              |              | ||||||
| @ -831,12 +847,25 @@ class WebRTCChat { | |||||||
|             // 通知服务器通话开始
 |             // 通知服务器通话开始
 | ||||||
|             this.socket.emit('call-started'); |             this.socket.emit('call-started'); | ||||||
|              |              | ||||||
|  |             // 开始播放当前场景的默认视频
 | ||||||
|  |             await this.startDefaultVideoStream(); | ||||||
|  |              | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|  |             // 如果出错,恢复到默认图标
 | ||||||
|  |             this.switchToDefaultIcon(); | ||||||
|             this.logMessage('无法访问麦克风: ' + error.message, 'error'); |             this.logMessage('无法访问麦克风: ' + error.message, 'error'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     stopCall() { |     stopCall() { | ||||||
|  |         // 恢复到默认图标
 | ||||||
|  |         this.switchToDefaultIcon(); | ||||||
|  |          | ||||||
|  |         // 发送用户关闭连接事件到后端
 | ||||||
|  |         if (this.socket && this.socket.connected) { | ||||||
|  |             this.socket.emit('user-disconnect'); | ||||||
|  |         } | ||||||
|  |          | ||||||
|         // 停止音频处理器
 |         // 停止音频处理器
 | ||||||
|         if (this.audioProcessor) { |         if (this.audioProcessor) { | ||||||
|             this.audioProcessor.stopRecording(); |             this.audioProcessor.stopRecording(); | ||||||
| @ -855,23 +884,59 @@ class WebRTCChat { | |||||||
|             this.peerConnection = null; |             this.peerConnection = null; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         // 停止当前视频流
 |         // 直接刷新页面清除所有缓存
 | ||||||
|  |         console.log('通话已结束,正在刷新页面清除缓存...'); | ||||||
|  |         window.location.reload(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 清除视频缓存的方法
 | ||||||
|  |     clearVideoCache() { | ||||||
|  |         // 清除视频元素缓存
 | ||||||
|  |         if (this.recordedVideo) { | ||||||
|  |             this.recordedVideo.src = ''; | ||||||
|  |             this.recordedVideo.srcObject = null; | ||||||
|  |             this.recordedVideo.load(); // 重新加载空视频
 | ||||||
|  |             // 暂停视频播放
 | ||||||
|  |             this.recordedVideo.pause(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (this.recordedVideoBuffer) { | ||||||
|  |             this.recordedVideoBuffer.src = ''; | ||||||
|  |             this.recordedVideoBuffer.srcObject = null; | ||||||
|  |             this.recordedVideoBuffer.load(); | ||||||
|  |             this.recordedVideoBuffer.pause(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // 清除视频流缓存
 | ||||||
|         if (this.currentVideoStream) { |         if (this.currentVideoStream) { | ||||||
|             this.currentVideoStream.getTracks().forEach(track => track.stop()); |             this.currentVideoStream.getTracks().forEach(track => track.stop()); | ||||||
|             this.currentVideoStream = null; |             this.currentVideoStream = null; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         this.recordedVideo.srcObject = null; |         // 清除视频流映射缓存
 | ||||||
|  |         this.videoStreams.clear(); | ||||||
|  |          | ||||||
|  |         // 重置视频相关状态
 | ||||||
|         this.currentVideo = null; |         this.currentVideo = null; | ||||||
|  |         this.activeVideoElement = 'main'; | ||||||
|  |         this.videoSender = null; | ||||||
|          |          | ||||||
|         this.startButton.disabled = false; |         this.logMessage('视频缓存已清除,视频已停止', 'info'); | ||||||
|         this.stopButton.disabled = true; |     } | ||||||
| 
 | 
 | ||||||
|         // 隐藏结束通话按钮
 |     // 添加清除API缓存的方法
 | ||||||
|         this.stopButton.classList.remove('show'); |     async clearApiCache() { | ||||||
|  |         // 重新加载视频映射
 | ||||||
|  |         this.videoMapping = {}; | ||||||
|  |         await this.loadVideoMapping(); | ||||||
|          |          | ||||||
|         this.updateAudioStatus('未连接', 'disconnected'); |         // 重新加载默认视频
 | ||||||
|         this.logMessage('音频通话已结束,所有资源已释放', 'info'); |         await this.loadDefaultVideo(); | ||||||
|  |          | ||||||
|  |         // 重新加载视频列表
 | ||||||
|  |         await this.loadVideoList(); | ||||||
|  |          | ||||||
|  |         this.logMessage('API缓存已清除并重新加载', 'info'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async createPeerConnection() { |     async createPeerConnection() { | ||||||
| @ -1041,16 +1106,12 @@ class WebRTCChat { | |||||||
| 
 | 
 | ||||||
|     // 修改:使用音频处理器的语音录制功能
 |     // 修改:使用音频处理器的语音录制功能
 | ||||||
|     async startVoiceRecording() { |     async startVoiceRecording() { | ||||||
|         const success = await this.audioProcessor.startRecording(); |         const success = await this.audioProcessor.startRecording(this.localStream); | ||||||
|          |          | ||||||
|         if (success) { |         if (success) { | ||||||
|             // this.startVoiceButton.disabled = true;
 |  | ||||||
|             // this.stopVoiceButton.disabled = false;
 |  | ||||||
|             // this.startVoiceButton.classList.add('recording');
 |  | ||||||
|             // this.voiceStatus.textContent = '等待语音输入...';
 |  | ||||||
|             this.logMessage('高级语音录制已启动', 'success'); |             this.logMessage('高级语音录制已启动', 'success'); | ||||||
|         } else { |         } else { | ||||||
|             // this.voiceStatus.textContent = '录音启动失败';
 |             this.logMessage('录音启动失败', 'error'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1155,6 +1216,34 @@ class WebRTCChat { | |||||||
|         return status; |         return status; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // 添加图标切换方法
 | ||||||
|  |     switchToCallingIcon() { | ||||||
|  |         const callIcon = document.getElementById('callIcon'); | ||||||
|  |         const callingIcon = document.getElementById('callingIcon'); | ||||||
|  |         const startButton = this.startButton; | ||||||
|  |          | ||||||
|  |         if (callIcon && callingIcon && startButton) { | ||||||
|  |             callIcon.style.display = 'none'; | ||||||
|  |             callingIcon.style.display = 'block'; | ||||||
|  |             startButton.classList.add('calling'); | ||||||
|  |             startButton.title = '通话中...'; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     switchToDefaultIcon() { | ||||||
|  |         const callIcon = document.getElementById('callIcon'); | ||||||
|  |         const callingIcon = document.getElementById('callingIcon'); | ||||||
|  |         const startButton = this.startButton; | ||||||
|  |          | ||||||
|  |         if (callIcon && callingIcon && startButton) { | ||||||
|  |             callIcon.style.display = 'block'; | ||||||
|  |             callingIcon.style.display = 'none'; | ||||||
|  |             startButton.classList.remove('calling'); | ||||||
|  |             startButton.title = '开始通话'; | ||||||
|  |             startButton.disabled = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async testAllVideoFiles() { |     async testAllVideoFiles() { | ||||||
|         this.logMessage('开始测试所有视频文件...', 'info'); |         this.logMessage('开始测试所有视频文件...', 'info'); | ||||||
|          |          | ||||||
|  | |||||||
| @ -65,19 +65,19 @@ 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 = '8-4-sh' | ||||||
|       const targetVideo = 'd-0.mp4' |       const targetVideo = window.webrtcApp.interactionVideo | ||||||
|       // 如果是第一个音频片段,触发视频切换
 |       // 如果是第一个音频片段,触发视频切换
 | ||||||
|       // if (isFirstChunk && sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.switchVideoWithReplaceTrack) {
 |       if (isFirstChunk && sayName != window.webrtcApp.currentVideoTag && window.webrtcApp && window.webrtcApp.switchVideoWithReplaceTrack) { | ||||||
|       //   try {
 |         try { | ||||||
|       //     console.log('--------------触发视频切换:', sayName);
 |           console.log('--------------触发视频切换:', sayName); | ||||||
|       //     window.webrtcApp.switchVideoWithReplaceTrack(targetVideo, 'audio', 'say-5s-m-sw');
 |           window.webrtcApp.switchVideoWithReplaceTrack(targetVideo, 'audio', '8-4-sh'); | ||||||
|       //     isFirstChunk = false;
 |           isFirstChunk = false; | ||||||
|       //     window.webrtcApp.currentVideoTag = sayName;
 |           window.webrtcApp.currentVideoTag = sayName; | ||||||
|       //   } catch (error) {
 |         } catch (error) { | ||||||
|       //     console.error('视频切换失败:', error);
 |           console.error('视频切换失败:', error); | ||||||
|       //   }
 |         } | ||||||
|       // }
 |       } | ||||||
|       await playAudioData(audioItem.audioData); |       await playAudioData(audioItem.audioData); | ||||||
|     } else { |     } else { | ||||||
|       // 等待一小段时间再检查
 |       // 等待一小段时间再检查
 | ||||||
| @ -89,11 +89,11 @@ async function processAudioQueue() { | |||||||
|   const text = 'default' |   const text = 'default' | ||||||
|   // await new Promise(resolve => setTimeout(resolve, 500));
 |   // await new Promise(resolve => setTimeout(resolve, 500));
 | ||||||
|   console.log("音频结束------------------------:", window.webrtcApp.currentVideoTag, isPlaying) |   console.log("音频结束------------------------:", window.webrtcApp.currentVideoTag, isPlaying) | ||||||
|   // if (window.webrtcApp.currentVideoTag != text) {
 |   if (window.webrtcApp.currentVideoTag != text) { | ||||||
|   //   isFirstChunk = true
 |     isFirstChunk = true | ||||||
|   //   window.webrtcApp.currentVideoTag = text
 |     window.webrtcApp.currentVideoTag = text | ||||||
|   //   window.webrtcApp.switchVideoWithReplaceTrack(window.webrtcApp.defaultVideo, 'audio', text);
 |     window.webrtcApp.switchVideoWithReplaceTrack(window.webrtcApp.defaultVideo, 'audio', text); | ||||||
|   // }
 |   } | ||||||
|   console.log('音频队列处理完成'); |   console.log('音频队列处理完成'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								videos/0-2.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/0-2.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/0.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/0.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/1-m.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/1-m.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/4-m.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/4-m.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/5.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/5.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/6.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/6.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-bd-1.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-bd-1.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-bd-2.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-bd-2.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-bd.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-bd.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-hc-bd.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-hc-bd.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-hc-sh.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-hc-sh.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-kc-bd.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-kc-bd.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-kc-sh.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-kc-sh.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/8-4-sh.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/8-4-sh.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/bd-1.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/bd-1.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/chang.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/chang.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								videos/d-0.mp4
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								videos/d-0.mp4
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Song367
						Song367