|
|
|
@ -606,86 +606,47 @@ export default {
|
|
|
|
|
// 开始录音
|
|
|
|
|
const startRecording = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// 请求用户的麦克风权限
|
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
audio: {
|
|
|
|
|
echoCancellation: true,
|
|
|
|
|
noiseSuppression: true,
|
|
|
|
|
autoGainControl: true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 检测浏览器支持的音频格式
|
|
|
|
|
let options = {};
|
|
|
|
|
let mimeType = '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 按优先级检测支持的格式
|
|
|
|
|
let mimeType = 'audio/webm';
|
|
|
|
|
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 // 降低比特率以减小文件大小
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const options = {
|
|
|
|
|
mimeType: mimeType,
|
|
|
|
|
audioBitsPerSecond: 32000
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 创建媒体录制器
|
|
|
|
|
mediaRecorder.value = new MediaRecorder(stream, options)
|
|
|
|
|
audioChunks.value = []
|
|
|
|
|
mediaRecorder.value = new MediaRecorder(stream, options);
|
|
|
|
|
audioChunks.value = [];
|
|
|
|
|
|
|
|
|
|
// 记录开始时间
|
|
|
|
|
recordingStartTime.value = Date.now()
|
|
|
|
|
recordingDuration.value = 0
|
|
|
|
|
recordingStartTime.value = Date.now();
|
|
|
|
|
|
|
|
|
|
// 监听数据可用事件
|
|
|
|
|
mediaRecorder.value.addEventListener('dataavailable', event => {
|
|
|
|
|
if (event.data.size > 0) {
|
|
|
|
|
audioChunks.value.push(event.data)
|
|
|
|
|
audioChunks.value.push(event.data);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 监听录制完成事件
|
|
|
|
|
mediaRecorder.value.addEventListener('stop', () => {
|
|
|
|
|
// 如果没有录制数据或录制被取消,直接返回
|
|
|
|
|
if (audioChunks.value.length === 0 || !isRecording.value) {
|
|
|
|
|
isRecording.value = false
|
|
|
|
|
recordingDuration.value = 0
|
|
|
|
|
stopMediaTracks(stream)
|
|
|
|
|
return
|
|
|
|
|
const actualDuration = (Date.now() - recordingStartTime.value) / 1000;
|
|
|
|
|
if (audioChunks.value.length === 0 || actualDuration < 0.5) {
|
|
|
|
|
stopMediaTracks(stream);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算实际录制时长(秒)
|
|
|
|
|
const actualDuration = (Date.now() - recordingStartTime.value) / 1000
|
|
|
|
|
console.log('录音实际时长:', actualDuration, '秒')
|
|
|
|
|
|
|
|
|
|
// 将录制的音频块转换为Blob
|
|
|
|
|
const audioBlob = new Blob(audioChunks.value, { type: mimeType || 'audio/webm' })
|
|
|
|
|
|
|
|
|
|
// 添加本地预览消息,减少感知延迟
|
|
|
|
|
const tempId = 'temp-' + Date.now()
|
|
|
|
|
const localAudioUrl = URL.createObjectURL(audioBlob)
|
|
|
|
|
const audioBlob = new Blob(audioChunks.value, { type: mimeType });
|
|
|
|
|
const localAudioUrl = URL.createObjectURL(audioBlob);
|
|
|
|
|
|
|
|
|
|
messages.value.push({
|
|
|
|
|
id: tempId,
|
|
|
|
|
id: `temp-${Date.now()}`,
|
|
|
|
|
user_id: currentUser.id,
|
|
|
|
|
username: currentUser.username,
|
|
|
|
|
message: localAudioUrl,
|
|
|
|
@ -693,81 +654,58 @@ export default {
|
|
|
|
|
audio_duration: actualDuration,
|
|
|
|
|
created_at: new Date().toISOString(),
|
|
|
|
|
isLocal: true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
scrollToBottom()
|
|
|
|
|
});
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
|
|
|
|
|
// 将Blob转换为Base64
|
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
reader.readAsDataURL(audioBlob)
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.readAsDataURL(audioBlob);
|
|
|
|
|
reader.onloadend = () => {
|
|
|
|
|
const base64Audio = reader.result
|
|
|
|
|
console.log('音频格式:', mimeType || 'unknown', '数据前缀:', base64Audio.substring(0, 50))
|
|
|
|
|
|
|
|
|
|
// 发送语音消息
|
|
|
|
|
const base64Audio = reader.result;
|
|
|
|
|
socket.emit('send_audio_message', {
|
|
|
|
|
room_id: currentRoom.value.id,
|
|
|
|
|
audio_data: base64Audio,
|
|
|
|
|
audio_duration: actualDuration,
|
|
|
|
|
audio_mime_type: mimeType || 'audio/webm'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 重置录音状态
|
|
|
|
|
isRecording.value = false
|
|
|
|
|
recordingDuration.value = 0
|
|
|
|
|
recordingStartTime.value = null
|
|
|
|
|
}
|
|
|
|
|
audio_mime_type: mimeType
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 停止所有媒体轨道
|
|
|
|
|
stopMediaTracks(stream)
|
|
|
|
|
})
|
|
|
|
|
stopMediaTracks(stream);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 开始录音,设置较短的时间片以便更快获取数据
|
|
|
|
|
mediaRecorder.value.start(100)
|
|
|
|
|
isRecording.value = true
|
|
|
|
|
mediaRecorder.value.start(100);
|
|
|
|
|
isRecording.value = true;
|
|
|
|
|
|
|
|
|
|
// 启动计时器 - 用于界面显示
|
|
|
|
|
recordingDuration.value = 0; // Reset duration display
|
|
|
|
|
recordingTimer.value = setInterval(() => {
|
|
|
|
|
// 使用实际经过的时间而不是自增计数
|
|
|
|
|
recordingDuration.value = (Date.now() - recordingStartTime.value) / 1000
|
|
|
|
|
|
|
|
|
|
// 限制最大录音时间为60秒
|
|
|
|
|
recordingDuration.value = (Date.now() - recordingStartTime.value) / 1000;
|
|
|
|
|
if (recordingDuration.value >= 60) {
|
|
|
|
|
stopRecording()
|
|
|
|
|
stopRecording();
|
|
|
|
|
}
|
|
|
|
|
}, 100) // 更新频率提高到100ms一次,使显示更平滑
|
|
|
|
|
}, 100);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('无法访问麦克风:', err)
|
|
|
|
|
alert('无法访问麦克风,请检查浏览器权限设置。')
|
|
|
|
|
console.error('无法访问麦克风:', err);
|
|
|
|
|
alert('无法访问麦克风,请检查浏览器权限设置。');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 切换录音状态
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 修复:恢复 toggleRecording 函数
|
|
|
|
|
const toggleRecording = () => {
|
|
|
|
|
if (isRecording.value) {
|
|
|
|
|
stopRecording()
|
|
|
|
|
stopRecording();
|
|
|
|
|
} else {
|
|
|
|
|
startRecording()
|
|
|
|
|
startRecording();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 停止录音
|
|
|
|
|
const stopRecording = (forceStop = false) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const stopRecording = () => {
|
|
|
|
|
if (mediaRecorder.value && isRecording.value) {
|
|
|
|
|
mediaRecorder.value.stop()
|
|
|
|
|
isRecording.value = false
|
|
|
|
|
mediaRecorder.value.stop();
|
|
|
|
|
isRecording.value = false;
|
|
|
|
|
if (recordingTimer.value) {
|
|
|
|
|
clearInterval(recordingTimer.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果不是强制停止(即用户正常完成录音),则处理音频数据
|
|
|
|
|
if (!forceStop) {
|
|
|
|
|
// onstop事件会处理后续逻辑
|
|
|
|
|
} else {
|
|
|
|
|
audioChunks.value = [] // 强制停止时清空数据
|
|
|
|
|
clearInterval(recordingTimer.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 播放音频
|
|
|
|
|
const playAudio = (message) => {
|
|
|
|
|