/** * 语音克隆页面 JavaScript */ // 全局变量 let loadingModal = null; let mediaRecorder = null; let audioChunks = []; let recordedBlob = null; // DOM加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initializeComponents(); bindEvents(); loadVoiceStatus(); }); /** * 初始化组件 */ function initializeComponents() { loadingModal = new bootstrap.Modal(document.getElementById('loadingModal')); } /** * 绑定事件 */ function bindEvents() { // 文件选择 document.getElementById('voice-file').addEventListener('change', handleFileSelect); // 录音控制 document.getElementById('start-record').addEventListener('click', startRecording); document.getElementById('stop-record').addEventListener('click', stopRecording); // 表单提交 document.getElementById('voice-upload-form').addEventListener('submit', uploadVoiceSample); document.getElementById('speech-generate-form').addEventListener('submit', generateSpeech); } /** * 显示加载状态 */ function showLoading(message = '正在处理中...', detail = '请稍候...') { document.getElementById('loading-message').textContent = message; document.getElementById('loading-detail').textContent = detail; loadingModal.show(); } /** * 隐藏加载状态 */ function hideLoading() { loadingModal.hide(); } /** * 显示成功消息 */ function showSuccess(message) { showToast(message, 'success'); } /** * 显示错误消息 */ function showError(message) { showToast(message, 'danger'); } /** * 显示Toast消息 */ function showToast(message, type = 'info') { const colors = { 'success': 'bg-success', 'danger': 'bg-danger', 'warning': 'bg-warning', 'info': 'bg-info' }; const toast = document.createElement('div'); toast.className = `toast align-items-center text-white ${colors[type]} border-0 position-fixed top-0 end-0 m-3`; toast.style.zIndex = '9999'; toast.innerHTML = `
${message}
`; document.body.appendChild(toast); const bsToast = new bootstrap.Toast(toast); bsToast.show(); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); } /** * 加载语音状态 */ async function loadVoiceStatus() { try { const response = await fetch('/voice-clone/api/voice-clone/status'); const result = await response.json(); updateStatusCard(result); if (result.has_sample) { // 启用语音生成功能 document.getElementById('generate-btn').disabled = false; } } catch (error) { console.error('加载语音状态失败:', error); updateStatusCard({ success: false, message: '无法加载语音状态' }); } } /** * 更新状态卡片 */ function updateStatusCard(result) { const content = document.getElementById('voice-status-content'); if (result.has_sample) { content.innerHTML = `
专属声音已准备好!

AI识别内容:「${result.recognized_text}」

录制时间:${result.upload_time}
已就绪
`; } else { content.innerHTML = `
还没有录制语音样本

快来录制你的专属声音,让AI学会模仿你说话吧!

`; } } /** * 处理文件选择 */ function handleFileSelect(e) { const file = e.target.files[0]; if (file) { recordedBlob = null; // 重置录音数据 document.getElementById('upload-btn').disabled = false; // 显示文件预览 const audioUrl = URL.createObjectURL(file); showAudioPreview(audioUrl); } } /** * 开始录音 */ async function startRecording() { try { // 重置文件选择 document.getElementById('voice-file').value = ''; const stream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true } }); mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' }); audioChunks = []; mediaRecorder.ondataavailable = function(event) { if (event.data.size > 0) { audioChunks.push(event.data); } }; mediaRecorder.onstop = function() { recordedBlob = new Blob(audioChunks, { type: 'audio/webm' }); const audioUrl = URL.createObjectURL(recordedBlob); showAudioPreview(audioUrl); // 启用上传按钮 document.getElementById('upload-btn').disabled = false; }; mediaRecorder.start(100); // 更新UI document.getElementById('start-record').disabled = true; document.getElementById('stop-record').disabled = false; document.getElementById('record-status').textContent = '正在录音...'; document.getElementById('record-status').className = 'text-danger'; } catch (error) { showError('录音失败,请检查麦克风权限'); console.error('录音失败:', error); } } /** * 停止录音 */ function stopRecording() { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); mediaRecorder.stream.getTracks().forEach(track => track.stop()); } // 更新UI document.getElementById('start-record').disabled = false; document.getElementById('stop-record').disabled = true; document.getElementById('record-status').textContent = '录音完成'; document.getElementById('record-status').className = 'text-success'; } /** * 显示音频预览 */ function showAudioPreview(audioUrl) { const previewAudio = document.getElementById('preview-audio'); const previewSource = document.getElementById('preview-source'); previewSource.src = audioUrl; previewAudio.load(); document.getElementById('audio-preview').style.display = 'block'; } /** * 上传语音样本 */ async function uploadVoiceSample(e) { e.preventDefault(); const fileInput = document.getElementById('voice-file'); const file = fileInput.files[0]; if (!file && !recordedBlob) { showError('请选择音频文件或先录音'); return; } showLoading('正在上传和训练你的声音...', '这可能需要几秒钟时间'); try { const formData = new FormData(); if (file) { formData.append('audio', file); } else if (recordedBlob) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); formData.append('audio', recordedBlob, `recording_${timestamp}.webm`); } const response = await fetch('/voice-clone/api/voice-clone/upload', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { showSuccess(result.message); loadVoiceStatus(); // 重新加载状态 // 清空表单 document.getElementById('voice-upload-form').reset(); document.getElementById('audio-preview').style.display = 'none'; document.getElementById('upload-btn').disabled = true; } else { showError(result.message); } } catch (error) { showError('上传失败,请检查网络连接'); console.error('上传失败:', error); } finally { hideLoading(); } } /** * 生成语音 */ async function generateSpeech(e) { e.preventDefault(); const text = document.getElementById('speech-text').value.trim(); if (!text) { showError('请输入要合成的文本'); return; } showLoading('正在用你的声音生成语音...', '请耐心等待,这很神奇哦'); try { const response = await fetch('/voice-clone/api/voice-clone/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ text: text }) }); const result = await response.json(); if (result.success) { showSuccess(result.message); // 显示生成的音频 const generatedAudio = document.getElementById('generated-audio'); const generatedSource = document.getElementById('generated-source'); // 创建音频URL const filename = result.audio_url.split('/').pop(); generatedSource.src = `/voice-test/download-audio/${filename}`; generatedAudio.load(); document.getElementById('speech-result').style.display = 'block'; } else { showError(result.message); } } catch (error) { showError('生成失败,请检查网络连接'); console.error('生成失败:', error); } finally { hideLoading(); } }