Capturar audio y video en HTML5

Introducción

La captura de audio y video ha sido el "Santo Grial" del desarrollo web durante mucho tiempo. Durante muchos años, hemos dependido de los complementos del navegador (Flash o Silverlight) para completar el trabajo. ¡Vamos!

HTML5 al rescate. Puede que no sea evidente, pero el auge de HTML5 trajo consigo un aumento repentino de acceso al hardware del dispositivo. Ubicación geográfica (GPS), la API de Orientation (acelerómetro), WebGL (GPU) y la API de Web Audio (hardware de audio) son ejemplos perfectos. Estas funciones son absurdamente potentes y exponen APIs de JavaScript de alto nivel que de las capacidades del hardware subyacente del sistema.

En este instructivo, se presenta una nueva API, GetUserMedia, que permite aplicaciones web para acceder a la cámara y al micrófono del usuario.

El camino hacia getUserMedia()

Si no conoces su historia, la manera en la que llegamos a la API de getUserMedia() es interesante.

Varias variantes de las “APIs de captura de contenido multimedia” han evolucionado en los últimos años. Muchas personas reconocieron la necesidad de poder acceder a dispositivos nativos en la Web, pero que llevó a todos y a su mamá a armar una nueva especificación. Las cosas se mejoraron tan desordenado que el W3C finalmente decidió formar un grupo de trabajo. ¿Cuál es su único propósito? Comprende la locura. El Grupo de trabajo de la Política de APIs de dispositivos (DAP) tiene la tarea de consolidar y estandarizar la gran cantidad de propuestas.

Voy a tratar de resumir lo que sucedió en 2011...

Ronda 1: captura de medios HTML

HTML Media Capture fue el primer recurso de DAP en el la estandarización de la captura de contenido multimedia en la Web. Funciona mediante la sobrecarga de <input type="file">. y agregar valores nuevos para el parámetro accept.

Si quieres permitir que los usuarios se tomen una instantánea con la cámara web, que es posible con capture=camera:

<input type="file" accept="image/*;capture=camera">

Grabar un video o audio es similar a lo siguiente:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

Bien, ¿verdad? En particular, me gusta que reutilice una entrada de archivo. Semánticamente, tiene mucho sentido. Cuando esta “API” en particular no se alcanza es la capacidad de hacer efectos en tiempo real (p.ej., renderizar datos de la cámara 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 a tiempo.

Asistencia:

  • Navegador Android 3.0 - una de las primeras implementaciones. Mira este video para verla en acción.
  • Chrome para Android (0.16)
  • Firefox para dispositivos móviles 10.0
  • Safari y Chrome para iOS6 (compatibilidad parcial)

Ronda 2: elemento del dispositivo

Muchos creyeron que la captura de medios HTML era demasiado limitante, por lo que una nueva especificación compatibles con cualquier tipo de dispositivo (futuro). No es sorprendente que el diseño llamado Para un elemento nuevo, 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 basada en el elemento <device>. Poco después (el mismo día, para ser más precisos) WhatWG decidió descartar la etiqueta <device> para reemplazarla por una empresa emergente, esta vez, una API de JavaScript llamada navigator.getUserMedia() Una semana después, Opera lanzó nuevas construcciones que incluían compatibilidad con la especificación getUserMedia() actualizada. Más tarde ese mismo año, Microsoft se unió a la empresa y lanzó un Lab for IE9 compatible con la nueva especificación.

Así se vería <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, nunca ningún navegador publicado incluyó <device>. Supongo que hay una API menos de la que debes preocuparte :) <device> tuvo dos excelentes resultados. para él: 1) era semántica y 2) era fácil de extender para admitir más que solo dispositivos de audio y video.

Respira con calma. ¡Esto se mueve rápido!

Ronda 3: WebRTC

El elemento <device> finalmente siguió el camino del Dodo.

El ritmo para encontrar una API de captura adecuada aceleró gracias a una iniciativa más amplia de WebRTC (Web Real Time Communications). El Grupo de trabajo de WebRTC de W3C supervisa esa especificación. Google, Opera, Mozilla y algunas más tienen implementaciones.

getUserMedia() está relacionado con WebRTC porque es la puerta de enlace 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. El acceso a la cámara ahora está a solo una llamada de distancia, no una instalación de distancia. Están integrados directamente en el navegador. ¿Estás ansioso por empezar?

Detección de funciones

La detección de atributos 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, necesitamos solicitar permiso. El primer parámetro de navigator.mediaDevices.getUserMedia() es un objeto que especifica los detalles y para cada tipo de contenido multimedia al que quieras 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, pasar {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. ¿Qué sucede aquí? La captura de contenido multimedia es un ejemplo perfecto de las nuevas APIs de HTML5 de trabajar juntos. Funciona en conjunto con nuestros otros amigos de HTML5, <audio> y <video>. Ten en cuenta que no estableceremos un atributo src ni incluiremos elementos <source> en el elemento <video>. En vez de proporcionarle al video una URL a un archivo multimedia, srcObject para el objeto LocalMediaStream que representa la cámara web

También le digo al <video> a autoplay; de lo contrario, se bloqueará en el primer fotograma. Agregar controls también funciona como esperabas.

Cómo configurar restricciones de contenido multimedia (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 multimedia que se muestra. Por ejemplo, en lugar de solo indicar que quieres tener acceso básico al video (p. ej., {video: true}), también puedes solicitar la transmisión continua. para que sea 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 opciones de configuración, consulta la API de Constraints.

Cómo seleccionar una fuente multimedia

El método enumerateDevices() de la interfaz MediaDevices solicita una lista de los dispositivos de entrada y salida de contenido multimedia disponibles, como micrófonos, cámaras, auriculares, etcétera. La promesa que se muestra se resuelve con un array de objetos MediaDeviceInfo que describen los dispositivos.

En este ejemplo, el último micrófono y cámara que se encontró se seleccionaron como el fuente de transmisión multimedia:

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);
}

Mira la excelente demostración de Sam Dutton para permitir que los usuarios seleccionen la fuente multimedia.

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 denegar el acceso a su cámara o micrófono. Por ejemplo, aquí es el diálogo de permisos de Chrome:

Diálogo de permisos en Chrome
Diálogo de permisos en Chrome

Cómo proporcionar resguardo

En el caso de los usuarios que no admiten navigator.mediaDevices.getUserMedia(), una opción es usar resguardo. 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;
}