Improved audio system with voice detection and test button
This commit is contained in:
parent
f1e4776b33
commit
17ea76e786
201
index.html
201
index.html
@ -699,8 +699,12 @@ body {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header"><div class="badge">ШАГ 1</div><h2>Арабский алфавит</h2><p>28 букв — основа чтения Корана</p></div>
|
<div class="section-header"><div class="badge">ШАГ 1</div><h2>Арабский алфавит</h2><p>28 букв — основа чтения Корана</p></div>
|
||||||
<div style="text-align:center;margin-bottom:24px">
|
<div style="text-align:center;margin-bottom:24px">
|
||||||
<button class="btn btn-primary" onclick="playAllLetters()">🔊 Прослушать все буквы</button>
|
<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap">
|
||||||
<p style="font-size:13px;color:var(--text-secondary);margin-top:8px">Нажмите на букву или 🔊 чтобы услышать произношение</p>
|
<button class="btn btn-primary" onclick="playAllLetters()">🔊 Прослушать все буквы</button>
|
||||||
|
<button class="btn btn-secondary" onclick="testAudio()">🔊 Тест звука</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-size:13px;color:var(--text-secondary);margin-top:12px">Нажмите «Тест звука» чтобы проверить работает ли аудио</p>
|
||||||
|
<div id="audioStatus" style="margin-top:8px;font-size:12px;padding:8px;border-radius:8px;display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="alphabetGrid"></div>
|
<div id="alphabetGrid"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -1258,7 +1262,36 @@ function renderAlphabet() {
|
|||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentAudio = null;
|
// Audio System - Multiple fallbacks
|
||||||
|
let voicesLoaded = false;
|
||||||
|
let arabicVoice = null;
|
||||||
|
|
||||||
|
// Load voices
|
||||||
|
function loadVoices() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const voices = speechSynthesis.getVoices();
|
||||||
|
if (voices.length > 0) {
|
||||||
|
arabicVoice = voices.find(v => v.lang.startsWith('ar')) || voices.find(v => v.lang.includes('ar')) || null;
|
||||||
|
voicesLoaded = true;
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
speechSynthesis.onvoiceschanged = () => {
|
||||||
|
const voices = speechSynthesis.getVoices();
|
||||||
|
arabicVoice = voices.find(v => v.lang.startsWith('ar')) || voices.find(v => v.lang.includes('ar')) || null;
|
||||||
|
voicesLoaded = true;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
// Timeout fallback
|
||||||
|
setTimeout(() => {
|
||||||
|
voicesLoaded = true;
|
||||||
|
resolve();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize voices
|
||||||
|
loadVoices();
|
||||||
|
|
||||||
function speakLetter(idx) {
|
function speakLetter(idx) {
|
||||||
const letter = alphabetData[idx].letter;
|
const letter = alphabetData[idx].letter;
|
||||||
@ -1267,50 +1300,66 @@ function speakLetter(idx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function playArabicAudio(text, label) {
|
function playArabicAudio(text, label) {
|
||||||
// Stop any currently playing audio
|
showToast('🔊 ' + (label || text), 'info');
|
||||||
if (currentAudio) {
|
|
||||||
currentAudio.pause();
|
// Try Web Speech API first
|
||||||
currentAudio = null;
|
if ('speechSynthesis' in window) {
|
||||||
|
window.speechSynthesis.cancel();
|
||||||
|
|
||||||
|
const utterance = new SpeechSynthesisUtterance(text);
|
||||||
|
utterance.lang = 'ar-SA';
|
||||||
|
utterance.rate = 0.6;
|
||||||
|
utterance.pitch = 1;
|
||||||
|
utterance.volume = 1;
|
||||||
|
|
||||||
|
if (arabicVoice) {
|
||||||
|
utterance.voice = arabicVoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
utterance.onerror = (e) => {
|
||||||
|
console.error('Speech error:', e);
|
||||||
|
showToast('Ошибка воспроизведения. Попробуйте другой браузер.', 'error');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.speechSynthesis.speak(utterance);
|
||||||
|
} else {
|
||||||
|
showToast('Ваш браузер не поддерживает аудио', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAudio() {
|
||||||
|
const statusEl = document.getElementById('audioStatus');
|
||||||
|
statusEl.style.display = 'block';
|
||||||
|
statusEl.style.background = 'var(--bg-card)';
|
||||||
|
statusEl.style.color = 'var(--text-secondary)';
|
||||||
|
statusEl.textContent = 'Проверка аудио...';
|
||||||
|
|
||||||
|
// Check speech synthesis support
|
||||||
|
if (!('speechSynthesis' in window)) {
|
||||||
|
statusEl.style.background = 'rgba(239,68,68,0.1)';
|
||||||
|
statusEl.style.color = 'var(--error)';
|
||||||
|
statusEl.textContent = '❌ Ваш браузер не поддерживает синтез речи. Попробуйте Chrome или Edge.';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use Google Translate TTS (free, public API)
|
// Check voices
|
||||||
const encoded = encodeURIComponent(text);
|
const voices = speechSynthesis.getVoices();
|
||||||
const url = `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=${encoded}&tl=ar`;
|
const arVoice = voices.find(v => v.lang.startsWith('ar'));
|
||||||
|
|
||||||
currentAudio = new Audio(url);
|
if (arVoice) {
|
||||||
currentAudio.crossOrigin = 'anonymous';
|
statusEl.textContent = '✅ Арабский голос найден: ' + arVoice.name + '. Воспроизвожу тест...';
|
||||||
|
} else if (voices.length > 0) {
|
||||||
|
statusEl.textContent = '⚠️ Арабский голос не найден. Используется стандартный голос. Голосов доступно: ' + voices.length;
|
||||||
|
} else {
|
||||||
|
statusEl.textContent = '⏳ Голоса загружаются... Попробуйте ещё раз через секунду.';
|
||||||
|
}
|
||||||
|
|
||||||
currentAudio.onplay = () => {
|
playArabicAudio('بسم الله الرحمن الرحيم', 'Тест');
|
||||||
showToast('🔊 ' + (label || text), 'info');
|
|
||||||
};
|
|
||||||
|
|
||||||
currentAudio.onerror = () => {
|
|
||||||
// Fallback to Web Speech API
|
|
||||||
if ('speechSynthesis' in window) {
|
|
||||||
window.speechSynthesis.cancel();
|
|
||||||
const utterance = new SpeechSynthesisUtterance(text);
|
|
||||||
utterance.lang = 'ar-SA';
|
|
||||||
utterance.rate = 0.5;
|
|
||||||
window.speechSynthesis.speak(utterance);
|
|
||||||
}
|
|
||||||
showToast('🔊 ' + (label || text), 'info');
|
|
||||||
};
|
|
||||||
|
|
||||||
currentAudio.play().catch(() => {
|
|
||||||
// Fallback to Web Speech API
|
|
||||||
if ('speechSynthesis' in window) {
|
|
||||||
window.speechSynthesis.cancel();
|
|
||||||
const utterance = new SpeechSynthesisUtterance(text);
|
|
||||||
utterance.lang = 'ar-SA';
|
|
||||||
utterance.rate = 0.5;
|
|
||||||
window.speechSynthesis.speak(utterance);
|
|
||||||
}
|
|
||||||
showToast('🔊 ' + (label || text), 'info');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function playAllLetters() {
|
function playAllLetters() {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
const cards = document.querySelectorAll('.letter-card');
|
||||||
|
|
||||||
function playNext() {
|
function playNext() {
|
||||||
if (index < alphabetData.length) {
|
if (index < alphabetData.length) {
|
||||||
@ -1318,7 +1367,6 @@ function playAllLetters() {
|
|||||||
const name = alphabetData[index].name;
|
const name = alphabetData[index].name;
|
||||||
|
|
||||||
// Highlight current letter
|
// Highlight current letter
|
||||||
const cards = document.querySelectorAll('.letter-card');
|
|
||||||
cards.forEach(c => c.style.borderColor = 'var(--border)');
|
cards.forEach(c => c.style.borderColor = 'var(--border)');
|
||||||
if (cards[index]) {
|
if (cards[index]) {
|
||||||
cards[index].style.borderColor = 'var(--quran-gold)';
|
cards[index].style.borderColor = 'var(--quran-gold)';
|
||||||
@ -1327,29 +1375,30 @@ function playAllLetters() {
|
|||||||
|
|
||||||
showToast('Буква ' + name + ' (' + (index + 1) + '/28)', 'info');
|
showToast('Буква ' + name + ' (' + (index + 1) + '/28)', 'info');
|
||||||
|
|
||||||
const encoded = encodeURIComponent(letter);
|
if ('speechSynthesis' in window) {
|
||||||
const url = `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=${encoded}&tl=ar`;
|
window.speechSynthesis.cancel();
|
||||||
|
const utterance = new SpeechSynthesisUtterance(letter);
|
||||||
|
utterance.lang = 'ar-SA';
|
||||||
|
utterance.rate = 0.6;
|
||||||
|
if (arabicVoice) utterance.voice = arabicVoice;
|
||||||
|
|
||||||
const audio = new Audio(url);
|
utterance.onend = () => {
|
||||||
audio.crossOrigin = 'anonymous';
|
index++;
|
||||||
|
setTimeout(playNext, 500);
|
||||||
|
};
|
||||||
|
|
||||||
audio.onended = () => {
|
utterance.onerror = () => {
|
||||||
|
index++;
|
||||||
|
setTimeout(playNext, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.speechSynthesis.speak(utterance);
|
||||||
|
} else {
|
||||||
index++;
|
index++;
|
||||||
setTimeout(playNext, 500);
|
setTimeout(playNext, 1000);
|
||||||
};
|
}
|
||||||
|
|
||||||
audio.onerror = () => {
|
|
||||||
index++;
|
|
||||||
setTimeout(playNext, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
audio.play().catch(() => {
|
|
||||||
index++;
|
|
||||||
setTimeout(playNext, 500);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
showToast('Все буквы прослушаны!', 'success');
|
showToast('Все буквы прослушаны!', 'success');
|
||||||
const cards = document.querySelectorAll('.letter-card');
|
|
||||||
cards.forEach(c => c.style.borderColor = 'var(--border)');
|
cards.forEach(c => c.style.borderColor = 'var(--border)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1650,38 +1699,46 @@ function speakFullSurah(name) {
|
|||||||
ikhlas: 'قُلْ هُوَ اللَّهُ أَحَدٌ. اللَّهُ الصَّمَدُ. لَمْ يَلِدْ وَلَمْ يُولَدْ. وَلَمْ يَكُن لَّهُ كُفُوًا أَحَدٌ'
|
ikhlas: 'قُلْ هُوَ اللَّهُ أَحَدٌ. اللَّهُ الصَّمَدُ. لَمْ يَلِدْ وَلَمْ يُولَدْ. وَلَمْ يَكُن لَّهُ كُفُوًا أَحَدٌ'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!('speechSynthesis' in window)) {
|
||||||
|
showToast('Ваш браузер не поддерживает аудио', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const text = surahs[name];
|
const text = surahs[name];
|
||||||
const parts = text.split('. ');
|
const parts = text.split('. ');
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
|
||||||
function playNext() {
|
function speakNext() {
|
||||||
if (index < parts.length) {
|
if (index < parts.length) {
|
||||||
const encoded = encodeURIComponent(parts[index]);
|
window.speechSynthesis.cancel();
|
||||||
const url = `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=${encoded}&tl=ar`;
|
const utterance = new SpeechSynthesisUtterance(parts[index]);
|
||||||
|
utterance.lang = 'ar-SA';
|
||||||
|
utterance.rate = 0.6;
|
||||||
|
utterance.pitch = 1;
|
||||||
|
utterance.volume = 1;
|
||||||
|
|
||||||
const audio = new Audio(url);
|
if (arabicVoice) {
|
||||||
audio.crossOrigin = 'anonymous';
|
utterance.voice = arabicVoice;
|
||||||
|
}
|
||||||
|
|
||||||
audio.onended = () => {
|
utterance.onend = () => {
|
||||||
index++;
|
index++;
|
||||||
setTimeout(playNext, 400);
|
setTimeout(speakNext, 400);
|
||||||
};
|
};
|
||||||
|
|
||||||
audio.onerror = () => {
|
utterance.onerror = () => {
|
||||||
index++;
|
index++;
|
||||||
setTimeout(playNext, 400);
|
setTimeout(speakNext, 400);
|
||||||
};
|
};
|
||||||
|
|
||||||
audio.play().catch(() => {
|
window.speechSynthesis.speak(utterance);
|
||||||
index++;
|
showToast('Аят ' + (index + 1) + '/' + parts.length, 'info');
|
||||||
setTimeout(playNext, 400);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
showToast('Сура прослушана!', 'success');
|
showToast('Сура прослушана!', 'success');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playNext();
|
speakNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
function markSurahPracticed(id) {
|
function markSurahPracticed(id) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user