From 4402c1137a7025d286853738f7808563ac8b2401 Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Sun, 31 Aug 2025 15:14:06 +0800 Subject: [PATCH] 'commit' --- dsLightRag/static/YunXiao/xueban.js | 147 ++++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 18 deletions(-) diff --git a/dsLightRag/static/YunXiao/xueban.js b/dsLightRag/static/YunXiao/xueban.js index b9d37b36..b3e9112f 100644 --- a/dsLightRag/static/YunXiao/xueban.js +++ b/dsLightRag/static/YunXiao/xueban.js @@ -17,7 +17,11 @@ const AudioState = { audioChunks: [], // 存储接收到的音频块 audioQueue: [], // 音频队列,用于流式播放 isStreamPlaying: false, // 是否正在流式播放 - currentAudioIndex: 0 // 当前播放的音频索引 + currentAudioIndex: 0, // 当前播放的音频索引 + streamAudioElement: null, // 流式播放音频元素 + totalPlayedTime: 0, // 新增:累计播放时间 + estimatedTotalDuration: 0, // 新增:估算总时长 + currentChunkDuration: 0 // 新增:当前块时长 }, websocket: { connection: null, @@ -56,7 +60,7 @@ const UIController = { } }, - // 更新按钮状态 + // 更新录音按钮状态 updateRecordingButtons(isRecording) { this.toggleElement('recordingIndicator', isRecording); this.toggleElement('startRecordBtn', !isRecording); @@ -92,12 +96,46 @@ const UIController = { } }, + // 添加总进度更新方法 + updateTotalProgress() { + // 根据实际业务逻辑计算总进度 + const totalProgress = AudioState.playback.totalPlayedChunks / AudioState.playback.totalChunks * 100; + this.updateProgress(totalProgress); + }, + // 更新时间显示 updateTimeDisplay(currentTime, duration) { const timeDisplay = document.getElementById('audioTime'); if (timeDisplay) { timeDisplay.textContent = `${Utils.formatTime(currentTime)} / ${Utils.formatTime(duration)}`; } + }, + + // 显示错误通知 + showErrorNotification(message) { + // 创建或显示错误通知元素 + let notification = document.getElementById('audioErrorNotification'); + if (!notification) { + notification = document.createElement('div'); + notification.id = 'audioErrorNotification'; + notification.style.position = 'fixed'; + notification.style.bottom = '20px'; + notification.style.left = '50%'; + notification.style.transform = 'translateX(-50%)'; + notification.style.backgroundColor = 'rgba(255, 0, 0, 0.8)'; + notification.style.color = 'white'; + notification.style.padding = '10px 20px'; + notification.style.borderRadius = '4px'; + notification.style.zIndex = '1000'; + document.body.appendChild(notification); + } + notification.textContent = message; + notification.style.display = 'block'; + + // 3秒后自动隐藏 + setTimeout(() => { + notification.style.display = 'none'; + }, 3000); } }; @@ -125,9 +163,15 @@ const WebSocketManager = { }; // 连接关闭 - AudioState.websocket.connection.onclose = () => { - console.log('WebSocket连接已关闭'); + AudioState.websocket.connection.onclose = (event) => { + console.log('WebSocket连接已关闭,代码:', event.code, '原因:', event.reason); AudioState.websocket.isConnected = false; + + // 添加自动重连逻辑 + if (!AudioState.websocket.isClosing) { + console.log('尝试重新连接WebSocket...'); + setTimeout(() => WebSocketManager.initConnection(), 3000); + } }; // 连接错误 @@ -386,18 +430,40 @@ const AudioPlayer = { // 更新时间显示 updateTimeDisplay() { if (!AudioState.playback.audioElement) return; - + const currentTime = AudioState.playback.audioElement.currentTime; const duration = AudioState.playback.audioElement.duration; UIController.updateTimeDisplay(currentTime, duration); }, - - // 在AudioPlayer.initStreamPlayer方法中添加暂停事件监听 + + // 流式播放进度更新 - 新增方法 + updateStreamProgress() { + if (!AudioState.playback.streamAudioElement) return; + + // 计算当前音频块播放进度 + const currentTime = AudioState.playback.streamAudioElement.currentTime; + const duration = AudioState.playback.streamAudioElement.duration || AudioState.playback.currentChunkDuration; + + // 更新进度条 + const progress = (currentTime / duration) * 100; + UIController.updateProgress(progress); + + // 更新时间显示(累计已播放时间 + 当前块播放时间) + const totalPlayedTime = AudioState.playback.totalPlayedTime + currentTime; + UIController.updateTimeDisplay(totalPlayedTime, AudioState.playback.estimatedTotalDuration); + }, + // 初始化流式播放器 initStreamPlayer() { // 创建新的音频元素用于流式播放 if (!AudioState.playback.streamAudioElement) { AudioState.playback.streamAudioElement = new Audio(); + + // 添加暂停按钮点击事件绑定 + const playBtn = document.getElementById('playAudioBtn'); + if (playBtn) { + playBtn.onclick = () => AudioPlayer.togglePlay(); + } // 监听音频结束事件 AudioState.playback.streamAudioElement.addEventListener('ended', () => { @@ -424,6 +490,11 @@ const AudioPlayer = { UIController.updatePlayButton(false); // 更新按钮状态 this.processAudioQueue(); }); + + // 添加进度更新事件监听 - 新增代码 + AudioState.playback.streamAudioElement.addEventListener('timeupdate', () => { + this.updateStreamProgress(); + }); } }, @@ -449,15 +520,29 @@ const AudioPlayer = { const audioUrl = URL.createObjectURL(audioBlob); AudioState.playback.streamAudioElement.src = audioUrl; + // 添加当前音频块时长跟踪 + AudioState.playback.currentChunkStartTime = Date.now(); + AudioState.playback.currentChunkDuration = audioBlob.size / 1024 / 16; // 估算时长 + // 修改AudioPlayer.processAudioQueue方法中的播放部分 AudioState.playback.streamAudioElement.play() .then(() => { console.log('开始播放音频块'); // 关键修复:更新播放按钮状态为播放中 UIController.updatePlayButton(true); + + // 更新总进度 - 修改前:this.updateTotalProgress(); + UIController.updateTotalProgress(); // 修改为直接调用UIController方法 }) .catch(error => { console.error('播放音频块失败:', error); + // 添加失败后状态重置 + AudioState.playback.isStreamPlaying = false; + UIController.updatePlayButton(false); + + // 添加用户可见的错误提示 + UIController.showErrorNotification('音频播放失败,请重试'); + this.processAudioQueue(); }) .finally(() => { @@ -598,6 +683,8 @@ const RecordingManager = { setTimeout(() => { if (AudioState.recording.isRecording) { console.log('达到最大录音时长,自动停止录音'); + // 添加用户提示 + alert('已达到最大录音时长'); this.stopRecording(); } }, AudioState.recording.maxDuration); @@ -624,8 +711,16 @@ const RecordingManager = { // 等待WebSocket连接建立 waitForConnection() { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { + const maxRetries = 30; // 最多等待3秒 + let retries = 0; + const checkConnection = () => { + if (retries >= maxRetries) { + reject(new Error('WebSocket连接超时')); + return; + } + console.log('检查WebSocket连接状态:', AudioState.websocket.isConnected); if (AudioState.websocket.isConnected && AudioState.websocket.connection && @@ -634,6 +729,7 @@ const RecordingManager = { resolve(); } else { console.log('WebSocket连接未建立,等待...'); + retries++; setTimeout(checkConnection, 100); } }; @@ -706,23 +802,23 @@ function initializeApp() { // 初始化流式播放器 AudioPlayer.initStreamPlayer(); - // 检查DOM是否已就绪 + // 统一使用DOMContentLoaded事件绑定 if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - EventBinder.bindEvents(); - console.log('学伴录音功能初始化完成(DOMContentLoaded)'); - }); + document.addEventListener('DOMContentLoaded', bindEventsAndInitialize); } else { - // DOM已经加载完成,直接绑定事件 - EventBinder.bindEvents(); - console.log('学伴录音功能初始化完成(直接执行)'); + bindEventsAndInitialize(); } } +function bindEventsAndInitialize() { + EventBinder.bindEvents(); + console.log('学伴录音功能初始化完成'); +} + // 立即执行初始化 initializeApp(); -// 同时保留原有的DOMContentLoaded事件作为备用 +// 移除重复的DOMContentLoaded和load事件绑定 document.addEventListener('DOMContentLoaded', () => { EventBinder.bindEvents(); console.log('学伴录音功能备用初始化完成'); @@ -736,5 +832,20 @@ window.addEventListener('load', () => { // 页面关闭时关闭WebSocket连接 window.addEventListener('beforeunload', () => { - WebSocketManager.closeConnection(); + WebSocketManager.closeConnection(true); + + // 清理音频元素 + if (AudioState.playback.streamAudioElement) { + // 移除所有事件监听器 + const oldElement = AudioState.playback.streamAudioElement; + const newElement = new Audio(); + AudioState.playback.streamAudioElement = newElement; + oldElement.remove(); + } + + // 清理URL对象 + if (AudioState.playback.audioUrl) { + URL.revokeObjectURL(AudioState.playback.audioUrl); + AudioState.playback.audioUrl = null; + } });