Acquisisci audio e video in HTML5

Introduzione

L'acquisizione audio/video è da molto tempo il "santo Graal" dello sviluppo web. Per molti anni abbiamo dovuto fare affidamento su plug-in per browser (Flash o Silverlight) per svolgere il nostro lavoro. Dai!

HTML5 viene in soccorso. Potrebbe non essere evidente, ma l'ascesa di HTML5 ha comportato un aumento dell'accesso all'hardware dei dispositivi. Geolocalizzazione (GPS), API Orientation (accelerometro), WebGL (GPU) e API Web Audio (hardware audio) sono esempi perfetti. Queste funzionalità sono incredibilmente potenti e mettono a disposizione API JavaScript di alto livello che si basano sulle funzionalità hardware di base del sistema.

Questo tutorial introduce una nuova API, GetUserMedia, che consente alle app web di accedere alla fotocamera e al microfono di un utente.

Il percorso per getUserMedia()

Se non conosci la sua storia, il modo in cui abbiamo creato l'API getUserMedia() è una storia interessante.

Negli ultimi anni si sono evolute diverse varianti delle "API Media Capture". Molte persone hanno riconosciuto la necessità di poter accedere ai dispositivi nativi sul web, ma questo ha portato tutti a creare una nuova specifica. La situazione è diventata così confusa che il W3C ha finalmente deciso di formare un gruppo di lavoro. Il loro unico scopo? Dai un senso alla follia. Il gruppo di lavoro relativo alle norme relative alle API di dispositivo (DAP) ha il compito di consolidare e standardizzare la pletora di proposte.

Cercherò di riepilogare cosa è successo nel 2011…

Primo turno: acquisizione multimediale HTML

HTML Media Capture è stata la prima esperienza del DAP per la standardizzazione della cattura di contenuti multimediali sul web. Funziona sovraccaricando <input type="file"> e aggiungendo nuovi valori per il parametro accept.

Se vuoi consentire agli utenti di scattarsi un'istantanea con la webcam, è possibile con capture=camera:

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

La registrazione di un video o di un audio è simile:

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

Non male, vero? Mi piace particolarmente il fatto che riutilizzi un input file. Semanticamente, ha molto senso. Quando questa particolare "API" non funziona, c'è la possibilità di eseguire effetti in tempo reale (ad es. eseguire il rendering dei dati della webcam in diretta su un <canvas> e applicare filtri WebGL). HTML Media Capture ti consente solo di registrare un file multimediale o acquisire uno snapshot nel tempo.

Assistenza:

  • Browser Android 3.0: una delle prime implementazioni. Guarda questo video per vedere la funzionalità in azione.
  • Chrome per Android (0.16)
  • Firefox Mobile 10.0
  • Safari e Chrome per iOS 6 (supporto parziale)

Fase 2: elemento dispositivo

Molti pensavano che HTML Media Capture fosse troppo limitato, quindi è emersa una nuova specifica che supportava qualsiasi tipo di dispositivo (futuro). Non sorprende che il design abbia richiesto un nuovo elemento, l'elemento <device>, che è diventato il predecessore di getUserMedia().

Opera è stato tra i primi browser a creare implementazioni iniziali di acquisizione video basate sull'elemento <device>. Poco dopo (lo stesso giorno, per la precisione), il WhatWG ha deciso di eliminare il tag <device> in favore di un'altra tecnologia emergente, questa volta un'API JavaScript chiamata navigator.getUserMedia(). Una settimana dopo, Opera ha rilasciato nuove build che includevano il supporto della specifica getUserMedia() aggiornata. Nello stesso anno, anche Microsoft si è unita al gruppo rilasciando un lab per IE9 che supportava la nuova specifica.

Ecco come sarebbe apparso <device>:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

Assistenza:

Purtroppo, nessun browser rilasciato ha mai incluso <device>. Un'API in meno di cui preoccuparsi, immagino :) <device> aveva però due grandi vantaggi: 1.) era semantica e 2.) era facilmente estensibile per supportare più di semplici dispositivi audio/video.

Fai un respiro. Questa roba si muove velocemente!

Terzo round: WebRTC

Alla fine, l'elemento <device> ha adottato la forma del Dodo.

La ricerca di un'API di acquisizione adatta è stata accelerata grazie al maggiore impegno per WebRTC (Web Real Time Communications). Queste specifiche sono supervisionate dal W3C WebRTC Working Group. Google, Opera, Mozilla e altri hanno implementazioni.

getUserMedia() è correlato a WebRTC perché è il gateway per questo insieme di API. Fornisce i mezzi per accedere allo stream locale della fotocamera/del microfono dell'utente.

Assistenza:

getUserMedia() è supportato a partire da Chrome 21, Opera 18 e Firefox 17.

Per iniziare

Con navigator.mediaDevices.getUserMedia(), possiamo finalmente accedere all'input della webcam e del microfono senza un plug-in. Ora l'accesso alla videocamera è a una chiamata di distanza, non a un'installazione. È integrato direttamente nel browser. Ti abbiamo stuzzicato la curiosità?

Rilevamento di funzionalità

Il rilevamento delle funzionalità è un semplice controllo dell'esistenza di navigator.mediaDevices.getUserMedia:

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

Ottenere l'accesso a un dispositivo di input

Per utilizzare la webcam o il microfono, dobbiamo richiedere l'autorizzazione. Il primo parametro di navigator.mediaDevices.getUserMedia() è un oggetto che specifica i dettagli e i requisiti per ogni tipo di contenuti multimediali a cui vuoi accedere. Ad esempio, se vuoi accedere alla webcam, il primo parametro deve essere {video: true}. Per utilizzare sia il microfono che la videocamera, invia {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>

Ok. Che cosa sta succedendo? La cattura di contenuti multimediali è un esempio perfetto di collaborazione tra le nuove API HTML5. Funziona in combinazione con gli altri nostri amici HTML5, <audio> e <video>. Tieni presente che non stiamo impostando un attributo src o includendo elementi <source> nell'elemento <video>. Invece di fornire al video un URL a un file multimediale, impostiamo srcObject sull'oggetto LocalMediaStream che rappresenta la webcam.

Inoltre, dico a <video> di autoplay, altrimenti rimarrebbe bloccato sul primo frame. Anche l'aggiunta di controls funziona come previsto.

Impostazione di vincoli per i contenuti multimediali (risoluzione, altezza, larghezza)

Il primo parametro di getUserMedia() può essere utilizzato anche per specificare ulteriori requisiti (o vincoli) per lo stream multimediale restituito. Ad esempio, anziché indicare semplicemente che vuoi l'accesso di base al video (ad es. {video: true}), puoi richiedere anche che lo stream sia 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);

Per altre configurazioni, consulta l'API constraints.

Selezione di un'origine multimediale

Il metodo enumerateDevices() dell'interfaccia MediaDevices richiede un elenco dei dispositivi di input e output multimediali disponibili, come microfoni, videocamere, cuffie e così via. La promessa restituita viene risolta con un array di MediaDeviceInfo oggetti che descrivono i dispositivi.

In questo esempio, l'ultimo microfono e l'ultima videocamera trovati vengono selezionati come fonte dello stream multimediale:

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

Dai un'occhiata alla fantastica demo di Sam Dutton su come consentire agli utenti di selezionare la sorgente multimediale.

Sicurezza

I browser mostrano una finestra di dialogo di autorizzazione quando viene chiamata navigator.mediaDevices.getUserMedia(), che offre agli utenti la possibilità di concedere o negare l'accesso alla fotocamera/al microfono. Ad esempio, di seguito è riportata la finestra di dialogo delle autorizzazioni di Chrome:

Finestra di dialogo delle autorizzazioni in Chrome
Finestra di dialogo delle autorizzazioni in Chrome

Fornire un'opzione di riserva

Per gli utenti che non supportano navigator.mediaDevices.getUserMedia(), un'opzione è quella di eseguire il fallback su un file video esistente se l'API non è supportata e/o la chiamata non va a buon fine per qualche motivo:

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}