diff --git a/server.js b/server.js
index ca263df..707527c 100644
--- a/server.js
+++ b/server.js
@@ -85,10 +85,64 @@ app.delete('/api/messages/clear', async (req, res) => {
// 存储连接的客户端和他们的视频流状态
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 = {
// '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-m-e': '4.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秒后回到默认视频
// 获取视频列表
@@ -115,13 +169,14 @@ app.get('/api/videos', (req, res) => {
// 获取视频映射
app.get('/api/video-mapping', (req, res) => {
+ // videoMapping = getCurrentScene()
res.json({ mapping: videoMapping });
});
// 获取默认视频
app.get('/api/default-video', (req, res) => {
res.json({
- defaultVideo: DEFAULT_VIDEO,
+ defaultVideo: getDefaultVideo(),
autoLoop: true
});
});
@@ -131,7 +186,7 @@ io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
connectedClients.set(socket.id, {
socket: socket,
- currentVideo: DEFAULT_VIDEO,
+ currentVideo: getDefaultVideo(),
isInInteraction: false
});
@@ -185,12 +240,12 @@ io.on('connection', (socket) => {
setTimeout(() => {
console.log(`交互超时,用户 ${socket.id} 回到默认视频`);
if (client) {
- client.currentVideo = DEFAULT_VIDEO;
+ client.currentVideo = getDefaultVideo();
client.isInInteraction = false;
}
// 广播回到默认视频的指令
io.emit('video-stream-switched', {
- videoFile: DEFAULT_VIDEO,
+ videoFile: getDefaultVideo(),
type: 'default',
from: socket.id
});
@@ -203,7 +258,7 @@ io.on('connection', (socket) => {
console.log('通话开始,用户:', socket.id);
const client = connectedClients.get(socket.id);
if (client) {
- client.currentVideo = DEFAULT_VIDEO;
+ client.currentVideo = getDefaultVideo();
client.isInInteraction = false;
}
io.emit('call-started', { from: socket.id });
@@ -262,15 +317,46 @@ io.on('connection', (socket) => {
console.log('用户请求回到默认视频:', socket.id);
const client = connectedClients.get(socket.id);
if (client) {
- client.currentVideo = DEFAULT_VIDEO;
+ client.currentVideo = getDefaultVideo();
client.isInInteraction = false;
}
socket.emit('switch-video-stream', {
- videoFile: DEFAULT_VIDEO,
+ videoFile: getDefaultVideo(),
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', () => {
console.log('用户断开连接:', socket.id);
diff --git a/src/audio_processor.js b/src/audio_processor.js
index e651a2d..853ed52 100644
--- a/src/audio_processor.js
+++ b/src/audio_processor.js
@@ -3,6 +3,7 @@
class AudioProcessor {
constructor(options = {}) {
this.audioContext = null;
+ this.stream = null; // 添加这一行
this.isRecording = false;
this.audioChunks = [];
@@ -311,22 +312,29 @@ class AudioProcessor {
}
// 开始录音
- async startRecording() {
+ async startRecording(existingStream = null) {
try {
- const stream = await navigator.mediaDevices.getUserMedia({
- audio: {
- sampleRate: 16000,
- channelCount: 1,
- echoCancellation: true,
- noiseSuppression: true
- }
- });
+ // 如果有外部提供的音频流,使用它;否则获取新的
+ if (existingStream) {
+ this.stream = existingStream;
+ console.log('使用外部提供的音频流');
+ } else {
+ this.stream = await navigator.mediaDevices.getUserMedia({
+ audio: {
+ sampleRate: 16000,
+ channelCount: 1,
+ echoCancellation: true,
+ noiseSuppression: true
+ }
+ });
+ console.log('获取新的音频流');
+ }
this.audioContext = new (window.AudioContext || window.webkitAudioContext)({
sampleRate: 16000
});
- const source = this.audioContext.createMediaStreamSource(stream);
+ const source = this.audioContext.createMediaStreamSource(this.stream);
const processor = this.audioContext.createScriptProcessor(4096, 1, 1);
processor.onaudioprocess = (event) => {
@@ -343,9 +351,13 @@ class AudioProcessor {
source.connect(processor);
processor.connect(this.audioContext.destination);
+ // 保存处理器引用以便后续清理
+ this.processor = processor;
+ this.source = source;
+
this.isRecording = true;
this.onStatusUpdate('等待语音输入...', 'ready');
-
+
// 在startRecording方法的最后添加
if (this.adaptiveThreshold && this.noiseCalibrationSamples.length === 0) {
this.onStatusUpdate('正在校准背景噪音,请保持安静...', 'calibrating');
@@ -364,6 +376,17 @@ class AudioProcessor {
stopRecording() {
console.log('开始停止录音...');
+ // 断开音频节点连接
+ if (this.source) {
+ this.source.disconnect();
+ this.source = null;
+ }
+
+ if (this.processor) {
+ this.processor.disconnect();
+ this.processor = null;
+ }
+
// 停止所有音频轨道
if (this.stream) {
this.stream.getTracks().forEach(track => {
@@ -400,6 +423,10 @@ class AudioProcessor {
this.consecutiveFramesCount = 0;
this.frameBuffer = [];
+ // 重置校准状态,确保下次启动时重新校准
+ this.noiseCalibrationSamples = [];
+ this.isCalibrated = false;
+
this.onStatusUpdate('录音已完全停止', 'stopped');
console.log('录音已完全停止,所有资源已释放');
}
diff --git a/src/index.html b/src/index.html
index 49d0af3..0809ed4 100644
--- a/src/index.html
+++ b/src/index.html
@@ -219,6 +219,67 @@
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 {
width: 24px;
height: 24px;
@@ -251,8 +312,8 @@
-
-
+
+
未连接
@@ -288,9 +349,22 @@