En este post voy a explicar cómo grabar audio del micrófono con JavaScript, MediaRecorder y la API de getUserMedia.
Al final seremos capaces de:
Todo eso sin usar plugins, extensiones ni esas cosas: se utiliza JavaScript puro.
Aquí voy a explicar las cosas más importantes, pero el código actualizado puede verse en mi GitHub.
En caso de que quieras probar la aplicación final, visita este enlace.
Nota: recuerda que ya vimos cómo tomar una foto con JavaScript y la cámara web para descargar, o para enviarla a PHP.
Para obtener la lista de dispositivos llamamos a navigator.mediaDevices.enumerateDevices
que devolverá una promesa que, al ser resuelta, traerá la lista.
Esta lista tiene todos los dispositivos pero a nosotros solo nos importan aquellos cuyo tipo (kind
) sea de audioinput; hacemos un pequeño filtro y ponemos los dispositivos de grabación en un select o lista desplegable.
// Consulta la lista de dispositivos de entrada de audio y llena el select
const llenarLista = () => {
navigator
.mediaDevices
.enumerateDevices()
.then(dispositivos => {
limpiarSelect();
dispositivos.forEach((dispositivo, indice) => {
if (dispositivo.kind === "audioinput") {
const $opcion = document.createElement("option");
// Firefox no trae nada con label, que viva la privacidad
// y que muera la compatibilidad
$opcion.text = dispositivo.label || `Dispositivo ${indice + 1}`;
$opcion.value = dispositivo.deviceId;
$listaDeDispositivos.appendChild($opcion);
}
})
})
};
En algunos casos el dispositivo no tiene label
, así que simplemente usamos las plantillas de cadena e indicamos “Dispositivo X”.
El punto importante es tener el deviceId
, ya que ese es pasado cuando se llama a getUserMedia
.
Para grabar el audio creamos un MediaRecorder, su constructor acepta un stream que obtenemos con getUserMedia
.
getUserMedia
acepta un parámetro para que le indiquemos el dispositivo con el que vamos a grabar, el cual es el deviceId
obtenido desde la lista desplegable.
const comenzarAGrabar = () => {
if (!$listaDeDispositivos.options.length) return alert("No hay dispositivos");
// No permitir que se grabe doblemente
if (mediaRecorder) return alert("Ya se está grabando");
navigator.mediaDevices.getUserMedia({
audio: {
deviceId: $listaDeDispositivos.value,
}
})
.then(
stream => {
// Comenzar a grabar con el stream
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
comenzarAContar();
// En el arreglo pondremos los datos que traiga el evento dataavailable
const fragmentosDeAudio = [];
// Escuchar cuando haya datos disponibles
mediaRecorder.addEventListener("dataavailable", evento => {
// Y agregarlos a los fragmentos
fragmentosDeAudio.push(evento.data);
});
}
)
.catch(error => {
// Aquí maneja el error, tal vez no dieron permiso
console.log(error)
});
};
En la llamada a getUserMedia
se pedirá permiso al usuario, si el usuario accede y cuenta con el hardware de grabación entonces se resuelve la promesa; en caso de que no, se rechaza y manejamos el error en catch
.
El verdadero comienzo de la grabación es en mediaRecorder.start
(la variable es global, y no te confundas, es la variable, no la clase MediaRercorder
).
La variable va a emitir el evento de dataavailable
(data available, datos disponibles) el cual escuchamos. Dicho evento trae datos (que son la grabación, bueno, pedazos de ella) que colocamos en un arreglo.
Para detener la grabación llamamos a mediaRecorder.stop()
, eso desencadenará el evento stop
, el cual escuchamos y dentro del mismo:
Eso lo hace el siguiente código:
// Cuando se detenga (haciendo click en el botón) se ejecuta esto
mediaRecorder.addEventListener("stop", () => {
// Detener el stream
stream.getTracks().forEach(track => track.stop());
// Detener la cuenta regresiva
detenerConteo();
// Convertir los fragmentos a un objeto binario
const blobAudio = new Blob(fragmentosDeAudio);
// Crear una URL o enlace para descargar
const urlParaDescargar = URL.createObjectURL(blobAudio);
// Crear un elemento <a> invisible para descargar el audio
let a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = urlParaDescargar;
a.download = "grabacion_parzibyte.me.webm";
// Hacer click en el enlace
a.click();
// Y remover el objeto
window.URL.revokeObjectURL(urlParaDescargar);
});
Para mostrar el tiempo que se lleva grabado puse un contador que muestra el tiempo de inicio de la grabación, el mismo se resetea cuando se descarga el audio.
const segundosATiempo = numeroDeSegundos => {
let horas = Math.floor(numeroDeSegundos / 60 / 60);
numeroDeSegundos -= horas * 60 * 60;
let minutos = Math.floor(numeroDeSegundos / 60);
numeroDeSegundos -= minutos * 60;
numeroDeSegundos = parseInt(numeroDeSegundos);
if (horas < 10) horas = "0" + horas;
if (minutos < 10) minutos = "0" + minutos;
if (numeroDeSegundos < 10) numeroDeSegundos = "0" + numeroDeSegundos;
return `${horas}:${minutos}:${numeroDeSegundos}`;
};
// Ayudante para la duración; no ayuda en nada pero muestra algo informativo
const comenzarAContar = () => {
tiempoInicio = Date.now();
idIntervalo = setInterval(refrescar, 500);
};
const refrescar = () => {
$duracion.textContent = segundosATiempo((Date.now() - tiempoInicio) / 1000);
}
No quiero meterme con formatos de audio pero todo esto tiene que ver con los códecs libres y esas cosas; el formato audio/webm funciona en los navegadores que probé y por eso se descarga con esa extensión.
Así podemos grabar un audio con JavaScript desde cualquier micrófono y descargar el archivo de la grabación. Funciona (lo he probado) en:
Recuerda que puedes ver la demostración en este enlace, y el código (ya que no puse el HTML) en mi GitHub.
Ah, cabe mencionar que getUserMedia funciona solo en localhost o en servidores https.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
Ver comentarios
los botones de comenzar la grabacion no funcionan. solo funcionan en samsung galazy s7
La mayoría de ocasiones lo más importante es que el teléfono tenga la versión más reciente del sistema operativo y utilice un navegador web actualizado, por ejemplo, Google Chrome. También recuerde que todo debe hacerse por https
Buena info, gracias por el aporte. Quisiera consultar, cómo se hace para enviar audio por websocket con socket.io de nodeJs y recibirlo en otro cliente?
Tuviste alguna solución que me puedas compartir?
Como se realizaria si la grabacion que obtengo quiero transcribirlo en un textarea en el html, que funciones mas debo agregar para convertir lo que grabo a texto? ,gracias
https://parzibyte.me/blog/2019/12/01/reconocimiento-voz-web/
Me encanto tu post tengo una duda, como le haría para hacer lo mismo pero con una web cam, te explico ósea grabar el video junto con el audio y poder descargarlo de la misma manera que el audio?
Como podría detectar la resolución de la web cam?
Gracias!
https://parzibyte.me/blog/2019/06/03/grabar-video-camara-javascript/
Muy bueno el post. Me ha resultado de mucha ayuda. Gracias!
Hola,
Muchísimas gracias por tu código. Sería muy bueno que completaras añadiendo como hacemos que se descargue en mp3. Gracias !
Gracias por sus comentarios. Le invito a seguirme y compartir.
Sobre su consulta; al momento de escribir esto, es imposible descargarlo como mp3.
Saludos :)
Hola, muchisimas gracias por tu aportación, te comento que soy nuevo en esto, quisiera saber si existe una forma de que se guarde el audio en alguna ruta del servidor, esto con la intención de que el usuario grabe un audio y al darle detener se guarde en el servidor y que posterior a ello pueda ser reproducido despues.
Hola. Sí, puedes guardar el audio en el servidor, tengo un ejemplo con PHP: https://parzibyte.me/blog/2019/05/27/grabar-audio-microfono-javascript-php/
En este caso simplemente debes cambiar la ruta en la variable
$rutaDeGuardado
y podrás guardar el audio en otro lugarPara reproducir el audio más tarde, puedes usar el elemento
audio
de HTML y poner la fuente como la ruta en donde se guardó en PHPSaludos