Introducción
La captura de audio y video ha sido el "Santo Grial" del desarrollo web durante mucho tiempo. Durante muchos años, tuvimos que depender de complementos del navegador (Flash o Silverlight) para realizar el trabajo. ¡Vamos!
HTML5 al rescate. Quizás no sea evidente, pero el aumento de HTML5 ha provocado un incremento en el acceso al hardware del dispositivo. La API de Geolocation (GPS), la API de Orientation (acelerómetro), WebGL (GPU) y la API de Web Audio (hardware de audio) son ejemplos perfectos. Estas funciones son increíblemente potentes y exponen APIs de JavaScript de alto nivel que se encuentran sobre las capacidades de hardware subyacentes del sistema.
En este instructivo, se presenta una nueva API, GetUserMedia, que permite que las apps web accedan a la cámara y el micrófono de un usuario.
El camino hacia getUserMedia()
Si no conoces su historial, la forma en que llegamos a la API de getUserMedia()
es una historia interesante.
En los últimos años, evolucionaron varias variantes de las "APIs de Media Capture". Muchas personas reconocieron la necesidad de poder acceder a dispositivos nativos en la Web, pero eso llevó a que todos y cada uno de ellos crearan una nueva especificación. Las cosas se complicaron tanto que el W3C finalmente decidió formar un grupo de trabajo. ¿Cuál es su único propósito? ¡Entiende la locura! Se le asignó al Grupo de trabajo de la Política de APIs de Dispositivos (DAP) la tarea de consolidar y estandarizar la gran cantidad de propuestas.
Intentaré resumir lo que sucedió en 2011…
Ronda 1: Captura de medios HTML
HTML Media Capture fue el primer intento de la DAP por estandarizar la captura de medios en la Web. Funciona sobrecargando el <input type="file">
y agregando valores nuevos para el parámetro accept
.
Si quieres permitir que los usuarios se tomen una instantánea con la cámara web, puedes hacerlo con capture=camera
:
<input type="file" accept="image/*;capture=camera">
Grabar un video o audio es similar:
<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">
Es bastante agradable, ¿verdad? En particular, me gusta que reutilice una entrada de archivo. Semánticamente, tiene mucho sentido. El punto débil de esta "API" en particular es la capacidad de realizar efectos en tiempo real (p.ej., renderizar datos de cámaras web en vivo en un <canvas>
y aplicar filtros de WebGL).
HTML Media Capture solo te permite grabar un archivo multimedia o tomar una instantánea.
Asistencia:
- Navegador de Android 3.0: Una de las primeras implementaciones. Mira este video para verla en acción.
- Chrome para Android (0.16)
- Firefox Mobile 10.0
- Safari y Chrome en iOS 6 (compatibilidad parcial)
Ronda 2: Elemento del dispositivo
Muchos pensaron que la captura de contenido multimedia en HTML era demasiado limitante, por lo que surgió una nueva especificación que admitía cualquier tipo de dispositivo (futuro). No es sorprendente que el diseño requiriera un nuevo elemento, el elemento <device>
, que se convirtió en el predecesor de getUserMedia()
.
Opera fue uno de los primeros navegadores en crear implementaciones iniciales de captura de video basadas en el elemento <device>
. Poco después (el mismo día, para ser exactos), el WhatWG decidió desechar la etiqueta <device>
en favor de otra promesa, esta vez una API de JavaScript llamada navigator.getUserMedia()
. Una semana después, Opera lanzó nuevas compilaciones que incluían compatibilidad con la especificación getUserMedia()
actualizada. Más adelante ese año, Microsoft se unió a la fiesta con el lanzamiento de un Lab para IE9 que admitía la nueva especificación.
Así se habría visto <device>
:
<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
function update(stream) {
document.querySelector('video').src = stream.url;
}
</script>
Asistencia:
Lamentablemente, ningún navegador lanzado incluyó <device>
.
Supongo que una API menos de la que preocuparse :) Sin embargo, <device>
tenía dos grandes ventajas: 1) era semántica y 2) se podía extender fácilmente para admitir más que solo dispositivos de audio y video.
Respira. ¡Esto se mueve rápido!
Ronda 3: WebRTC
El elemento <device>
finalmente desapareció.
El ritmo para encontrar una API de captura adecuada se aceleró gracias al mayor esfuerzo de WebRTC (Web Real Time Communications). Esa especificación está supervisada por el Grupo de trabajo de WebRTC de W3C. Google, Opera, Mozilla y algunos otros tienen implementaciones.
getUserMedia()
se relaciona con WebRTC porque es la puerta de entrada a ese conjunto de APIs.
Proporciona los medios para acceder a la transmisión local de la cámara o el micrófono del usuario.
Asistencia:
getUserMedia()
es compatible desde Chrome 21, Opera 18 y Firefox 17.
Cómo comenzar
Con navigator.mediaDevices.getUserMedia()
, finalmente podemos aprovechar la entrada de la cámara web y el micrófono sin un complemento.
Ahora, el acceso a la cámara está a una llamada de distancia, no a una instalación. Está integrada directamente en el navegador. ¿Te entusiasma?
Detección de características
La detección de funciones es una verificación simple de la existencia de navigator.mediaDevices.getUserMedia
:
if (navigator.mediaDevices?.getUserMedia) {
// Good to go!
} else {
alert("navigator.mediaDevices.getUserMedia() is not supported");
}
Cómo obtener acceso a un dispositivo de entrada
Para usar la cámara web o el micrófono, debemos solicitar permiso.
El primer parámetro de navigator.mediaDevices.getUserMedia()
es un objeto que especifica los detalles y los requisitos de cada tipo de medio al que deseas acceder. Por ejemplo, si quieres acceder a la cámara web, el primer parámetro debe ser {video: true}
. Para usar el micrófono y la cámara, pasa {video: true, audio: true}
:
<video autoplay></video>
<script>
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((localMediaStream) => {
const video = document.querySelector("video");
video.srcObject = localMediaStream;
})
.catch((error) => {
console.log("Rejected!", error);
});
</script>
De acuerdo. Entonces, ¿qué sucede aquí? La captura de medios es un ejemplo perfecto de cómo funcionan en conjunto las nuevas APIs de HTML5. Funciona en conjunto con nuestros otros amigos de HTML5, <audio>
y <video>
.
Ten en cuenta que no establecemos un atributo src
ni incluimos elementos <source>
en el elemento <video>
. En lugar de proporcionarle al video una URL a un archivo multimedia, establecemos srcObject
en el objeto LocalMediaStream
que representa la cámara web.
También le digo a <video>
que autoplay
; de lo contrario, se quedaría congelado en el primer fotograma. Agregar controls
también funciona como esperabas.
Cómo establecer restricciones de medios (resolución, altura y ancho)
El primer parámetro de getUserMedia()
también se puede usar para especificar más requisitos (o restricciones) en la transmisión de medios que se devuelve. Por ejemplo, en lugar de solo indicar que quieres acceso básico al video (p.ej., {video: true}
), también puedes solicitar que la transmisión sea en HD:
const hdConstraints = {
video: { width: { exact: 1280} , height: { exact: 720 } },
};
const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
video: { width: { exact: 640} , height: { exact: 360 } },
};
const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
Para obtener más información sobre la configuración, consulta la API de Constraints.
Cómo seleccionar una fuente de contenido multimedia
El método enumerateDevices()
de la interfaz MediaDevices
solicita una lista de los dispositivos de entrada y salida de medios disponibles, como micrófonos, cámaras, auriculares, etcétera. La promesa devuelta se resuelve con un array de objetos MediaDeviceInfo
que describen los dispositivos.
En este ejemplo, se selecciona el último micrófono y la última cámara que se encuentran como la fuente de transmisión de medios:
if (!navigator.mediaDevices?.enumerateDevices) {
console.log("enumerateDevices() not supported.");
} else {
// List cameras and microphones.
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
let audioSource = null;
let videoSource = null;
devices.forEach((device) => {
if (device.kind === "audioinput") {
audioSource = device.deviceId;
} else if (device.kind === "videoinput") {
videoSource = device.deviceId;
}
});
sourceSelected(audioSource, videoSource);
})
.catch((err) => {
console.error(`${err.name}: ${err.message}`);
});
}
async function sourceSelected(audioSource, videoSource) {
const constraints = {
audio: { deviceId: audioSource },
video: { deviceId: videoSource },
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
}
Echa un vistazo a la excelente demostración de Sam Dutton sobre cómo permitir que los usuarios seleccionen la fuente de medios.
Seguridad
Los navegadores muestran un diálogo de permisos cuando se llama a navigator.mediaDevices.getUserMedia()
, lo que les da a los usuarios la opción de otorgar o rechazar el acceso a la cámara o el micrófono. Por ejemplo, este es el diálogo de permisos de Chrome:

Cómo proporcionar una opción alternativa
Para los usuarios que no tienen compatibilidad con navigator.mediaDevices.getUserMedia()
, una opción es recurrir a un archivo de video existente si la API no es compatible o si la llamada falla por algún motivo:
if (!navigator.mediaDevices?.getUserMedia) {
video.src = "fallbackvideo.webm";
} else {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
}