🔊 Conversor de text a veu
to run into somebody.
have a good trip.
to pass on greetings.
there’s a restroom in the café.
have a good trip.
to pass on greetings.
there’s a restroom in the café.
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<title>Conversor de text a veu</title>
<style>
/* Estil general de la pàgina */
body {
font-family: sans-serif;
padding: 2em;
background-color: lab(90.09% -0.67 -0.25);
}
/* Editor de text editable */
#editor {
font-size: 1.2em;
padding: 0.5em;
margin-top: 1em;
width: 100%;
max-width: 500px;
height: 15em;
overflow-y: auto;
border: 1px solid #ccc;
white-space: pre-wrap;
}
/* Estil dels selectors (idioma i pausa) */
select {
font-size: 1.2em;
padding: 0.5em;
margin-top: 1em;
width: 45%;
max-width: 220px;
height: 2.5em;
overflow-y: hidden;
display: inline-block;
vertical-align: top;
}
/* Estil dels botons de control */
#speakBtn, #toggleBtn {
font-size: 2em;
background: none;
border: none;
cursor: pointer;
margin-top: 1em;
}
#speakBtn:hover, #toggleBtn:hover {
color: hsl(180, 1%, 72%);
}
/* Estil de cada línia destacable */
.linia {
display: block;
padding: 0.2em;
transition: background-color 0.3s ease;
}
/* Línia activa mentre es llegeix */
.activa {
background-color: hsl(180, 1%, 72%);
}
</style>
</head>
<body>
<h2>🔊 Conversor de text a veu</h2>
<!-- Editor de text on l'usuari pot escriure -->
<div id="editor" contenteditable="true" placeholder="Escriu el text que vols escoltar">
to run into somebody.<br>
have a good trip.<br>
to pass on greetings.<br>
there’s a restroom in the café.
</div>
<!-- Selectors d'idioma i pausa -->
<div>
<select id="voiceSelect">
<option value="">Selecciona idioma</option>
<option value="en-US">🇺🇸 Anglès (US)</option>
<option value="en-GB">🇬🇧 Anglès (UK)</option>
<option value="fr-FR">🇫🇷 Francès</option>
<option value="de-DE">🇩🇪 Alemany</option>
<option value="it-IT">🇮🇹 Italià</option>
<option value="pt-PT">🇵🇹 Portuguès</option>
<option value="es-ES">🇪🇸 Espanyol</option>
<option value="ca-ES">🎗️ Català</option>
</select>
<select id="pauseSelect">
<option value="1">⏱️ Pausa x1</option>
<option value="2">⏱️ Pausa x2</option>
<option value="3">⏱️ Pausa x3</option>
<option value="4">⏱️ Pausa x4</option>
<option value="10">⏱️ Pausa x10</option>
</select>
</div>
<!-- Botons de control -->
<button id="speakBtn" title="Escolta el text">🔊</button>
<button id="toggleBtn" title="Atura o reprèn">⏹️</button>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Referències als elements HTML
const editor = document.getElementById('editor');
const speakBtn = document.getElementById('speakBtn');
const toggleBtn = document.getElementById('toggleBtn');
const voiceSelect = document.getElementById('voiceSelect');
const pauseSelect = document.getElementById('pauseSelect');
// Variables de control
let voices = [];
let isSpeaking = false;
let liniesGuardades = [];
let currentIndex = 0;
// Carrega les veus disponibles
function loadVoices() {
voices = speechSynthesis.getVoices();
}
speechSynthesis.onvoiceschanged = loadVoices;
loadVoices();
// Recupera l'idioma seleccionat anteriorment
const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');
if (idiomaGuardat) {
voiceSelect.value = idiomaGuardat;
}
// Guarda l'idioma seleccionat
voiceSelect.addEventListener('change', () => {
localStorage.setItem('idiomaSeleccionat', voiceSelect.value);
});
// Obté les línies del text
function getLinies() {
const text = editor.innerText || editor.textContent;
return text.split('\n').map(l => l.trim()).filter(l => l);
}
// Marca visualment la línia que s'està llegint
function marcarLinia(index) {
const linies = editor.querySelectorAll('.linia');
linies.forEach((el, i) => {
el.classList.toggle('activa', i === index);
});
}
// Converteix el text en elements <span> per poder destacar línies
function prepararEditor() {
const linies = getLinies();
editor.innerHTML = '';
linies.forEach((text, i) => {
const span = document.createElement('span');
span.className = 'linia';
span.textContent = text;
editor.appendChild(span);
editor.appendChild(document.createElement('br'));
});
}
// Calcula la pausa entre línies segons el selector
function getPausaEntreLinies() {
const factor = parseInt(pauseSelect.value || '1');
return 500 * factor;
}
// Llegeix una línia i programa la següent
function speakLinia() {
if (currentIndex >= liniesGuardades.length) {
isSpeaking = false;
setTimeout(() => {
speakText(); // Reinicia automàticament
}, 2000);
return;
}
const linia = liniesGuardades[currentIndex];
const utterance = new SpeechSynthesisUtterance(linia);
const voice = voices.find(v => v.lang === voiceSelect.value);
if (voice) {
utterance.voice = voice;
utterance.lang = voice.lang;
}
utterance.rate = 1;
utterance.pitch = 1;
utterance.onstart = () => {
isSpeaking = true;
marcarLinia(currentIndex);
};
utterance.onend = () => {
currentIndex++;
setTimeout(() => {
speakLinia();
}, getPausaEntreLinies());
};
speechSynthesis.speak(utterance);
}
// Inicia la lectura des del principi
function speakText() {
if (isSpeaking) return;
prepararEditor();
liniesGuardades = getLinies();
currentIndex = 0;
speechSynthesis.cancel();
speakLinia();
}
// Botó 🔊 per iniciar la lectura
speakBtn.addEventListener('click', speakText);
// Botó ⏹️ per aturar o reprendre
toggleBtn.addEventListener('click', () => {
if (isSpeaking) {
speechSynthesis.cancel();
isSpeaking = false;
} else {
speakLinia();
}
});
});
</script>
</body>
</html>