/**
* 语音克隆页面 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 = `
`;
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();
}
}