From 70e431eebc4a28d1c8a991f7b0ae9bd65e4957cf Mon Sep 17 00:00:00 2001
From: wang <3202024218@qq.com>
Date: Wed, 25 Jun 2025 00:23:27 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95=E6=92=AD?=
=?UTF-8?q?=E6=94=BEbug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
goldminer/backend/app.py | 26 ++-
.../node_modules/.cache/eslint/123ee647.json | 2 +-
.../frontend/src/components/ChatRoom.vue | 210 +++++++++++-------
3 files changed, 154 insertions(+), 84 deletions(-)
diff --git a/goldminer/backend/app.py b/goldminer/backend/app.py
index addddfdd..1622ebc5 100644
--- a/goldminer/backend/app.py
+++ b/goldminer/backend/app.py
@@ -675,16 +675,33 @@ def handle_send_audio_message(data):
room_id = data.get('room_id')
audio_data = data.get('audio_data') # Base64编码的音频数据
audio_duration = data.get('audio_duration', 0) # 音频持续时间(秒)
+ audio_mime_type = data.get('audio_mime_type', 'audio/webm') # 音频MIME类型
if not room_id or not audio_data:
logger.error("语音消息缺少必要参数: room_id或audio_data")
return
- # 检查audio_data是否为有效的base64编码数据
- if not audio_data.startswith('data:audio'):
- logger.warning(f"接收到非标准格式的音频数据,前缀: {audio_data[:20]}")
+ # 检查和修复音频数据格式
+ try:
+ # 确保音频数据有正确的MIME类型前缀
+ if not audio_data.startswith('data:audio'):
+ logger.warning(f"接收到非标准格式的音频数据,尝试修复")
+
+ # 如果数据是base64但没有前缀,添加前缀
+ if not audio_data.startswith('data:'):
+ # 从base64数据中移除可能存在的前缀
+ cleaned_data = audio_data.replace('data:audio/webm;base64,', '')
+ cleaned_data = cleaned_data.replace('data:audio/ogg;base64,', '')
+ cleaned_data = cleaned_data.replace('data:audio/wav;base64,', '')
+ cleaned_data = cleaned_data.replace('data:;base64,', '')
+
+ # 使用客户端提供的MIME类型
+ audio_data = f'data:{audio_mime_type};base64,{cleaned_data}'
+ logger.info(f"已修复音频数据格式,使用MIME类型: {audio_mime_type}")
+ except Exception as e:
+ logger.error(f"修复音频数据格式时出错: {str(e)}")
- logger.info(f"处理语音消息: 用户ID={user_id}, 房间ID={room_id}, 音频时长={audio_duration}秒, 数据长度={len(audio_data)}")
+ logger.info(f"处理语音消息: 用户ID={user_id}, 房间ID={room_id}, 音频时长={audio_duration}秒, MIME类型={audio_mime_type}, 数据长度={len(audio_data)}")
try:
# 获取用户和房间信息
@@ -716,6 +733,7 @@ def handle_send_audio_message(data):
'message': audio_data,
'message_type': 'audio',
'audio_duration': audio_duration,
+ 'audio_mime_type': audio_mime_type,
'created_at': message.created_at.isoformat()
}, room=str(room_id))
logger.info(f"语音消息已广播到房间: {room_id}")
diff --git a/goldminer/frontend/node_modules/.cache/eslint/123ee647.json b/goldminer/frontend/node_modules/.cache/eslint/123ee647.json
index 6f107444..e868a7aa 100644
--- a/goldminer/frontend/node_modules/.cache/eslint/123ee647.json
+++ b/goldminer/frontend/node_modules/.cache/eslint/123ee647.json
@@ -1 +1 @@
-[{"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\main.js":"1","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\App.vue":"2","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\router.js":"3","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Home.vue":"4","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Register.vue":"5","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Game.vue":"6","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Login.vue":"7","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\UserProfile.vue":"8","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\ChatRoom.vue":"9","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Leaderboard.vue":"10","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Header.vue":"11","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Admin.vue":"12","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\AdminSetup.vue":"13"},{"size":2697,"mtime":1750769523218,"results":"14","hashOfConfig":"15"},{"size":1004,"mtime":1750128639386,"results":"16","hashOfConfig":"15"},{"size":2623,"mtime":1750303858493,"results":"17","hashOfConfig":"15"},{"size":5735,"mtime":1750209720346,"results":"18","hashOfConfig":"15"},{"size":4492,"mtime":1750147385471,"results":"19","hashOfConfig":"15"},{"size":53697,"mtime":1750767242119,"results":"20","hashOfConfig":"15"},{"size":7100,"mtime":1750769571067,"results":"21","hashOfConfig":"15"},{"size":11652,"mtime":1750381631833,"results":"22","hashOfConfig":"15"},{"size":59225,"mtime":1750770950073,"results":"23","hashOfConfig":"15"},{"size":13225,"mtime":1750128639387,"results":"24","hashOfConfig":"15"},{"size":4886,"mtime":1750381631832,"results":"25","hashOfConfig":"15"},{"size":19436,"mtime":1750391010410,"results":"26","hashOfConfig":"15"},{"size":4836,"mtime":1750303858491,"results":"27","hashOfConfig":"15"},{"filePath":"28","messages":"29","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},"i331ru",{"filePath":"31","messages":"32","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"34","messages":"35","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"36","messages":"37","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"38","messages":"39","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"40","messages":"41","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"42","messages":"43","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"44","messages":"45","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"46","messages":"47","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"48","messages":"49","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"50","messages":"51","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"52","messages":"53","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"54","messages":"55","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\main.js",[],[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\App.vue",[],[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\router.js",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Home.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Register.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Game.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Login.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\UserProfile.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\ChatRoom.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Leaderboard.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Header.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Admin.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\AdminSetup.vue",[]]
\ No newline at end of file
+[{"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\main.js":"1","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\App.vue":"2","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\router.js":"3","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Home.vue":"4","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Register.vue":"5","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Game.vue":"6","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Login.vue":"7","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\UserProfile.vue":"8","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\ChatRoom.vue":"9","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Leaderboard.vue":"10","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Header.vue":"11","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Admin.vue":"12","D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\AdminSetup.vue":"13"},{"size":2697,"mtime":1750769523218,"results":"14","hashOfConfig":"15"},{"size":1004,"mtime":1750128639386,"results":"16","hashOfConfig":"15"},{"size":2623,"mtime":1750303858493,"results":"17","hashOfConfig":"15"},{"size":5735,"mtime":1750209720346,"results":"18","hashOfConfig":"15"},{"size":4492,"mtime":1750147385471,"results":"19","hashOfConfig":"15"},{"size":53697,"mtime":1750767242119,"results":"20","hashOfConfig":"15"},{"size":7100,"mtime":1750769571067,"results":"21","hashOfConfig":"15"},{"size":11652,"mtime":1750381631833,"results":"22","hashOfConfig":"15"},{"size":61600,"mtime":1750781985794,"results":"23","hashOfConfig":"15"},{"size":13225,"mtime":1750128639387,"results":"24","hashOfConfig":"15"},{"size":4886,"mtime":1750381631832,"results":"25","hashOfConfig":"15"},{"size":19436,"mtime":1750391010410,"results":"26","hashOfConfig":"15"},{"size":4836,"mtime":1750303858491,"results":"27","hashOfConfig":"15"},{"filePath":"28","messages":"29","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},"i331ru",{"filePath":"31","messages":"32","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"34","messages":"35","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"36","messages":"37","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"38","messages":"39","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"40","messages":"41","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"42","messages":"43","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"44","messages":"45","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"46","messages":"47","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"48","messages":"49","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"50","messages":"51","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"52","messages":"53","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},{"filePath":"54","messages":"55","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"33"},"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\main.js",[],[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\App.vue",[],[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\router.js",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Home.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Register.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Game.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Login.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\UserProfile.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\ChatRoom.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Leaderboard.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Header.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\Admin.vue",[],"D:\\学习\\网络应用程序开发\\pdf\\goldminer\\frontend\\src\\components\\AdminSetup.vue",[]]
\ No newline at end of file
diff --git a/goldminer/frontend/src/components/ChatRoom.vue b/goldminer/frontend/src/components/ChatRoom.vue
index 1cca7fae..be02ac88 100644
--- a/goldminer/frontend/src/components/ChatRoom.vue
+++ b/goldminer/frontend/src/components/ChatRoom.vue
@@ -194,16 +194,14 @@
/>
-
+
@@ -480,59 +478,8 @@ export default {
}
})
- // WebRTC语音聊天事件
- socket.on('voice_chat_event', (data) => {
- console.log('收到语音聊天事件:', data)
-
- const { type, user_id, username } = data
-
- switch (type) {
- case 'user_joined': {
- // 有新用户加入语音聊天
- addNotification(`${username} 加入了语音聊天`)
-
- // 将用户添加到参与者列表
- if (!rtcParticipants.value.find(p => p.id === user_id)) {
- rtcParticipants.value.push({
- id: user_id,
- username: username,
- muted: false
- })
- }
-
- // 如果自己在语音聊天中,发起连接
- if (rtcActive.value && localStream.value) {
- // 通知新用户有新的对等连接
- sendSignal(user_id, 'new_peer', {})
- }
- break
- }
-
- case 'user_left': {
- // 用户离开语音聊天
- addNotification(`${username} 离开了语音聊天`)
-
- // 从参与者列表中移除
- rtcParticipants.value = rtcParticipants.value.filter(p => p.id !== user_id)
-
- // 关闭与该用户的对等连接
- closePeerConnection(user_id)
- break
- }
-
- case 'mute_change': {
- // 用户改变静音状态
- const mutedStatus = data.muted
- const participant = rtcParticipants.value.find(p => p.id === user_id)
-
- if (participant) {
- participant.muted = mutedStatus
- addNotification(`${username} ${mutedStatus ? '已静音' : '已取消静音'}`)
- }
- break
- }
- }
- })
+
+
}
// 获取聊天室列表
@@ -646,10 +593,37 @@ export default {
}
})
- // 使用更高效的编码配置
- const options = {
- mimeType: 'audio/webm;codecs=opus',
- audioBitsPerSecond: 32000 // 降低比特率以减小文件大小
+ // 检测浏览器支持的音频格式
+ let options = {};
+ let mimeType = '';
+
+ // 按优先级检测支持的格式
+ if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {
+ mimeType = 'audio/webm;codecs=opus';
+ console.log('使用 audio/webm;codecs=opus 格式录音');
+ }
+ else if (MediaRecorder.isTypeSupported('audio/webm')) {
+ mimeType = 'audio/webm';
+ console.log('使用 audio/webm 格式录音');
+ }
+ else if (MediaRecorder.isTypeSupported('audio/ogg;codecs=opus')) {
+ mimeType = 'audio/ogg;codecs=opus';
+ console.log('使用 audio/ogg;codecs=opus 格式录音');
+ }
+ else if (MediaRecorder.isTypeSupported('audio/mp4')) {
+ mimeType = 'audio/mp4';
+ console.log('使用 audio/mp4 格式录音');
+ }
+ else {
+ console.log('使用默认音频格式录音');
+ }
+
+ // 设置录音选项
+ if (mimeType) {
+ options = {
+ mimeType: mimeType,
+ audioBitsPerSecond: 32000 // 降低比特率以减小文件大小
+ };
}
// 创建媒体录制器
@@ -682,7 +656,7 @@ export default {
console.log('录音实际时长:', actualDuration, '秒')
// 将录制的音频块转换为Blob
- const audioBlob = new Blob(audioChunks.value, { type: 'audio/webm' })
+ const audioBlob = new Blob(audioChunks.value, { type: mimeType || 'audio/webm' })
// 添加本地预览消息,减少感知延迟
const tempId = 'temp-' + Date.now()
@@ -706,12 +680,14 @@ export default {
reader.readAsDataURL(audioBlob)
reader.onloadend = () => {
const base64Audio = reader.result
+ console.log('音频格式:', mimeType || 'unknown', '数据前缀:', base64Audio.substring(0, 50))
// 发送语音消息
socket.emit('send_audio_message', {
room_id: currentRoom.value.id,
audio_data: base64Audio,
- audio_duration: actualDuration
+ audio_duration: actualDuration,
+ audio_mime_type: mimeType || 'audio/webm'
})
// 重置录音状态
@@ -744,6 +720,15 @@ export default {
}
}
+ // 切换录音状态
+ const toggleRecording = () => {
+ if (isRecording.value) {
+ stopRecording()
+ } else {
+ startRecording()
+ }
+ }
+
// 停止录音
const stopRecording = () => {
if (!mediaRecorder.value || mediaRecorder.value.state === 'inactive') {
@@ -799,24 +784,53 @@ export default {
console.log('准备播放语音消息:', message.id, '类型:', typeof message.message);
// 确保音频源是有效的
+ let audioSource = null;
+ const mimeType = message.audio_mime_type || 'audio/webm';
+
if (message.localAudioUrl && message.localAudioUrl.startsWith('blob:')) {
// 优先使用本地缓存的Blob URL
- audioPlayer.value.src = message.localAudioUrl;
+ audioSource = message.localAudioUrl;
console.log('使用本地缓存的Blob URL播放');
} else if (message.message.startsWith('blob:')) {
// 本地消息使用Blob URL
- audioPlayer.value.src = message.message;
+ audioSource = message.message;
console.log('使用Blob URL播放:', message.message);
} else if (message.message.startsWith('data:audio')) {
// 服务器返回的base64音频数据
- audioPlayer.value.src = message.message;
+ audioSource = message.message;
console.log('使用base64数据播放');
} else {
// 尝试将消息转换为有效的音频源
- console.warn('未知格式的音频消息,尝试直接使用:', message.message.substring(0, 50) + '...');
- audioPlayer.value.src = message.message;
+ console.warn('未知格式的音频消息,尝试修复格式');
+
+ // 尝试修复可能的格式问题
+ try {
+ // 检查是否是有效的base64数据但缺少头部
+ if (typeof message.message === 'string') {
+ // 尝试检测是否为base64编码
+ const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
+ const cleanedData = message.message.replace(/^data:.*?;base64,/, '');
+
+ if (base64Regex.test(cleanedData)) {
+ // 添加适当的MIME类型头
+ audioSource = `data:${mimeType};base64,` + cleanedData;
+ console.log(`修复为带头部的base64数据,使用MIME类型: ${mimeType}`);
+ } else {
+ // 可能不是base64,直接使用
+ audioSource = message.message;
+ }
+ } else {
+ audioSource = message.message;
+ }
+ } catch (formatErr) {
+ console.error('修复音频格式失败:', formatErr);
+ audioSource = message.message;
+ }
}
+ // 设置音频源
+ audioPlayer.value.src = audioSource;
+
// 设置当前播放状态
currentPlayingId.value = message.id;
isPlaying.value = true;
@@ -830,8 +844,39 @@ export default {
// 播放出错时重置状态
audioPlayer.value.onerror = (e) => {
- console.error('音频播放出错:', e);
- alert('播放语音失败,可能是格式不支持或数据已损坏');
+ const errorDetails = e.target.error ? `错误代码: ${e.target.error.code}` : '未知错误';
+ console.error('音频播放出错:', errorDetails);
+
+ // 尝试使用不同的MIME类型重新播放
+ if (!message.retryCount) {
+ message.retryCount = 1;
+ console.log('尝试使用备用MIME类型重新播放');
+
+ // 清除当前音频源
+ audioPlayer.value.src = '';
+
+ // 尝试不同的MIME类型
+ const alternativeMimeTypes = ['audio/webm', 'audio/ogg', 'audio/mp4', 'audio/wav'];
+ const currentMimeType = mimeType;
+
+ // 找到一个不同的MIME类型
+ let newMimeType = alternativeMimeTypes.find(type => type !== currentMimeType) || 'audio/webm';
+
+ // 使用新的MIME类型
+ message.audio_mime_type = newMimeType;
+
+ // 重新尝试播放
+ setTimeout(() => playAudio(message), 100);
+ return;
+ }
+
+ // 如果是本地录制的消息,提示可能是暂时性问题
+ if (message.isLocal) {
+ alert('播放本地录制的语音失败,这可能是暂时性问题,语音消息仍会发送给其他用户');
+ } else {
+ alert('播放语音失败,可能是格式不支持或数据已损坏');
+ }
+
currentPlayingId.value = null;
isPlaying.value = false;
};
@@ -930,7 +975,7 @@ export default {
// 1. 获取媒体设备列表
await getMediaDevices()
- // 2. 请求麦克风权限并获取音频流
+ // 2. 请求麦克风权限并获取音频流(静音状态)
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: selectedAudioInput.value ? { exact: selectedAudioInput.value } : undefined,
@@ -941,6 +986,12 @@ export default {
video: false
})
+ // 默认静音开始
+ stream.getAudioTracks().forEach(track => {
+ track.enabled = false
+ })
+ isMuted.value = true
+
localStream.value = stream
// 3. 设置音频分析器(用于显示音量)
@@ -953,7 +1004,7 @@ export default {
// 5. 标记WebRTC为活跃状态
rtcActive.value = true
- addNotification('您已加入语音聊天')
+ addNotification('您已加入语音聊天(已静音)')
isLoadingRTC.value = false
} catch (err) {
@@ -1345,6 +1396,7 @@ export default {
audioPlayer,
isRecording,
recordingDuration,
+ rtcActive,
localStream,
peerConnections,
mediaDevices,
@@ -1360,7 +1412,7 @@ export default {
joinRoom,
leaveRoom,
sendMessage,
- startRecording,
+ toggleRecording,
stopRecording,
cancelRecording,
playAudio,
@@ -1369,7 +1421,7 @@ export default {
isCurrentUser,
formatTime,
toggleVoiceChat,
- rtcActive
+ toggleMute
}
}
}
@@ -1836,9 +1888,6 @@ export default {
.recording-text {
position: absolute;
- left: -120px;
- top: 50%;
- transform: translateY(-50%);
white-space: nowrap;
font-size: 12px;
color: #e74c3c;
@@ -1848,6 +1897,9 @@ export default {
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 10;
+ left: -120px;
+ top: 50%;
+ transform: translateY(-50%);
}
/* 在空间不足时的备选位置 */