121 lines
4.9 KiB
JavaScript
121 lines
4.9 KiB
JavaScript
// ---------- Глобальные переменные и инициализация ----------
|
|
const GROQ_API_KEY = '';
|
|
const VERSION = 'v72';
|
|
|
|
let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = '';
|
|
let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null;
|
|
let recordStartTime = 0, recordDuration = 0;
|
|
|
|
function setStatus(msg) { document.getElementById('status').textContent = msg; }
|
|
function enable(id, val) { document.getElementById(id).disabled = !val; }
|
|
|
|
// ---------- Инициализация ----------
|
|
document.getElementById('ver').textContent = VERSION;
|
|
renderHistory();
|
|
renderRecHistory();
|
|
|
|
// ---------- Кнопка Запись ----------
|
|
document.getElementById('btnRecord').onclick = async function () {
|
|
const btn = this;
|
|
if (isRecording) {
|
|
btn.disabled = true;
|
|
stopRecording();
|
|
return;
|
|
}
|
|
try {
|
|
isRecording = true;
|
|
btn.textContent = '■ Стоп';
|
|
await startRecording();
|
|
} catch (err) {
|
|
let msg = '⛔ ';
|
|
if (err.name === 'NotAllowedError') msg += 'Доступ к микрофону запрещён. Нажмите на замочек 🔒 в адресной строке → Микрофон → Разрешить.';
|
|
else if (err.name === 'NotFoundError') msg += 'Микрофон не найден. Подключите микрофон.';
|
|
else if (err.name === 'NotReadableError') msg += 'Микрофон занят другим приложением.';
|
|
else msg += 'Ошибка: ' + (err.message || err.name);
|
|
setStatus(msg);
|
|
btn.textContent = '🎙 Записать';
|
|
isRecording = false;
|
|
}
|
|
};
|
|
|
|
// ---------- Воспроизведение своей записи ----------
|
|
document.getElementById('btnPlayUser').onclick = async () => {
|
|
if (!userAudioBlob) return setStatus('Нет записи!');
|
|
if (userAudioBlob.size === 0) return setStatus('Запись пустая!');
|
|
const url = URL.createObjectURL(userAudioBlob);
|
|
const audio = new Audio(url);
|
|
audio.volume = 1.0;
|
|
audio.onended = () => { URL.revokeObjectURL(url); setStatus('Готово.'); };
|
|
audio.onerror = () => { URL.revokeObjectURL(url); setStatus('Ошибка воспроизведения.'); };
|
|
setStatus('🔊 Ваша запись...');
|
|
try {
|
|
await audio.play();
|
|
} catch (err) {
|
|
URL.revokeObjectURL(url);
|
|
setStatus('Ошибка воспроизведения — нажмите ещё раз.');
|
|
return;
|
|
}
|
|
const original = document.getElementById('inputText').value.trim();
|
|
if (original && userAudioBlob) {
|
|
await doCompare(userAudioBlob, original, recordDuration);
|
|
}
|
|
};
|
|
|
|
// ---------- Кнопки ----------
|
|
document.getElementById('btnTTS').onclick = async () => {
|
|
ttsText = document.getElementById('inputText').value.trim();
|
|
if (!ttsText) return setStatus('Введите текст!');
|
|
saveToHistory(ttsText);
|
|
translateToRussian(ttsText);
|
|
setStatus('🔊 Эталон...');
|
|
await playTTS(ttsText);
|
|
setStatus('Готово.');
|
|
};
|
|
|
|
document.getElementById('btnCompare').onclick = async () => {
|
|
if (!userAudioBlob) return setStatus('Нет записи!');
|
|
const original = document.getElementById('inputText').value.trim();
|
|
await doCompare(userAudioBlob, original, recordDuration);
|
|
};
|
|
|
|
document.getElementById('btnStereo').onclick = async () => {
|
|
const text = document.getElementById('inputText').value.trim();
|
|
if (!text) return setStatus('Введите текст для эталона!');
|
|
if (!userAudioBlob) return setStatus('Нет записи! Нажмите 🎙 Записать.');
|
|
await playStereo(text, userAudioBlob);
|
|
const original = document.getElementById('inputText').value.trim();
|
|
if (original && userAudioBlob) {
|
|
await doCompare(userAudioBlob, original, recordDuration);
|
|
}
|
|
};
|
|
|
|
// ---------- История: compareRec / stereoRec ----------
|
|
async function compareRec(id) {
|
|
try {
|
|
const db = await openRecDB();
|
|
const tx = db.transaction('recs', 'readonly');
|
|
const store = tx.objectStore('recs');
|
|
const rec = await new Promise(r => { const req = store.get(id); req.onsuccess = () => r(req.result); });
|
|
if (rec?.blob) {
|
|
document.getElementById('inputText').value = rec.word;
|
|
const tr = await doCompare(rec.blob, rec.word, rec.duration);
|
|
if (tr) saveTranscription(id, tr);
|
|
}
|
|
} catch(e) { setStatus('Ошибка загрузки записи.'); }
|
|
}
|
|
|
|
async function stereoRec(id) {
|
|
try {
|
|
const db = await openRecDB();
|
|
const tx = db.transaction('recs', 'readonly');
|
|
const store = tx.objectStore('recs');
|
|
const rec = await new Promise(r => { const req = store.get(id); req.onsuccess = () => r(req.result); });
|
|
if (rec?.blob) {
|
|
document.getElementById('inputText').value = rec.word;
|
|
await playStereo(rec.word, rec.blob);
|
|
const tr = await doCompare(rec.blob, rec.word, rec.duration);
|
|
if (tr) saveTranscription(id, tr);
|
|
}
|
|
} catch(e) { setStatus('Ошибка загрузки записи.'); }
|
|
}
|