🔊 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é.
📌 Selecciona un idioma i prem “🔊” per veure quina veu s’utilitza.
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Caixa Comodí - Text a veu</title>
<link rel="icon" href="calculadora.ico" type="image/x-icon">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: flex-start;
background-color: #f0f0f0;
height: 100vh;
}
.container {
background-color: rgb(213, 225, 225);
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
max-width: 440px;
width: 100%;
height: 60%;
margin-top: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
h2 {
text-align: center;
color: #333;
margin: 0 0 15px 0;
font-size: 1.4em;
}
#editor {
font-size: 1.3em;
padding: 8px;
margin: 8px 0;
width: 100%;
height: 320px;
overflow-y: auto;
border: 1px solid #ccc;
white-space: pre-wrap;
background: white;
border-radius: 5px;
box-sizing: border-box;
}
select {
font-size: 1em;
padding: 6px;
margin: 6px 4px 0 0;
width: calc(50% - 8px);
height: 36px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
}
#debugInfo {
margin: 10px 0;
padding: 8px;
background: #fff;
border-radius: 5px;
font-size: 0.9em;
color: #333;
border: 1px solid #ccc;
}
.controls {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 12px;
}
#speakBtn, #toggleBtn {
font-size: 24px;
background: #007bff;
color: white;
border: none;
border-radius: 8px;
width: 50px;
height: 50px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
#speakBtn:hover, #toggleBtn:hover {
background: #0056b3;
}
.linia {
display: block;
padding: 0.2em;
transition: background-color 0.3s ease;
}
.activa {
background-color: hsl(180, 1%, 72%);
}
</style>
</head>
<body>
<div class="container">
<h2>🔊 Text a veu</h2>
<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>
<div>
<select id="voiceSelect">
<option value="">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>
<!-- 🔍 Indicador de diagnòstic -->
<div id="debugInfo">
📌 Selecciona un idioma i prem "🔊" per veure quina veu s'utilitza.
</div>
<div class="controls">
<button id="speakBtn" title="Escolta el text">🔊</button>
<button id="toggleBtn" title="Atura o reprèn">⏹️</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
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');
const debugInfo = document.getElementById('debugInfo');
let voices = [];
let isSpeaking = false;
let liniesGuardades = [];
let currentIndex = 0;
function loadVoices() {
voices = speechSynthesis.getVoices();
// Opcional: mostra totes les veus disponibles al carregar (per depuració)
// console.log('Veus disponibles:', voices.map(v => `${v.name} (${v.lang})`));
}
speechSynthesis.onvoiceschanged = loadVoices;
loadVoices();
const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');
if (idiomaGuardat) {
voiceSelect.value = idiomaGuardat;
}
voiceSelect.addEventListener('change', () => {
localStorage.setItem('idiomaSeleccionat', voiceSelect.value);
// Actualitzem el missatge quan es canvia l'idioma
debugInfo.textContent = `📌 Idioma seleccionat: ${voiceSelect.options[voiceSelect.selectedIndex].text}`;
});
function getLinies() {
const text = editor.innerText || editor.textContent;
return text.split('\n').map(l => l.trim()).filter(l => l);
}
function marcarLinia(index) {
const linies = editor.querySelectorAll('.linia');
linies.forEach((el, i) => {
el.classList.toggle('activa', i === index);
});
}
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'));
});
}
function getPausaEntreLinies() {
const factor = parseInt(pauseSelect.value || '1');
return 500 * factor;
}
// 🔍 Funció per trobar la millor veu i mostrar informació
function getBestVoiceAndReport(langCode) {
if (!langCode) {
debugInfo.innerHTML = `ℹ️ Cap idioma seleccionat. S'utilitzarà la veu per defecte.`;
return null;
}
// Normalitzem per a comparacions flexibles
const normalizedTarget = langCode.replace('-', '_').toLowerCase();
// 1. Cerca exacta (amb normalització)
let best = voices.find(v =>
v.lang.replace('-', '_').toLowerCase() === normalizedTarget
);
if (best) {
debugInfo.innerHTML = `✅ Idioma demanat: <strong>${langCode}</strong><br>🗣️ Veus trobada: <strong>${best.name}</strong> (${best.lang})`;
return best;
}
// 2. Cerca per prefix (p. ex., "ca" per a "ca-ES")
const prefix = langCode.split('-')[0].toLowerCase();
best = voices.find(v => v.lang.toLowerCase().startsWith(prefix));
if (best) {
debugInfo.innerHTML = `⚠️ Idioma demanat: <strong>${langCode}</strong><br>🗣️ Veus aproximada: <strong>${best.name}</strong> (${best.lang})`;
return best;
}
// 3. No s'ha trobat cap veu
debugInfo.innerHTML = `❌ Idioma demanat: <strong>${langCode}</strong><br>🚫 Cap veu trobada. Comprova que tens l'idioma instal·lat a Android.`;
return null;
}
function speakLinia() {
if (currentIndex >= liniesGuardades.length) {
isSpeaking = false;
setTimeout(() => {
speakText();
}, 2000);
return;
}
const linia = liniesGuardades[currentIndex];
const utterance = new SpeechSynthesisUtterance(linia);
const selectedLang = voiceSelect.value;
const voice = getBestVoiceAndReport(selectedLang);
if (voice) {
utterance.voice = voice;
}
utterance.rate = 1;
utterance.pitch = 1;
utterance.onstart = () => {
isSpeaking = true;
marcarLinia(currentIndex);
};
utterance.onend = () => {
currentIndex++;
setTimeout(() => {
speakLinia();
}, getPausaEntreLinies());
};
speechSynthesis.speak(utterance);
}
function speakText() {
if (isSpeaking) return;
prepararEditor();
liniesGuardades = getLinies();
currentIndex = 0;
speechSynthesis.cancel();
speakLinia();
}
speakBtn.addEventListener('click', speakText);
toggleBtn.addEventListener('click', () => {
if (isSpeaking) {
speechSynthesis.cancel();
isSpeaking = false;
} else {
speakLinia();
}
});
});
</script>
</body>
</html>
Simple text to speak
Per afegir aquest codi de sota cal usar el connector Enlighter, afegir un bloc a la entrada amb el mateix nom i descarregat el codi del porta-retalls
<!DOCTYPE html>
<!-- Declara que aquest document és HTML5 -->
<html lang="ca">
<!-- L'idioma principal del contingut és el català -->
<head>
<meta charset="UTF-8">
<!-- Assegura la correcta visualització de caràcters especials i emojis -->
<title>Conversor de text a veu</title>
<!-- Títol que apareix a la pestanya del navegador -->
<style>
/* Estils generals de la pàgina */
body {
font-family: sans-serif; /* Tipus de lletra llegible */
padding: 2em; /* Espai al voltant del contingut */
background-color: hsl(194, 91%, 91%); /* Fons blau clar suau */
}
/* Estil comú per al camp de text i el selector d'idioma */
input, select {
font-size: 1.2em; /* Mida de lletra una mica més gran */
padding: 0.5em; /* Espai interior dins dels camps */
margin-top: 1em; /* Separació superior */
width: 100%; /* Ocupen tot l'ample disponible */
max-width: 500px; /* Però no més de 500px */
}
/* Estil del botó d'altaveu (🔊) */
#speakBtn {
font-size: 2em; /* Molt gran per facilitar la interacció */
background: none; /* Sense fons */
border: none; /* Sense vores */
cursor: pointer; /* Cursor en forma de mà en passar-hi */
margin-top: 1em; /* Separació respecte als elements anteriors */
}
/* Efecte visual quan l'usuari passa el ratolí pel botó */
#speakBtn:hover {
color: #c8e3f8; /* Canvia a un blau més clar */
}
</style>
</head>
<body>
<!-- Títol visible de la pàgina amb un emoji d'altaveu -->
<h2>🔊 Conversor de text a veu</h2>
<!-- Camp on l'usuari escriu el text a sintetitzar -->
<input type="text" id="cercar" placeholder="Escriu el text que vols escoltar">
<!-- Selector per triar la veu/idioma desitjat -->
<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>
<!-- Els valors (value) són codis d'idioma estàndard BCP 47 -->
</select>
<!-- Botó per iniciar la síntesi de veu -->
<button id="speakBtn" title="Escolta el text">🔊</button>
<script>
// S'executa quan tot el contingut HTML ha carregat
document.addEventListener('DOMContentLoaded', () => {
// Referències als elements HTML que necessitarem
const inputElement = document.getElementById('cercar'); // Camp de text
const speakBtn = document.getElementById('speakBtn'); // Botó d'altaveu
const voiceSelect = document.getElementById('voiceSelect'); // Selector d'idioma
let voices = []; // Array per emmagatzemar les veus disponibles
// Funció per carregar la llista de veus del sistema
function loadVoices() {
voices = speechSynthesis.getVoices();
// Aquesta API pot trigar a carregar les veus en alguns navegadors
}
// Assegurem-nos que les veus es carreguin, fins i tot si arriben més tard
speechSynthesis.onvoiceschanged = loadVoices;
loadVoices(); // Intentem carregar-les ja des del principi
// Funció principal: converteix el text a veu
function speakText() {
const text = inputElement.value.trim(); // Obtenim i netegem el text
if (!text) return; // Si està buit, no fem res
const selectedLang = voiceSelect.value; // Idioma seleccionat per l'usuari
const utterance = new SpeechSynthesisUtterance(text); // Creem l'objecte de parla
// Busquem una veu que coincideixi exactament amb l'idioma triat
const voice = voices.find(v => v.lang === selectedLang);
if (voice) {
// Si la trobem, l'assignem
utterance.voice = voice;
utterance.lang = voice.lang;
} else {
// Si no, mostrem un avís a la consola (per depuració)
console.log('No s\'ha trobat cap veu per a ' + selectedLang);
// Nota: l'API pot usar una veu per defecte si no s'assigna cap
}
// Paràmetres opcionals de parla
utterance.rate = 1; // Velocitat normal (1 = 100%)
utterance.pitch = 1; // To normal
// Cancel·lem qualsevol parla anterior per evitar solapaments
speechSynthesis.cancel();
// Iniciem la nova síntesi de veu
speechSynthesis.speak(utterance);
}
// Quan l'usuari fa clic al botó, es llegeix el text
speakBtn.addEventListener('click', speakText);
// ⚠️ Aquesta línia fa que el text es llegeixi AUTOMÀTICAMENT cada 10 segons
// Pot ser útil per a demostracions, però pot resultar molest en ús normal
setInterval(speakText, 10000);
});
</script>
</body>
</html>