修复无法播放bug

main
wang 2 months ago
parent 36c92b7faf
commit 70e431eebc

@ -675,16 +675,33 @@ def handle_send_audio_message(data):
room_id = data.get('room_id') room_id = data.get('room_id')
audio_data = data.get('audio_data') # Base64编码的音频数据 audio_data = data.get('audio_data') # Base64编码的音频数据
audio_duration = data.get('audio_duration', 0) # 音频持续时间(秒) 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: if not room_id or not audio_data:
logger.error("语音消息缺少必要参数: room_id或audio_data") logger.error("语音消息缺少必要参数: room_id或audio_data")
return return
# 检查audio_data是否为有效的base64编码数据 # 检查和修复音频数据格式
if not audio_data.startswith('data:audio'): try:
logger.warning(f"接收到非标准格式的音频数据,前缀: {audio_data[:20]}") # 确保音频数据有正确的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: try:
# 获取用户和房间信息 # 获取用户和房间信息
@ -716,6 +733,7 @@ def handle_send_audio_message(data):
'message': audio_data, 'message': audio_data,
'message_type': 'audio', 'message_type': 'audio',
'audio_duration': audio_duration, 'audio_duration': audio_duration,
'audio_mime_type': audio_mime_type,
'created_at': message.created_at.isoformat() 'created_at': message.created_at.isoformat()
}, room=str(room_id)) }, room=str(room_id))
logger.info(f"语音消息已广播到房间: {room_id}") logger.info(f"语音消息已广播到房间: {room_id}")

File diff suppressed because one or more lines are too long

@ -194,16 +194,14 @@
/> />
<button @click="sendMessage" :disabled="!newMessage.trim()" class="send-button">发送</button> <button @click="sendMessage" :disabled="!newMessage.trim()" class="send-button">发送</button>
<!-- 语音消息按钮 --> <!-- 语音消息按钮 - 改为开关形式 -->
<button <button
@mousedown="startRecording" @click="toggleRecording"
@mouseup="stopRecording"
@mouseleave="cancelRecording"
class="voice-button" class="voice-button"
:class="{ 'recording': isRecording }" :class="{ 'recording': isRecording }"
title="按住说话" :title="isRecording ? '点击结束录音' : '点击开始录音'"
> >
<i class="mic-icon">🎤</i> <i class="mic-icon">{{ isRecording ? '⏹️' : '🎤' }}</i>
<span v-if="isRecording" class="recording-text">... {{ recordingDuration }}s</span> <span v-if="isRecording" class="recording-text">... {{ recordingDuration }}s</span>
</button> </button>
</div> </div>
@ -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 = { let options = {};
mimeType: 'audio/webm;codecs=opus', let mimeType = '';
audioBitsPerSecond: 32000 //
//
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, '秒') console.log('录音实际时长:', actualDuration, '秒')
// Blob // 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() const tempId = 'temp-' + Date.now()
@ -706,12 +680,14 @@ export default {
reader.readAsDataURL(audioBlob) reader.readAsDataURL(audioBlob)
reader.onloadend = () => { reader.onloadend = () => {
const base64Audio = reader.result const base64Audio = reader.result
console.log('音频格式:', mimeType || 'unknown', '数据前缀:', base64Audio.substring(0, 50))
// //
socket.emit('send_audio_message', { socket.emit('send_audio_message', {
room_id: currentRoom.value.id, room_id: currentRoom.value.id,
audio_data: base64Audio, 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 = () => { const stopRecording = () => {
if (!mediaRecorder.value || mediaRecorder.value.state === 'inactive') { if (!mediaRecorder.value || mediaRecorder.value.state === 'inactive') {
@ -799,24 +784,53 @@ export default {
console.log('准备播放语音消息:', message.id, '类型:', typeof message.message); 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:')) { if (message.localAudioUrl && message.localAudioUrl.startsWith('blob:')) {
// 使Blob URL // 使Blob URL
audioPlayer.value.src = message.localAudioUrl; audioSource = message.localAudioUrl;
console.log('使用本地缓存的Blob URL播放'); console.log('使用本地缓存的Blob URL播放');
} else if (message.message.startsWith('blob:')) { } else if (message.message.startsWith('blob:')) {
// 使Blob URL // 使Blob URL
audioPlayer.value.src = message.message; audioSource = message.message;
console.log('使用Blob URL播放:', message.message); console.log('使用Blob URL播放:', message.message);
} else if (message.message.startsWith('data:audio')) { } else if (message.message.startsWith('data:audio')) {
// base64 // base64
audioPlayer.value.src = message.message; audioSource = message.message;
console.log('使用base64数据播放'); console.log('使用base64数据播放');
} else { } else {
// //
console.warn('未知格式的音频消息,尝试直接使用:', message.message.substring(0, 50) + '...'); console.warn('未知格式的音频消息,尝试修复格式');
audioPlayer.value.src = message.message;
//
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; currentPlayingId.value = message.id;
isPlaying.value = true; isPlaying.value = true;
@ -830,8 +844,39 @@ export default {
// //
audioPlayer.value.onerror = (e) => { audioPlayer.value.onerror = (e) => {
console.error('音频播放出错:', e); const errorDetails = e.target.error ? `错误代码: ${e.target.error.code}` : '未知错误';
alert('播放语音失败,可能是格式不支持或数据已损坏'); 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; currentPlayingId.value = null;
isPlaying.value = false; isPlaying.value = false;
}; };
@ -930,7 +975,7 @@ export default {
// 1. // 1.
await getMediaDevices() await getMediaDevices()
// 2. // 2.
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
audio: { audio: {
deviceId: selectedAudioInput.value ? { exact: selectedAudioInput.value } : undefined, deviceId: selectedAudioInput.value ? { exact: selectedAudioInput.value } : undefined,
@ -941,6 +986,12 @@ export default {
video: false video: false
}) })
//
stream.getAudioTracks().forEach(track => {
track.enabled = false
})
isMuted.value = true
localStream.value = stream localStream.value = stream
// 3. // 3.
@ -953,7 +1004,7 @@ export default {
// 5. WebRTC // 5. WebRTC
rtcActive.value = true rtcActive.value = true
addNotification('您已加入语音聊天') addNotification('您已加入语音聊天(已静音)')
isLoadingRTC.value = false isLoadingRTC.value = false
} catch (err) { } catch (err) {
@ -1345,6 +1396,7 @@ export default {
audioPlayer, audioPlayer,
isRecording, isRecording,
recordingDuration, recordingDuration,
rtcActive,
localStream, localStream,
peerConnections, peerConnections,
mediaDevices, mediaDevices,
@ -1360,7 +1412,7 @@ export default {
joinRoom, joinRoom,
leaveRoom, leaveRoom,
sendMessage, sendMessage,
startRecording, toggleRecording,
stopRecording, stopRecording,
cancelRecording, cancelRecording,
playAudio, playAudio,
@ -1369,7 +1421,7 @@ export default {
isCurrentUser, isCurrentUser,
formatTime, formatTime,
toggleVoiceChat, toggleVoiceChat,
rtcActive toggleMute
} }
} }
} }
@ -1836,9 +1888,6 @@ export default {
.recording-text { .recording-text {
position: absolute; position: absolute;
left: -120px;
top: 50%;
transform: translateY(-50%);
white-space: nowrap; white-space: nowrap;
font-size: 12px; font-size: 12px;
color: #e74c3c; color: #e74c3c;
@ -1848,6 +1897,9 @@ export default {
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 10; z-index: 10;
left: -120px;
top: 50%;
transform: translateY(-50%);
} }
/* 在空间不足时的备选位置 */ /* 在空间不足时的备选位置 */

Loading…
Cancel
Save