🎙️ Voz MiniMax M3
Esta página prueba el micrófono (STT) y la voz (TTS) de tu navegador. Chrome y Edge funcionan. Firefox NO tiene STT. Safari solo TTS.
Listo.
Cargando voces…
Diagnóstico:
(function(){
const $ = id => document.getElementById(id);
const texto = $(‘texto’);
const status = $(‘status’);
const btnHablar = $(‘btnHablar’);
const btnLeer = $(‘btnLeer’);
const btnParar = $(‘btnParar’);
const btnProbar = $(‘btnProbar’);
const selVoces = $(‘voces’);
const vocesInfo = $(‘vocesInfo’);
function setStatus(msg, cls) {
status.textContent = msg;
status.className = cls || ”;
}
// Diagnóstico inicial
$(‘d_stt’).textContent = !!(window.SpeechRecognition || window.webkitSpeechRecognition) ? ‘SÍ’ : ‘NO’;
$(‘d_tts’).textContent = ‘speechSynthesis’ in window ? ‘SÍ’ : ‘NO’;
// ===== STT =====
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = null;
if (SR) {
recognition = new SR();
recognition.lang = ‘es-ES’;
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.continuous = false;
recognition.onresult = e => {
const t = e.results[0][0].transcript;
texto.value = (texto.value + ‘ ‘ + t).trim();
setStatus(‘Te he oído: “‘ + t + ‘”‘, ‘ok’);
btnLeer.disabled = false;
};
recognition.onerror = e => setStatus(‘Error STT: ‘ + e.error + ‘ — si es “not-allowed”, revisa los permisos del micrófono.’, ‘err’);
recognition.onend = () => { btnHablar.disabled = false; btnHablar.textContent = ‘🎤 Hablar (STT)’; };
btnHablar.addEventListener(‘click’, () => {
if (btnHablar.disabled) return;
btnHablar.disabled = true;
btnHablar.textContent = ‘🎤 Escuchando…’;
setStatus(‘Te escucho. Habla ahora.’, ‘info’);
try { recognition.start(); }
catch (e) { setStatus(‘No se pudo iniciar STT: ‘ + e.message, ‘err’); btnHablar.disabled = false; }
});
} else {
btnHablar.disabled = true;
btnHablar.title = ‘Tu navegador no soporta STT. Usa Chrome o Edge.’;
}
// ===== TTS =====
let voices = [];
let voicesReady = false;
let voicesReadyResolve = null;
const voicesReadyPromise = new Promise(r => { voicesReadyResolve = r; });
function loadVoices() {
return new Promise(resolve => {
const v = speechSynthesis.getVoices();
if (v && v.length > 0) { voices = v; resolve(v); return; }
// Algunos navegadores tardan; esperar al evento
const onChange = () => {
speechSynthesis.removeEventListener(‘onvoiceschanged’, onChange);
const v2 = speechSynthesis.getVoices();
voices = v2 || [];
resolve(v2 || []);
};
speechSynthesis.addEventListener(‘voiceschanged’, onChange);
// Timeout de seguridad: 1.5s
setTimeout(() => {
speechSynthesis.removeEventListener(‘onvoiceschanged’, onChange);
const v3 = speechSynthesis.getVoices();
voices = v3 || [];
resolve(v3 || []);
}, 1500);
});
}
// Orden: voces en español primero, luego el resto
function sortedVoices() {
const es = voices.filter(v => /^es/i.test(v.lang));
const others = voices.filter(v => !/^es/i.test(v.lang));
return […es, …others];
}
// Seleccionar voz por defecto inteligente
function pickBestVoice() {
const sv = sortedVoices();
if (!sv.length) return null;
// Preferir voces Microsoft en español, luego Google, luego la primera es
const preferNames = [‘Elvira’, ‘Helena’, ‘Laura’, ‘Paloma’, ‘Monica’, ‘Microsoft’];
for (const name of preferNames) {
const found = sv.find(v => v.name.toLowerCase().includes(name.toLowerCase()));
if (found) return found;
}
return sv[0];
}
function refreshVocesUI() {
const sv = sortedVoices();
selVoces.innerHTML = ”;
if (!sv.length) {
selVoces.innerHTML = ‘(sin voces disponibles — el TTS puede no funcionar)’;
$(‘d_es’).textContent = ‘0’;
$(‘d_total’).textContent = ‘0’;
return;
}
sv.forEach((v, i) => {
const opt = document.createElement(‘option’);
opt.value = i;
opt.textContent = `${v.name} (${v.lang})${v.default ? ‘ — predeterminada’ : ”}`;
selVoces.appendChild(opt);
});
// Marcar la mejor por defecto
const best = pickBestVoice();
if (best) {
const idx = sv.indexOf(best);
if (idx >= 0) selVoces.value = idx;
vocesInfo.textContent = `Mejor: ${best.name} (${best.lang})`;
}
const esCount = voices.filter(v => /^es/i.test(v.lang)).length;
$(‘d_es’).textContent = esCount;
$(‘d_total’).textContent = voices.length;
}
// Cargar voces al inicio y al evento
loadVoices().then(() => {
voicesReady = true;
voicesReadyResolve();
refreshVocesUI();
});
// Re-cargar si el navegador las entrega tarde
if (speechSynthesis.onvoiceschanged !== undefined) {
speechSynthesis.onvoiceschanged = () => loadVoices().then(refreshVocesUI);
}
// Función hablar robusta
function hablar(textoALeer) {
if (!(‘speechSynthesis’ in window)) {
setStatus(‘Tu navegador no soporta TTS.’, ‘err’);
return;
}
if (!textoALeer || !textoALeer.trim()) {
setStatus(‘No hay texto para leer.’, ‘err’);
return;
}
// Cancelar lo que esté sonando
try { speechSynthesis.cancel(); } catch (e) {}
const u = new SpeechSynthesisUtterance(textoALeer);
const sv = sortedVoices();
const idx = parseInt(selVoces.value || ‘0’, 10);
if (sv[idx]) {
u.voice = sv[idx];
u.lang = sv[idx].lang;
} else {
u.lang = ‘es-ES’;
}
u.rate = 0.95; // un pelín más lento, mejor para TEA
u.pitch = 1.0;
u.volume = 1.0;
u.onstart = () => { btnParar.disabled = false; setStatus(‘Hablando…’, ‘ok’); };
u.onend = () => { btnParar.disabled = true; setStatus(‘Terminado.’, ‘ok’); };
u.onerror = e => {
btnParar.disabled = true;
setStatus(‘Error TTS: ‘ + (e.error || ‘desconocido’) + ‘ — prueba otra voz del desplegable.’, ‘err’);
};
// Truco: algunos navegadores necesitan un pequeño delay tras cancel()
setTimeout(() => {
try {
speechSynthesis.speak(u);
} catch (e) {
setStatus(‘No se pudo hablar: ‘ + e.message, ‘err’);
}
}, 100);
}
btnLeer.addEventListener(‘click’, () => hablar(texto.value));
btnParar.addEventListener(‘click’, () => { try { speechSynthesis.cancel(); } catch (e) {} btnParar.disabled = true; setStatus(‘Parado.’); });
btnProbar.addEventListener(‘click’, () => hablar(‘Hola Jordi. Soy la IA MiniMax M3. Pulsa hablar y dime tu respuesta.’));
texto.addEventListener(‘input’, () => { btnLeer.disabled = !texto.value.trim(); });
})();
- STT disponible: ?
- TTS disponible: ?
- Voces en español: ?
- Voces totales: ?