Acquisisci audio e video in HTML5

Introduzione

L'acquisizione di audio/video è stata il "Santo Graal" dello sviluppo web per molto tempo. Per molti anni abbiamo dovuto fare affidamento sui plug-in del browser (Flash o Silverlight) per svolgere il lavoro. Coraggio!

HTML5 in soccorso. Potrebbe non essere evidente, ma l'ascesa dell'HTML5 ha portato a un aumento dell'accesso all'hardware dei dispositivi. Geolocalizzazione (GPS), l'API Orientation (accelerometro), WebGL (GPU) e l'API Web Audio (hardware audio) sono esempi perfetti. Queste funzionalità sono incredibilmente potenti ed espongono API JavaScript di alto livello che si trovano sopra le funzionalità hardware sottostanti del sistema.

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

La strada verso getUserMedia()

Se non conosci la sua storia, il modo in cui siamo arrivati all'API getUserMedia() è una storia interessante.

Negli ultimi anni si sono evolute diverse varianti delle "API Media Capture". Molti hanno riconosciuto la necessità di poter accedere ai dispositivi nativi sul web, ma questo ha portato tutti a creare una nuova specifica. Le cose sono diventate così confuse che il W3C ha finalmente deciso di formare un gruppo di lavoro. Il loro unico scopo? Dai un senso alla follia! Il Device APIs Policy (DAP) Working Group ha il compito di consolidare e standardizzare la miriade di proposte.

Cercherò di riassumere cosa è successo nel 2011…

Round 1: HTML Media Capture

HTML Media Capture è stato il primo tentativo del DAP di standardizzare l'acquisizione di contenuti multimediali sul web. Funziona sovraccaricando il <input type="file"> e aggiungendo nuovi valori per il parametro accept.

Se vuoi consentire agli utenti di scattare un'istantanea di se stessi con la webcam, puoi farlo 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 di file. Semanticamente, ha molto senso. La lacuna di questa particolare "API" è l'impossibilità di applicare effetti in tempo reale (ad es. eseguire il rendering dei dati della webcam in tempo reale in un <canvas> e applicare filtri WebGL). HTML Media Capture consente solo di registrare un file multimediale o di acquisire un'istantanea nel tempo.

Assistenza:

  • Browser Android 3.0: una delle prime implementazioni. Guarda questo video per vederla in azione.
  • Chrome per Android (0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari e Chrome (supporto parziale)

Round 2: elemento del dispositivo

Molti ritenevano che HTML Media Capture fosse troppo limitante, quindi è emersa una nuova specifica che supportava qualsiasi tipo di dispositivo (futuro). Non sorprende che il design richiedesse 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> a favore di un'altra novità, questa volta un'API JavaScript chiamata navigator.getUserMedia(). Una settimana dopo, Opera ha rilasciato nuove build che includevano il supporto per la specifica getUserMedia() aggiornata. Più avanti nello stesso anno, Microsoft si è unita alla festa rilasciando un Lab per IE9 che supportava la nuova specifica.

Ecco come sarebbe stato <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 estendibile per supportare non solo i dispositivi audio/video.

Rilassati. Queste cose si muovono velocemente.

Round 3: WebRTC

L'elemento <device> alla fine è stato abbandonato.

Il ritmo per trovare un'API di acquisizione adatta è accelerato grazie al maggiore impegno di WebRTC (Web Real Time Communications). Questa specifica è supervisionata dal gruppo di lavoro WebRTC del W3C. Google, Opera, Mozilla e alcuni altri hanno implementazioni.

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

Assistenza:

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

Per iniziare

Con navigator.mediaDevices.getUserMedia(), possiamo finalmente sfruttare l'input della webcam e del microfono senza un plug-in. Ora l'accesso alla fotocamera è a portata di chiamata, non di installazione. È integrata direttamente nel browser. Non vedi l'ora?

Rilevamento delle caratteristiche

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 contenuto multimediale a cui vuoi accedere. Ad esempio, se vuoi accedere alla webcam, il primo parametro deve essere {video: true}. Per utilizzare sia il microfono sia la videocamera, passa {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. Allora, cosa sta succedendo qui? L'acquisizione di contenuti multimediali è un esempio perfetto di come le nuove API HTML5 collaborano. Funziona in combinazione con gli altri elementi HTML5, <audio> e <video>. Tieni presente che non impostiamo un attributo src né includiamo elementi <source> nell'elemento <video>. Anziché fornire al video un URL di un file multimediale, impostiamo srcObject sull'oggetto LocalMediaStream che rappresenta la webcam.

Sto anche dicendo alla <video> di autoplay, altrimenti rimarrebbe bloccata sul primo frame. Anche l'aggiunta di controls funziona come previsto.

Impostazione dei vincoli dei 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, invece di indicare semplicemente che vuoi l'accesso di base al video (ad es. {video: true}), puoi anche richiedere che lo stream sia in 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 una fonte 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 oggetti MediaDeviceInfo che descrivono i dispositivi.

In questo esempio, l'ultimo microfono e l'ultima videocamera trovati vengono selezionati come origine 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 l'origine multimediale.

Sicurezza

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

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

Fornire un'alternativa

Per gli utenti che non dispongono del supporto per navigator.mediaDevices.getUserMedia(), un'opzione è eseguire il fallback a 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;
}