Convirtiendo texto a voz usando Javascript
Conviertiendo texto a voz usando Javascript
No cabe duda, y podemos confirmar que a día de hoy Javascript es un lenguaje de programación muy potente que nos permite crear «casi» cualquier cosa.
Javascript junto a otras tecnologías a conseguido dar forma a lo que hoy es internet, pasando de un entorno «offline» a otro completamente «online».
El navegador web, esa aplicación que hasta hace unos años era una más, hoy se ha convertido en la «aplicación» casi indispensable y necesaria para realizar gran parte de nuestro trabajo.
Junto a la evolución del propio lenguaje, el navegador web ha ido evolucionando ofreciendo características que hasta hace unos años ni si quiera se hubieran planteado.
Esa evolución se debe en parte a las mejoras que se han ido introduciendo en la API del navegador.
Algunas de esas características todavía están en fase experimental y en algunos casos solo con soporte para navegadores como Google Chrome y/o Mozilla Firefox.
Una de esas características es la Web Speech API, la cual nos permite añadir voz a nuestro navegador pudiendo escuchar cualquier texto exponiendo la interface SpeechSynthesis.
Escuchando a nuestro navegador
Ya que este ejemplo es muy sencillo voy a usar HTML, Javascript y CSS sin apoyarme en ningún framework o librería, pues me interesa mostrar la configuración y el flujo mínimo necesario para escuchar a nuestro navegador.
Para nuestro ejemplo podemos usar el siguiente código
index.html
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TEXT TO VOICE - JAVASCRIPT</title> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous" /> <link rel="stylesheet" href="./css/index.css"> </head> <body> <div class="container"> <input type="text" placeholder="Texto to Speech" id="text"> <div class="conversation"></div> <div class="container-button"> <select id="voices"></select> <button id="btnSave"><i class="fas fa-upload"></i>Save text</button> <button id="btnLoad" disabled><i class="fas fa-download"></i>Load text</button> <button id="btnPlay" disabled><i class="fas fa-play-circle"></i>Play text</button> </div> </div> <div class="notification"></div> <script src="./js/index.js"></script> </body> </html>
index.css
body { font-family: 'Montserrat', sans-serif; overflow: hidden; } input[type='text'] { border: 0; border-bottom: 1px solid #cccccc; width: calc(100% - 20px); margin: 10px 0; padding: 10px; } button, select { border: 0; padding: 10px; background: #3988f2; color: #ffffff; box-shadow: 0 0 3px 1px rgb(0, 0, 0, 0.2); cursor: pointer; } button:focus, input:focus { outline: -webkit-focus-ring-color auto 0px; } button[disabled] { background: #aaaaaa; color: #000000; cursor: not-allowed; } button i { margin-right: 10px; } select { padding: 9px; } .conversation { width: calc(100% - 20px); height: 300px; box-shadow: 0 0 7px 2px rgb(0, 0, 0, 0.2); margin: 15px 0; padding: 10px; overflow: auto; } .container-button { text-align: right; } .notification { width: 200px; padding: 10px; border-radius: 2px; position: absolute; left: calc(50% - 100px); bottom: -100px; transition: 450ms ease-in-out; text-align: center; line-height: 25px; } .notification.show { bottom: 50px; transition: 450ms ease-in-out; } .info { background: #3988f2; color: #ffffff; } .warning { background: #fe8c8c; color: #000000; } .success { background: #60c356; color: #000000; }
Bien, de momento con los dos ficheros anteriores solo hemos creado la vista de nuestra pequeña aplicación.
A continuación veremos la lógica que nos permite escuchar a nuestro navegador.
Inicializando nuestra voz en Javascript
Como la finalidad de este proyecto es sentar las bases solo usare alguno de los métodos que ofrece esta API del navegador, y el resto lo dejamos a la imaginación de cada uno.
Lo primero que haremos es comprobar que nuestro navegador tiene soporte para esta API.
if (!'speechSynthesis' in window) { showNotify({ msn: 'Your browser not support tha Web Speech API', show: true, type: 'success' }); return false; }
Ahora vamos a obtener la interface de la API, ya que necesitaremos manipular algunas de sus propiedades
let voice = new SpeechSynthesisUtterance();
A continuación guardaremos el objeto que nos permite acceder a la funcionalidad de la API.
let jarvis = window.speechSynthesis;
Bien, pues con estas dos líneas de código ya tendríamos preparado nuestro navegador para que nos hable.
Añadiendo un poco de lógica
Con las dos líneas anteriores ya tenemos preparado todo, pero claro…, antes de hacer la magia vamos a explicar algunos conceptos básicos.
Usando el objeto que almacenamos en la variable jarvis podemos hacer algunas cosas bastante curiosas usando algunos de los métodos que tiene
-
- cancel(): Elimina cualquier reproducción que este en curso.
- getVoices(): Nos devuelve un listado de todas las voces que soporta el dispositivo.
- pause(): Pausa la reproducción.
- resume(): Continua con la reproducción.
- speak(): Añade un nuevo texto a la cola de reproducción quedando a la espera según vaya vaciando la cola.
Como sucede con el objeto almacenado en jarvis, ahora vamos a ver que podemos hacer por ejemplo con las propiedades del interface almacenado en voice.
-
- interface.lang: Podemos modificar y obtener el idioma.
- interface.pitch: Podemos modificar y obtener el tono de la voz.
- interface.rate: Podemos modificar y obtener la velocidad de la voz.
- interface.text: Podemos modificar y obtener el texto que se convierte a voz.
- interface.voice: Podemos modificar y obtener la voz.
- interface.volume: Podemos modificar y obtener el volumen de la voz.
Ahora si!!!!, vamos a ampliar nuestro Javascript.
const init = () => { // Comprobamos si tenemos soporte en nuestro navegador if (!'speechSynthesis' in window) { showNotify({ msn: 'Your browser not support tha Web Speech API', show: true, type: 'success' }); return false; } // Interface de la API let voice = new SpeechSynthesisUtterance(); // Objeto de la API let jarvis = window.speechSynthesis; let disabledPlay = true; const store = window.localStorage.getItem('dataSave') || ''; const text = document.querySelector('#text'); const btnSave = document.querySelector('#btnSave'); const btnLoad = document.querySelector('#btnLoad'); const btnPlay = document.querySelector('#btnPlay'); const renderText = document.querySelector('.conversation'); const select = document.getElementById("voices"); // Controlamos el botón para cargar datos del localstorage btnLoad[store ? 'removeAttribute' : 'setAttribute']('disabled', true); // Observamos la propiedad 'innerHTML' para observar los cambios que se producen const SET = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set; const handler = { set(value) { const hasConversation = Object.values(this.classList).includes('conversation') && disabledPlay; if (hasConversation) { btnPlay.removeAttribute('disabled'); disabledPlay = false; } return SET.call(this, value); } }; Object.defineProperty(Element.prototype, 'innerHTML', handler); text.addEventListener('keydown', function({ code }) { if (['Enter', 'NumpadEnter'].includes(code)) { const lastWord = this.value.split('\n'); renderText.innerHTML += `<div>${this.value}</div>`; this.value = ''; // Convertimos el texto a voz playVoice(lastWord[lastWord.length - 1]); } }); btnSave.addEventListener('click', () => { showNotify({ msn: 'The text has been saved success', show: true, type: 'success' }); const dataSave = renderText.innerHTML; // Guardamos los datos en el localStorage storeData(dataSave); }); btnLoad.addEventListener('click', () => { showNotify({ msn: 'The text has been loaded success', show: true, type: 'success' }); // Recuperamos los daots del localStorage const text = storeData('', false); renderText.innerHTML = text; }); btnPlay.addEventListener('click', () => { // Reproducimos la voz pauseVoice(); stopVoice(); showNotify({ msn: 'The text is playing', show: true, type: 'success' }); playVoice(renderText.innerHTML); }); const playVoice = text => { // Reproduce la voz voice.text = text; jarvis.speak(voice); }; const stopVoice = () => { // Cancela la reproducción de la voz jarvis.cancel() }; const pauseVoice = () => { // Pausa la reproducción de la voz jarvis.resume(); } // Obtenemos todas las voces soportadas const getVoices = function() { const voices = jarvis.getVoices(); voices.forEach(item => { const { name, lang } = item; const option = document.createElement('option'); option.textContent = `${name} - [${lang}]`; option.setAttribute('data-language', lang); option.setAttribute('data-name', name); select.appendChild(option); }); voice.lang = this.selectedOptions?.[0]?.dataset.language.split('-')[0] || 'es'; }; getVoices(); jarvis.onvoiceschanged = getVoices; select.addEventListener('input', getVoices); // Función extra, no es necesario para el funcionamiento const storeData = (data = '', save = true) => { return window.localStorage[save ? 'setItem' : 'getItem']('dataSave', data); }; }; // Mostra una notificación const showNotify = ({ msn = '', duration = 3000, show = false, type = '' }) => { if (show) { const notification = document.querySelector('.notification'); const bgNotification = ['info', 'warning', 'success'].includes(type) ? type : 'info'; notification.innerHTML = msn; notification.classList.add('show', bgNotification); setTimeout(() => { notification.classList.remove('show'); }, duration); } }; document.addEventListener('DOMContentLoaded', init);
Con esto tendríamos una base para entender como funciona y las posibilidades que nos ofrece esta API.
En el código he añadido la opción de guardar y cargar todo el texto que vamos escribiendo en el localStorage, por si en un futuro quisiéramos escuchar el texto que escribimos.
Para finalizar, en Github puedes encontrar el repositorio y desde aquí puedes acceder al ejemplo.