Files
dsProject/dsLightRag/static/YunXiao/xueban.js
2025-09-01 07:02:21 +08:00

876 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 学伴录音功能核心逻辑
* 模块化组织录音管理、ASR处理、音频播放、UI控制
*/
// ==================== 全局状态管理 ====================
const AudioState = {
recording: {
mediaRecorder: null,
audioChunks: [],
isRecording: false,
maxDuration: 60000 // 60秒
},
playback: {
audioElement: null,
isPlaying: false,
audioChunks: [], // 存储接收到的音频块
audioQueue: [], // 音频队列,用于流式播放
isStreamPlaying: false, // 是否正在流式播放
currentAudioIndex: 0, // 当前播放的音频索引
streamAudioElement: null, // 流式播放音频元素
totalPlayedTime: 0, // 新增:累计播放时间
estimatedTotalDuration: 0, // 新增:估算总时长
currentChunkDuration: 0 // 新增:当前块时长
},
websocket: {
connection: null,
isConnected: false
}
};
// ==================== 工具函数 ====================
const Utils = {
// 格式化时间显示
formatTime(seconds) {
const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
const secs = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
},
// 将Blob转换为Base64
blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
};
// ==================== UI控制器 ====================
const UIController = {
// 显示/隐藏元素
toggleElement(elementId, show) {
const element = document.getElementById(elementId);
if (element) {
element.style.display = show ? 'flex' : 'none';
}
},
// 更新录音按钮状态
updateRecordingButtons(isRecording) {
this.toggleElement('recordingIndicator', isRecording);
this.toggleElement('startRecordBtn', !isRecording);
this.toggleElement('stopRecordBtn', isRecording);
},
// 禁用/启用帮我讲题按钮
setStartRecordButtonEnabled(enabled) {
const startBtn = document.getElementById('startRecordBtn');
if (startBtn) {
startBtn.disabled = !enabled;
startBtn.style.opacity = enabled ? '1' : '0.5';
startBtn.style.cursor = enabled ? 'pointer' : 'not-allowed';
}
},
// 更新播放按钮图标
updatePlayButton(isPlaying) {
const btn = document.getElementById('playAudioBtn');
if (!btn) return;
const playIcon = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M8 5V19L19 12L8 5Z" fill="white"/></svg>';
const pauseIcon = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/></svg>';
btn.innerHTML = isPlaying ? pauseIcon : playIcon;
},
// 更新进度条
updateProgress(progress) {
const progressBar = document.getElementById('progressBar');
if (progressBar) {
progressBar.style.width = `${progress}%`;
}
},
// 添加总进度更新方法
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);
}
};
// ==================== WebSocket管理模块 ====================
const WebSocketManager = {
// 初始化WebSocket连接
initConnection() {
console.log('初始化WebSocket连接');
if (AudioState.websocket.connection &&
AudioState.websocket.connection.readyState === WebSocket.OPEN) {
console.log('WebSocket连接已存在');
return;
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/xueban/streaming-chat`;
console.log('正在建立WebSocket连接:', wsUrl);
AudioState.websocket.connection = new WebSocket(wsUrl);
// 连接打开
AudioState.websocket.connection.onopen = () => {
console.log('WebSocket连接已建立');
AudioState.websocket.isConnected = true;
};
// 连接关闭
AudioState.websocket.connection.onclose = (event) => {
console.log('WebSocket连接已关闭代码:', event.code, '原因:', event.reason);
AudioState.websocket.isConnected = false;
// 修改自动重连为用户确认
if (!AudioState.websocket.isClosing) {
console.log('WebSocket连接已关闭询问用户是否重试');
// 显示确认对话框,让用户决定是否重试
if (confirm('WebSocket连接已关闭是否重试连接')) {
WebSocketManager.initConnection();
}
}
};
// 连接错误
AudioState.websocket.connection.onerror = (error) => {
console.error('WebSocket连接错误:', error);
AudioState.websocket.isConnected = false;
UIController.toggleElement('thinkingIndicator', false);
UIController.setStartRecordButtonEnabled(true);
// 将alert改为confirm让用户决定是否重试
if (confirm('连接服务器失败,是否重试?')) {
WebSocketManager.initConnection();
}
};
// 接收消息
AudioState.websocket.connection.onmessage = (event) => {
console.log('收到WebSocket消息:', {
type: typeof event.data,
size: typeof event.data === 'string' ? event.data.length : event.data.size
});
this.handleMessage(event);
};
},
// 处理接收到的消息
async handleMessage(event) {
// 检查消息类型
if (typeof event.data === 'string') {
// JSON消息
try {
const data = JSON.parse(event.data);
console.log('解析JSON消息成功:', data);
switch (data.type) {
case 'asr_result':
// 显示ASR识别结果
console.log('收到ASR结果:', data.text);
const asrTextElement = document.getElementById('asrResultText');
if (asrTextElement) {
asrTextElement.textContent = data.text || '未识别到内容';
}
break;
case 'end':
// 处理结束
console.log('流式处理完成');
console.log('当前音频块数量:', AudioState.playback.audioChunks.length);
UIController.toggleElement('thinkingIndicator', false);
UIController.setStartRecordButtonEnabled(true);
// 标记流式播放结束
AudioState.playback.isStreamPlaying = false;
// 如果有音频数据但尚未开始播放,则开始播放
if (AudioState.playback.audioQueue.length > 0 && !AudioState.playback.isPlaying) {
console.log('开始播放队列中的音频');
AudioPlayer.processAudioQueue();
}
break;
case 'error':
// 错误处理
console.error('收到错误消息:', data.message);
UIController.toggleElement('thinkingIndicator', false);
UIController.setStartRecordButtonEnabled(true);
// 重置流式播放状态
AudioState.playback.isStreamPlaying = false;
AudioState.playback.audioQueue = [];
alert('处理失败: ' + data.message);
break;
default:
console.log('未知消息类型:', data.type);
}
} catch (e) {
console.error('解析JSON消息失败:', e);
console.error('原始消息内容:', event.data);
}
} else {
// 修改WebSocketManager.handleMessage方法约第210-225行
// 二进制音频数据
console.log('收到音频数据,大小:', event.data.size);
console.log('音频数据类型:', event.data.type);
// 保存到原始音频块数组
AudioState.playback.audioChunks.push(event.data);
// 添加到音频队列(用于流式播放)
AudioState.playback.audioQueue.push(event.data);
console.log('当前音频队列长度:', AudioState.playback.audioQueue.length);
// 显示播放界面
UIController.toggleElement('resultContainer', true);
// 关键修复:立即开始处理音频队列,实现流式播放
if (!AudioState.playback.isStreamPlaying) {
AudioState.playback.isStreamPlaying = true;
AudioPlayer.processAudioQueue();
}
return;
}
},
// 合并所有音频块并播放
combineAndPlayAudio() {
try {
console.log('开始合并音频块,数量:', AudioState.playback.audioChunks.length);
// 创建一个新的Blob包含所有音频块
const combinedBlob = new Blob(AudioState.playback.audioChunks, { type: 'audio/wav' });
console.log('合并后的Blob大小:', combinedBlob.size);
// 创建音频URL
const audioUrl = URL.createObjectURL(combinedBlob);
console.log('创建音频URL:', audioUrl);
// 初始化音频播放器
AudioPlayer.initPlayer(audioUrl);
} catch (error) {
console.error('合并和播放音频失败:', error);
}
},
// 关闭WebSocket连接
closeConnection() {
if (AudioState.websocket.connection) {
AudioState.websocket.connection.close();
AudioState.websocket.connection = null;
AudioState.websocket.isConnected = false;
console.log('WebSocket连接已关闭');
}
}
};
// ==================== 音频播放模块 ====================
const AudioPlayer = {
// 初始化音频播放器
initPlayer(audioUrl) {
console.log('AudioPlayer.initPlayer 被调用音频URL:', audioUrl);
// 停止当前播放的音频
if (AudioState.playback.audioElement) {
console.log('停止当前播放的音频');
AudioState.playback.audioElement.pause();
}
// 创建新的音频元素
console.log('创建新的音频元素');
AudioState.playback.audioElement = new Audio(audioUrl);
AudioState.playback.isPlaying = false;
// 绑定音频事件
AudioState.playback.audioElement.onloadedmetadata = () => {
console.log('音频元数据加载完成');
this.updateTimeDisplay();
this.play(); // 自动播放
};
AudioState.playback.audioElement.onplay = () => {
console.log('音频开始播放');
};
AudioState.playback.audioElement.onpause = () => {
console.log('音频暂停');
};
AudioState.playback.audioElement.ontimeupdate = () => {
this.updateProgress();
this.updateTimeDisplay();
};
AudioState.playback.audioElement.onended = () => {
console.log('音频播放结束');
AudioState.playback.isPlaying = false;
UIController.updatePlayButton(false);
};
AudioState.playback.audioElement.onerror = (error) => {
console.error('音频播放错误:', error);
};
// 绑定播放按钮点击事件
const playBtn = document.getElementById('playAudioBtn');
if (playBtn) {
playBtn.onclick = () => this.togglePlay();
}
// 绑定进度条点击事件
const progressContainer = document.getElementById('audioProgress');
if (progressContainer) {
progressContainer.onclick = (e) => {
const rect = progressContainer.getBoundingClientRect();
const clickPosition = (e.clientX - rect.left) / rect.width;
AudioState.playback.audioElement.currentTime = clickPosition * AudioState.playback.audioElement.duration;
};
}
},
// 播放/暂停切换
// 修改AudioPlayer.togglePlay方法
// 播放/暂停切换
togglePlay() {
// 首先检查是否在流式播放
if (AudioState.playback.isStreamPlaying && AudioState.playback.streamAudioElement) {
// 流式播放模式
if (AudioState.playback.streamAudioElement.paused) {
// 如果当前是暂停状态,播放
AudioState.playback.streamAudioElement.play();
UIController.updatePlayButton(true);
} else {
// 如果当前正在播放,暂停
AudioState.playback.streamAudioElement.pause();
UIController.updatePlayButton(false);
}
return;
}
// 常规播放模式
if (!AudioState.playback.audioElement) return;
if (AudioState.playback.isPlaying) {
this.pause();
} else {
this.play();
}
},
// 播放
play() {
if (!AudioState.playback.audioElement) return;
try {
AudioState.playback.audioElement.play();
AudioState.playback.isPlaying = true;
UIController.updatePlayButton(true);
} catch (e) {
console.error('播放失败:', e);
}
},
// 暂停
pause() {
if (!AudioState.playback.audioElement) return;
AudioState.playback.audioElement.pause();
AudioState.playback.isPlaying = false;
UIController.updatePlayButton(false);
},
// 更新进度条
updateProgress() {
if (!AudioState.playback.audioElement) return;
const progress = (AudioState.playback.audioElement.currentTime / AudioState.playback.audioElement.duration) * 100;
UIController.updateProgress(progress);
},
// 更新时间显示
updateTimeDisplay() {
if (!AudioState.playback.audioElement) return;
const currentTime = AudioState.playback.audioElement.currentTime;
const duration = AudioState.playback.audioElement.duration;
UIController.updateTimeDisplay(currentTime, duration);
},
// 流式播放进度更新 - 新增方法
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', () => {
// 当前音频播放完毕,处理队列中的下一个音频
UIController.updatePlayButton(false); // 更新按钮状态
this.processAudioQueue();
});
// 监听暂停事件
AudioState.playback.streamAudioElement.addEventListener('pause', () => {
// 当音频暂停时,更新按钮状态
UIController.updatePlayButton(false);
});
// 监听播放事件
AudioState.playback.streamAudioElement.addEventListener('play', () => {
// 当音频开始播放时,更新按钮状态
UIController.updatePlayButton(true);
});
// 监听错误事件
AudioState.playback.streamAudioElement.addEventListener('error', (e) => {
console.error('流式播放音频错误:', e);
UIController.updatePlayButton(false); // 更新按钮状态
this.processAudioQueue();
});
// 添加进度更新事件监听 - 新增代码
AudioState.playback.streamAudioElement.addEventListener('timeupdate', () => {
this.updateStreamProgress();
});
}
},
// 处理音频队列
processAudioQueue() {
// 在AudioPlayer.processAudioQueue方法中修改队列为空时的处理
// 如果队列为空,则返回
if (AudioState.playback.audioQueue.length === 0) {
AudioState.playback.isStreamPlaying = false;
console.log('音频队列为空,停止流式播放');
// 隐藏播放界面 - 这是新增的代码
UIController.toggleElement('audioPlayer', false);
// 新增:音频播放结束,启用帮我讲题按钮
UIController.setStartRecordButtonEnabled(true);
return;
}
// 从队列中取出第一个音频块
const audioBlob = AudioState.playback.audioQueue.shift();
console.log('从队列取出音频块,剩余队列长度:', AudioState.playback.audioQueue.length);
// 创建音频URL并设置为源
const audioUrl = URL.createObjectURL(audioBlob);
AudioState.playback.streamAudioElement.src = audioUrl;
// 添加当前音频块时长跟踪
AudioState.playback.currentChunkStartTime = Date.now();
AudioState.playback.currentChunkDuration = audioBlob.size / 1024 / 16; // 估算时长
// 新增:音频开始播放前,禁用帮我讲题按钮
UIController.setStartRecordButtonEnabled(false);
// 修改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(() => {
// 播放完成后释放URL对象
setTimeout(() => {
URL.revokeObjectURL(audioUrl);
}, 1000);
});
}
};
// ==================== 事件绑定模块 ====================
const EventBinder = {
// 绑定所有事件
bindEvents() {
// 绑定录音按钮事件
const startBtn = document.getElementById('startRecordBtn');
const stopBtn = document.getElementById('stopRecordBtn');
console.log('开始绑定事件,查找按钮元素...');
console.log('开始录音按钮:', startBtn);
console.log('停止录音按钮:', stopBtn);
if (startBtn) {
startBtn.onclick = () => {
console.log('点击开始录音按钮');
RecordingManager.startRecording();
};
console.log('已绑定开始录音按钮事件');
} else {
console.error('未找到开始录音按钮');
}
if (stopBtn) {
stopBtn.onclick = () => {
console.log('点击停止录音按钮');
RecordingManager.stopRecording();
};
console.log('已绑定停止录音按钮事件');
} else {
console.error('未找到停止录音按钮');
}
}
};
// ==================== 录音管理模块 ====================
const RecordingManager = {
// 初始化录音
async initRecording() {
try {
// 获取用户媒体设备
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 创建媒体录制器
AudioState.recording.mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm;codecs=opus'
});
// 监听数据可用事件
AudioState.recording.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
AudioState.recording.audioChunks.push(event.data);
}
};
// 监听停止事件
AudioState.recording.mediaRecorder.onstop = async () => {
console.log('录音停止,开始处理音频数据');
// 创建音频Blob
const audioBlob = new Blob(AudioState.recording.audioChunks, { type: 'audio/webm' });
console.log('录音Blob大小:', audioBlob.size);
// 更新UI
UIController.toggleElement('thinkingIndicator', true);
// 初始化WebSocket连接
WebSocketManager.initConnection();
// 等待连接建立
await this.waitForConnection();
// 发送音频数据 - 这里是错误的调用
// const success = await WebSocketManager.sendAudio(audioBlob);
// 修复后的正确调用
const success = await RecordingManager.sendAudio(audioBlob);
if (!success) {
console.error('发送音频数据失败');
UIController.toggleElement('thinkingIndicator', false);
UIController.setStartRecordButtonEnabled(true);
}
// 清空音频块
AudioState.recording.audioChunks = [];
// 停止所有音频轨道
stream.getTracks().forEach(track => track.stop());
};
console.log('录音初始化成功');
return true;
} catch (error) {
console.error('录音初始化失败:', error);
alert('录音初始化失败,请授予麦克风权限后重试');
return false;
}
},
// 开始录音
async startRecording() {
console.log('开始录音');
// 新增强制关闭并重置现有WebSocket连接
if (AudioState.websocket.connection) {
WebSocketManager.closeConnection();
}
// 检查是否已经在录音
if (AudioState.recording.isRecording) {
console.warn('已经在录音中');
return;
}
// 初始化录音
const initialized = await this.initRecording();
if (!initialized) {
console.error('录音初始化失败,无法开始录音');
// 新增:初始化失败时重新启用按钮
UIController.setStartRecordButtonEnabled(true);
return;
}
// 开始录音
AudioState.recording.isRecording = true;
AudioState.recording.mediaRecorder.start();
// 更新UI
UIController.updateRecordingButtons(true);
console.log('录音开始成功');
// 设置最大录音时长
setTimeout(() => {
if (AudioState.recording.isRecording) {
console.log('达到最大录音时长,自动停止录音');
// 添加用户提示
alert('已达到最大录音时长');
this.stopRecording();
}
}, AudioState.recording.maxDuration);
},
// 停止录音
stopRecording() {
console.log('停止录音');
if (!AudioState.recording.isRecording || !AudioState.recording.mediaRecorder) {
console.warn('当前没有在录音');
return;
}
// 停止录音
AudioState.recording.mediaRecorder.stop();
AudioState.recording.isRecording = false;
// 更新UI
UIController.updateRecordingButtons(false);
console.log('录音停止命令已发送');
},
// 等待WebSocket连接建立
waitForConnection() {
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 &&
AudioState.websocket.connection.readyState === WebSocket.OPEN) {
console.log('WebSocket连接已建立可以发送数据');
resolve();
} else {
console.log('WebSocket连接未建立等待...');
retries++;
setTimeout(checkConnection, 100);
}
};
checkConnection();
});
},
// 发送音频数据
async sendAudio(audioBlob) {
console.log('=== 开始执行sendAudio方法 ===');
// 参数验证
if (!audioBlob) {
console.error('sendAudio方法参数错误: audioBlob为空');
return false;
}
// 新增确保WebSocket连接已建立最多等待1秒
let connectionAttempts = 0;
const maxAttempts = 10;
while (connectionAttempts < maxAttempts &&
(!AudioState.websocket.isConnected ||
!AudioState.websocket.connection ||
AudioState.websocket.connection.readyState !== WebSocket.OPEN)) {
console.log('等待WebSocket连接建立...', connectionAttempts);
connectionAttempts++;
await new Promise(resolve => setTimeout(resolve, 100));
}
// 连接状态检查
console.log('WebSocket连接状态:', {
isConnected: AudioState.websocket.isConnected,
connectionExists: !!AudioState.websocket.connection,
readyState: AudioState.websocket.connection ? AudioState.websocket.connection.readyState : 'N/A'
});
if (!AudioState.websocket.isConnected ||
!AudioState.websocket.connection ||
AudioState.websocket.connection.readyState !== WebSocket.OPEN) {
console.error('WebSocket连接未建立无法发送音频数据');
return false;
}
try {
console.log('将音频数据转换为Base64');
// 将音频数据转换为Base64
const base64Audio = await Utils.blobToBase64(audioBlob);
console.log('音频数据Base64长度:', base64Audio.length);
const payload = {
audio_data: base64Audio
};
console.log('准备发送的载荷:', {
keys: Object.keys(payload),
audioDataLength: payload.audio_data.length
});
// 发送音频数据
console.log('发送音频数据到WebSocket');
AudioState.websocket.connection.send(JSON.stringify(payload));
console.log('=== 音频数据发送成功 ===');
return true;
} catch (error) {
console.error('发送音频数据失败:', error);
return false;
}
}
};
// ==================== 初始化 ====================
function initializeApp() {
console.log('开始初始化学伴录音功能...');
// 初始化流式播放器
AudioPlayer.initStreamPlayer();
// 统一使用DOMContentLoaded事件绑定
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bindEventsAndInitialize);
} else {
bindEventsAndInitialize();
}
}
function bindEventsAndInitialize() {
EventBinder.bindEvents();
console.log('学伴录音功能初始化完成');
}
// 立即执行初始化
initializeApp();
// 移除重复的DOMContentLoaded和load事件绑定
document.addEventListener('DOMContentLoaded', () => {
EventBinder.bindEvents();
console.log('学伴录音功能备用初始化完成');
});
// 页面加载完成后也尝试绑定(确保万无一失)
window.addEventListener('load', () => {
EventBinder.bindEvents();
console.log('学伴录音功能load事件初始化完成');
});
// 页面关闭时关闭WebSocket连接
window.addEventListener('beforeunload', () => {
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;
}
});