Added ResponsiveVoice.js for Arabic audio pronunciation
This commit is contained in:
parent
17ea76e786
commit
947fc39a17
130
index.html
130
index.html
@ -6,6 +6,7 @@
|
|||||||
<title>QuranReader — Читай Коран на арабском</title>
|
<title>QuranReader — Читай Коран на арабском</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Noto+Naskh+Arabic:wght@400;600;700&family=Amiri:wght@400;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Noto+Naskh+Arabic:wght@400;600;700&family=Amiri:wght@400;700&display=swap" rel="stylesheet">
|
||||||
|
<script src="https://code.responsivevoice.org/responsivevoice.js?key=FREE"></script>
|
||||||
<style>
|
<style>
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
--ink: #0F1218; --bg: #FFFFFF; --bg-card: #F2F4F7; --bg-card-hover: #E8FCFF;
|
--ink: #0F1218; --bg: #FFFFFF; --bg-card: #F2F4F7; --bg-card-hover: #E8FCFF;
|
||||||
@ -1262,31 +1263,83 @@ function renderAlphabet() {
|
|||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio System - Multiple fallbacks
|
// Audio System using ResponsiveVoice
|
||||||
let voicesLoaded = false;
|
function speakLetter(idx) {
|
||||||
let arabicVoice = null;
|
const letter = alphabetData[idx].letter;
|
||||||
|
const name = alphabetData[idx].name;
|
||||||
|
playArabicAudio(letter, name);
|
||||||
|
}
|
||||||
|
|
||||||
// Load voices
|
function playArabicAudio(text, label) {
|
||||||
function loadVoices() {
|
showToast('🔊 ' + (label || text), 'info');
|
||||||
return new Promise((resolve) => {
|
|
||||||
const voices = speechSynthesis.getVoices();
|
if (typeof responsiveVoice !== 'undefined' && responsiveVoice.voiceSupport()) {
|
||||||
if (voices.length > 0) {
|
responsiveVoice.speak(text, "Arabic Male", {
|
||||||
arabicVoice = voices.find(v => v.lang.startsWith('ar')) || voices.find(v => v.lang.includes('ar')) || null;
|
rate: 0.8,
|
||||||
voicesLoaded = true;
|
pitch: 1,
|
||||||
resolve();
|
volume: 1
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
speechSynthesis.onvoiceschanged = () => {
|
showToast('Аудио не поддерживается. Обновите страницу.', 'error');
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 'Проверка аудио...';
|
||||||
|
|
||||||
|
if (typeof responsiveVoice !== 'undefined' && responsiveVoice.voiceSupport()) {
|
||||||
|
statusEl.style.background = 'rgba(34,197,94,0.1)';
|
||||||
|
statusEl.style.color = 'var(--success)';
|
||||||
|
statusEl.textContent = '✅ Аудио поддерживается! Воспроизвожу тест...';
|
||||||
|
responsiveVoice.speak("بسم الله", "Arabic Male", {rate: 0.8});
|
||||||
|
} else {
|
||||||
|
statusEl.style.background = 'rgba(239,68,68,0.1)';
|
||||||
|
statusEl.style.color = 'var(--error)';
|
||||||
|
statusEl.textContent = '❌ Аудио не загружено. Обновите страницу и попробуйте снова.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playAllLetters() {
|
||||||
|
let index = 0;
|
||||||
|
const cards = document.querySelectorAll('.letter-card');
|
||||||
|
|
||||||
|
function playNext() {
|
||||||
|
if (index < alphabetData.length) {
|
||||||
|
const letter = alphabetData[index].letter;
|
||||||
|
const name = alphabetData[index].name;
|
||||||
|
|
||||||
|
cards.forEach(c => c.style.borderColor = 'var(--border)');
|
||||||
|
if (cards[index]) {
|
||||||
|
cards[index].style.borderColor = 'var(--quran-gold)';
|
||||||
|
cards[index].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('Буква ' + name + ' (' + (index + 1) + '/28)', 'info');
|
||||||
|
|
||||||
|
if (typeof responsiveVoice !== 'undefined' && responsiveVoice.voiceSupport()) {
|
||||||
|
responsiveVoice.speak(letter, "Arabic Male", {
|
||||||
|
rate: 0.8,
|
||||||
|
onend: () => {
|
||||||
|
index++;
|
||||||
|
setTimeout(playNext, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
setTimeout(playNext, 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast('Все буквы прослушаны!', 'success');
|
||||||
|
cards.forEach(c => c.style.borderColor = 'var(--border)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playNext();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1699,8 +1752,8 @@ function speakFullSurah(name) {
|
|||||||
ikhlas: 'قُلْ هُوَ اللَّهُ أَحَدٌ. اللَّهُ الصَّمَدُ. لَمْ يَلِدْ وَلَمْ يُولَدْ. وَلَمْ يَكُن لَّهُ كُفُوًا أَحَدٌ'
|
ikhlas: 'قُلْ هُوَ اللَّهُ أَحَدٌ. اللَّهُ الصَّمَدُ. لَمْ يَلِدْ وَلَمْ يُولَدْ. وَلَمْ يَكُن لَّهُ كُفُوًا أَحَدٌ'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!('speechSynthesis' in window)) {
|
if (typeof responsiveVoice === 'undefined' || !responsiveVoice.voiceSupport()) {
|
||||||
showToast('Ваш браузер не поддерживает аудио', 'error');
|
showToast('Аудио не поддерживается', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1710,29 +1763,14 @@ function speakFullSurah(name) {
|
|||||||
|
|
||||||
function speakNext() {
|
function speakNext() {
|
||||||
if (index < parts.length) {
|
if (index < parts.length) {
|
||||||
window.speechSynthesis.cancel();
|
|
||||||
const utterance = new SpeechSynthesisUtterance(parts[index]);
|
|
||||||
utterance.lang = 'ar-SA';
|
|
||||||
utterance.rate = 0.6;
|
|
||||||
utterance.pitch = 1;
|
|
||||||
utterance.volume = 1;
|
|
||||||
|
|
||||||
if (arabicVoice) {
|
|
||||||
utterance.voice = arabicVoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
utterance.onend = () => {
|
|
||||||
index++;
|
|
||||||
setTimeout(speakNext, 400);
|
|
||||||
};
|
|
||||||
|
|
||||||
utterance.onerror = () => {
|
|
||||||
index++;
|
|
||||||
setTimeout(speakNext, 400);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.speechSynthesis.speak(utterance);
|
|
||||||
showToast('Аят ' + (index + 1) + '/' + parts.length, 'info');
|
showToast('Аят ' + (index + 1) + '/' + parts.length, 'info');
|
||||||
|
responsiveVoice.speak(parts[index], "Arabic Male", {
|
||||||
|
rate: 0.8,
|
||||||
|
onend: () => {
|
||||||
|
index++;
|
||||||
|
setTimeout(speakNext, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
showToast('Сура прослушана!', 'success');
|
showToast('Сура прослушана!', 'success');
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user