Acquisisci audio e video in HTML5

Introduzione

L'acquisizione di audio/video è da tempo il Santo Graal dello sviluppo web. Per molti anni abbiamo dovuto fare affidamento su plug-in del browser (Flash o Silverlight) per svolgere queste attività. Dai!

HTML5 in soccorso. Potrebbe non essere evidente, ma l'ascesa dell'HTML5 ha portato un aumento dell'accesso all'hardware dei dispositivi. La geolocalizzazione (GPS), l'API di Orientation (accelerometro), la GPU 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 basano sulle 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 per getUserMedia()

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

Negli ultimi anni si sono evolute diverse varianti delle "API Media Capture". Molte persone si resero conto della necessità di accedere ai dispositivi nativi sul web, ma ciò spingeva tutti, e le loro mamme, a creare una nuova specifica. Le cose si complicarono così tanto che W3C ha finalmente deciso di formare un gruppo di lavoro. Il loro unico scopo? Dai un senso alla pazzia! Il gruppo di lavoro DAP (Device API Policy) ha ricevuto l'incarico di consolidare e standardizzare l'ampia gamma di proposte.

Cercherò di riassumere quello che è successo nel 2011...

Fase 1: acquisizione di contenuti multimediali HTML

HTML Media Capture è stata la prima scelta del DAP per la standardizzazione dell'acquisizione dei contenuti multimediali sul web. Funziona mediante il sovraccarico di <input type="file"> e l'aggiunta di nuovi valori per il parametro accept.

Se vuoi consentire agli utenti di scattare un'istantanea di se stessi con la webcam, è possibile farlo con capture=camera:

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

La registrazione di un video o di un audio funziona in modo simile:

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

Va bene, no? Mi piace in particolare il fatto che riutilizza un input di file. Dal punto di vista semantico, ha molto senso. Se questa particolare "API" non è all'altezza, si tratta della capacità di creare 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 un'istantanea in tempo.

Assistenza:

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

2° round: elemento del dispositivo

Molti pensavano che la funzionalità HTML Media Capture fosse troppo limitata, perciò è nata una nuova specifica che supportava qualsiasi tipo di dispositivo (futuro). Non sorprende che il design richiami un nuovo elemento, l'elemento <device>, che è diventato il predecessore di getUserMedia().

Opera è stata tra i primi browser a creare implementazioni iniziali di acquisizione video basate sull'elemento <device>. Poco dopo (lo stesso giorno per la precisione), WhatWG ha deciso di eliminare il tag <device> a favore di un'altra azienda emergente, questa volta un'API JavaScript chiamata navigator.getUserMedia(). Una settimana dopo, Opera ha pubblicato nuove build che includevano il supporto per la specifica aggiornata di getUserMedia(). Più tardi quell'anno, Microsoft si è unita al gruppo 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:

Sfortunatamente, nessun browser rilasciato ha mai incluso <device>. Un'API in meno di cui preoccuparsi, immagino :) <device> ha avuto due grandi cose in questo senso: 1.) era semantica e 2.) era facilmente estendibile per supportare più di semplici dispositivi audio/video.

Fai un respiro. Questa roba si muove velocemente!

Round 3: WebRTC

L'elemento <device> alla fine ha superato il Dodo.

Il ritmo per trovare un'API di acquisizione adeguata ha accelerato grazie al più ampio impegno di WebRTC (Web Real Time Communications). Questa specifica è supervisionata dal W3C WebRTC Working Group. Google, Opera, Mozilla e alcuni altri dispongono di implementazioni.

getUserMedia() è correlato a WebRTC perché è il gateway per quell'insieme di API. Consente di accedere allo stream locale della fotocamera o del microfono dell'utente.

Assistenza:

getUserMedia() è supportato 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 basta una chiamata per accedere alla videocamera, non un'installazione. È integrato direttamente nel browser. Sei ancora entusiasta?

Rilevamento delle funzionalità

Il rilevamento delle caratteristiche consiste in 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 per 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 fotocamera, 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. Che succede qui? L'acquisizione di contenuti multimediali è un esempio perfetto di nuove API HTML5 che funzionano insieme. Funziona in combinazione con gli altri nostri amici HTML5, <audio> e <video>. Nota che non stiamo impostando un attributo src né includendo elementi <source> nell'elemento <video>. Anziché fornire al video un URL a un file multimediale tramite feed, impostiamo srcObject sull'oggetto LocalMediaStream che rappresenta la webcam.

Sto anche comunicando <video> a autoplay, altrimenti verrebbe bloccato al primo frame. Anche l'aggiunta di controls funziona come previsto.

Impostare vincoli multimediali (risoluzione, altezza, larghezza)

Il primo parametro per 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 ai video (ad es. {video: true}), puoi richiedere anche 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 ulteriori configurazioni, consulta l'API dei vincoli.

Selezione di un'origine multimediale

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

In questo esempio, sono stati selezionati l'ultimo microfono e l'ultima videocamera rilevati 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);
}

Guarda l'ottima demo di Sam Dutton su come consentire agli utenti di selezionare la fonte di contenuti multimediali.

Sicurezza

I browser mostrano una finestra di dialogo di autorizzazione alla chiamata di navigator.mediaDevices.getUserMedia(), che offre agli utenti la possibilità di concedere o negare l'accesso alla fotocamera/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

Fornitura di fallback

Per gli utenti che non supportano navigator.mediaDevices.getUserMedia(), un'opzione consiste nel ricorrere a un file video esistente di riserva 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;
}