Aprende

Texto a voz con JavaScript y SpeechSynthesis

Rene, Kabir Goel 
Texto a voz con JavaScript y SpeechSynthesis

Para convertir texto en voz con JavaScript, crea un SpeechSynthesisUtterance y pásalo a window.speechSynthesis.speak(). El navegador se encarga de reproducirlo. No necesitas una clave de API, un servidor ni un paquete de JavaScript.

Este enfoque funciona bien para un prototipo o un control de lectura en voz alta si te sirven las voces del dispositivo. Si desarrollas un agente de voz que necesita la misma voz en todos los dispositivos o transmitir audio a una llamada telefónica, usa una API de texto a voz alojada. SpeechSynthesis del navegador y la API de Cartesia son formas distintas de generar voz.

Cómo funciona la síntesis de voz del navegador

window.speechSynthesis es el controlador de voz sintetizada de la API Web Speech. Un enunciado contiene el texto y los ajustes de una solicitud. Al llamar a speak(), el enunciado se añade a una cola. No se devuelve un archivo de audio.1

El navegador expone las voces disponibles en el dispositivo actual mediante getVoices(). Esa lista puede cargarse después de ejecutar el script. Consúltala de inmediato y actualízala cuando se active voiceschanged.2 No fijes en el código el nombre de una voz de tu portátil esperando que también exista en el teléfono de un cliente.

1. Guarda una demo funcional de texto a voz

Guarda lo siguiente como speech.html y ábrelo en un navegador. Introduce una frase breve, elige una voz si el navegador muestra alguna y pulsa Speak. Usa texto de ejemplo que no sea confidencial. Algunas voces utilizan servicios remotos.3

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>JavaScript text-to-speech demo</title>
	</head>
	<body>
		<h1>Read text aloud</h1>
		<p>
			<label for="text">Text to speak</label><br />
			<textarea id="text" rows="4" cols="30" maxlength="500">
Hello from your browser.</textarea
			>
		</p>
		<p>
			<label for="voice">Voice</label>
			<select id="voice"></select>
		</p>
		<button id="speak" type="button">Speak</button>
		<button id="stop" type="button">Stop</button>
		<p id="status" role="status" aria-live="polite">Ready.</p>

		<script>
			const text = document.querySelector("#text");
			const voiceSelect = document.querySelector("#voice");
			const speakButton = document.querySelector("#speak");
			const stopButton = document.querySelector("#stop");
			const status = document.querySelector("#status");
			let voices = [];
			let currentUtterance = null;

			if (
				!("speechSynthesis" in window) ||
				!("SpeechSynthesisUtterance" in window)
			) {
				status.textContent = "This browser does not support speech synthesis.";
				speakButton.disabled = true;
				stopButton.disabled = true;
			} else {
				const synth = window.speechSynthesis;

				function loadVoices() {
					const previousVoice = voiceSelect.value;
					voices = synth.getVoices();
					voiceSelect.replaceChildren(new Option("Browser default", ""));
					for (const voice of voices) {
						voiceSelect.add(
							new Option(`${voice.name} (${voice.lang})`, voice.voiceURI),
						);
					}
					if (voices.some((voice) => voice.voiceURI === previousVoice)) {
						voiceSelect.value = previousVoice;
					}
				}

				loadVoices();
				synth.addEventListener("voiceschanged", loadVoices);

				speakButton.addEventListener("click", () => {
					const transcript = text.value.trim();
					if (!transcript) {
						status.textContent = "Enter some text first.";
						return;
					}

					// Ignore callbacks from the utterance being replaced.
					currentUtterance = null;
					synth.cancel();
					const utterance = new SpeechSynthesisUtterance(transcript);
					const voice = voices.find(
						(item) => item.voiceURI === voiceSelect.value,
					);
					if (voice) {
						utterance.voice = voice;
						utterance.lang = voice.lang;
					} else {
						utterance.lang = document.documentElement.lang;
					}
					utterance.rate = 1;
					currentUtterance = utterance;
					status.textContent = "Starting speech...";
					utterance.onstart = () => {
						if (currentUtterance === utterance)
							status.textContent = "Speaking...";
					};
					utterance.onend = () => {
						if (currentUtterance !== utterance) return;
						currentUtterance = null;
						status.textContent = "Finished.";
					};
					utterance.onerror = (event) => {
						if (currentUtterance !== utterance) return;
						currentUtterance = null;
						status.textContent = `Speech failed: ${event.error}`;
					};
					synth.speak(utterance);
				});

				stopButton.addEventListener("click", () => {
					currentUtterance = null;
					synth.cancel();
					status.textContent = "Stopped.";
				});
			}
		</script>
	</body>
</html>

La demo limita la entrada a 500 caracteres para que la primera prueba sea breve. Es un límite de la demo, no de la API Web Speech. Prueba pasajes más largos por separado en cada navegador compatible con tu producto.

2. Comprueba la selección de voz y la reproducción

El selector muestra el nombre y el idioma de cada voz disponible. Elige una voz que coincida con el idioma del texto. Configurar utterance.lang no traduce las palabras.

Speak se ejecuta al pulsar un botón, en lugar de al cargar la página. Así el oyente tiene el control y no dependes de la reproducción automática. Pulsar Speak de nuevo cancela la solicitud actual antes de iniciar otra, en lugar de añadir cada clic a la cola. Stop llama a cancel(), que elimina los enunciados pendientes y detiene el habla actual.4

La comprobación de currentUtterance evita que una devolución de llamada tardía de una solicitud cancelada sobrescriba el estado de otra más reciente. Si adaptas este código a un componente, elimina el listener de voiceschanged cuando se desmonte. Cancela la reproducción al desmontarlo solo si ese componente controla la cola de voz de la página.

3. Resuelve problemas de silencio o habla inconsistente

SíntomaQué comprobar
El selector solo muestra Browser defaultEspera a voiceschanged. Si la lista sigue vacía, comprueba la compatibilidad del navegador y las voces instaladas. La demo aún puede solicitar una voz predeterminada.
No se oye nada al pulsar SpeakComprueba el volumen y la salida de audio, prueba una frase breve en un idioma compatible y lee el mensaje de estado por si hay un error de síntesis.
Una voz existe en el ordenador, pero no en el móvilLas listas dependen del dispositivo. Conserva una opción predeterminada y prueba los sistemas operativos reales que admites.
Varios clics seguidos retrasan el hablaspeak() encola solicitudes. Cancela primero la anterior si el texto nuevo debe sustituirla.
El habla falla sin conexión a internetLa voz seleccionada puede usar un servicio remoto. Inspecciona voice.localService y prueba sin conexión en vez de asumir síntesis local.

En una función de lectura en voz alta, conserva visible el texto original y permite acceder a Stop con el teclado. No inicies el habla automáticamente ni trates la voz sintetizada como un sustituto de la compatibilidad con lectores de pantalla.

Cuándo usar una API de TTS alojada

Elige la voz del navegador cuando basten las voces disponibles del oyente y solo necesites reproducir audio en ese dispositivo. Elige una API alojada cuando tu aplicación necesite bytes de audio generado, una identidad de voz gestionada por el proveedor o entregar audio fuera del navegador.

RequisitoSpeechSynthesis del navegadorAPI de TTS alojada como Cartesia
CredencialesNo necesita clave de API de la aplicaciónCredenciales de API y una cuenta con acceso de uso
Selección de vozLista de voces del navegador y del sistema operativoBiblioteca del proveedor o voz personalizada autorizada
Destino del audioReproducción en el navegadorLa aplicación recibe audio para reproducir, guardar o transportar
Comportamiento sin conexiónDepende de la voz y del dispositivoLas solicitudes a la API de Cartesia necesitan conexión a la red
Tratamiento del textoLocal o remoto, según la vozEl texto se envía al proveedor; revisa los requisitos de datos antes de integrar

Para usar la síntesis de voz de Cartesia, llama a la API desde tu backend. Mantén ahí la clave de API, fuera de archivos HTML públicos y del código del cliente. Una aplicación de navegador puede enviar texto a tu backend autenticado, que aplica límites de entrada y devuelve audio al cliente. Limita la frecuencia de solicitudes a ese endpoint para que los visitantes no consuman tus créditos de API sin límite.

Empieza por generar un archivo WAV con el SDK de Python de Cartesia. Esa guía prueba la generación alojada; no es un reproductor de streaming para navegador. Una aplicación conversacional también necesita búferes de reproducción y gestión de interrupciones. Compara ese trabajo con Cartesia Managed Agents antes de desarrollar tú todo el flujo del agente de voz.

Referencias relacionadas

Notas al pie

  1. MDN, SpeechSynthesis. ↩

  2. MDN, SpeechSynthesis.getVoices(). ↩

  3. MDN, SpeechSynthesisVoice.localService. ↩

  4. MDN, SpeechSynthesis.cancel(). ↩

Preguntas frecuentes

¿Puede JavaScript convertir texto a voz sin una clave de API?

Sí. En los navegadores compatibles, window.speechSynthesis pronuncia texto con las voces disponibles en el dispositivo. No necesitas una cuenta ni una clave de API de Cartesia para este ejemplo. La disponibilidad y el comportamiento de las voces varían según el navegador y el sistema operativo.

¿Por qué speechSynthesis.getVoices() devuelve un array vacío?

Puede que el navegador aún no haya cargado las voces. Llama a getVoices al iniciar y escucha voiceschanged para actualizar la lista. Si sigue vacía, comprueba la compatibilidad de voz del navegador y las voces instaladas en el sistema operativo.

¿Puede SpeechSynthesis guardar un archivo MP3 o WAV?

SpeechSynthesis reproduce voz mediante el navegador. No ofrece una API que devuelva el audio generado como archivo o flujo de bytes. Usa un servicio de voz con un endpoint de salida de audio si tu aplicación necesita guardar o transmitir el resultado.

¿Funciona sin conexión la síntesis de voz del navegador?

Depende de la voz. SpeechSynthesisVoice.localService indica si procede de un servicio de voz local o remoto. No asumas que todas las voces del navegador funcionan sin conexión o mantienen el texto en el dispositivo. Verifica la voz elegida en los dispositivos previstos.