Acquisisci audio e video in HTML5

Introduzione

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

HTML5 in soccorso. Potrebbe non essere evidente, ma l'ascesa dell'HTML5 ha portato di accesso all'hardware del dispositivo. Geolocalizzazione (GPS), API Orientation (accelerometro), WebGL (GPU), e l'API Web Audio (hardware audio) sono esempi perfetti. Queste funzionalità sono incredibilmente potenti, espongono API JavaScript di alto livello che si basano in base alle funzionalità hardware sottostanti del sistema.

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

Il percorso per getUserMedia()

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

Diverse varianti delle "API Media Capture" si sono evolute negli ultimi anni. Molte persone hanno capito la necessità di poter accedere a dispositivi nativi sul web, ma che ha portato tutti e le loro mamme a creare nuove specifiche. Si è verificato un problema così disordinato che il W3C decise finalmente di istituire un gruppo di lavoro. Il loro unico scopo? Dai un senso a questa follia! Il gruppo di lavoro Device APIs Policy (DAP) ha il compito di consolidare e standardizzare la pletora di proposte.

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

Primo round: acquisizione di contenuti multimediali HTML

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

Se vuoi consentire agli utenti di scattare una foto di sé con la webcam, che è 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">

Un po' carino, vero? Mi piace soprattutto il fatto che riutilizzi un input di file. Semanticamente, ha tutto senso. Questa specifica "API" non è sufficiente produrre effetti in tempo reale (ad esempio, eseguire il rendering dei dati della webcam in diretta su un <canvas> e applicare i filtri WebGL). HTML Media Capture ti consente solo di registrare un file multimediale o di scattare un'istantanea in tempo reale.

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
  • iOS6, Safari e Chrome (supporto parziale)

Fase 2: elemento dispositivo

Molti pensavano che HTML Media Capture fosse troppo limitante, quindi una nuova specifica che supportava qualsiasi tipo di dispositivo (in futuro). Non sorprende che il design chiamato per un nuovo elemento, l'elemento <device>, che diventò il predecessore di getUserMedia().

Opera è stato tra i primi browser a creare implementazioni iniziali di acquisizione video in base all'elemento <device>. Poco dopo (lo stesso giorno per essere precisi). WhatWG ha deciso di eseguire lo scraping del tag <device> in favore di un altro up and comer, questa volta un'API JavaScript chiamata navigator.getUserMedia(). Una settimana dopo, Opera ha realizzato nuove build che includevano supporto per la specifica getUserMedia() aggiornata. Entro la fine dell'anno Microsoft si è unita al gruppo rilasciando un Lab per IE9 che supporta la nuova specifica.

Ecco come avrebbe avuto <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, credo :) <device> aveva due ottime cose da fare per questo: 1.) era semantico, e 2.) era facilmente estensibile per supportare molto più che semplici dispositivi audio/video.

Fai un respiro. Questa roba si muove velocemente!

Terzo round: WebRTC

Alla fine, l'elemento <device> ha cambiato il nome del Dodo.

La velocità necessaria per trovare un'API di acquisizione adatta è stata accelerata grazie al maggiore impegno offerto da WebRTC (Web Real Time Communications). Questa specifica è controllata dal WebRTC Working Group di W3C. Google, Opera, Mozilla e pochi altri hanno delle implementazioni.

getUserMedia() è correlato a WebRTC perché è il gateway per l'insieme di API. Fornisce i mezzi per accedere allo stream locale della videocamera/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 sfruttare l'input della webcam e del microfono senza plug-in. Ora l'accesso alla fotocamera richiede una chiamata, non un'installazione. Viene integrata direttamente nel browser. Sei già entusiasta?

Rilevamento delle caratteristiche

Il rilevamento delle caratteristiche è 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 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: superamento {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 nuove API HTML5 che lavorano in sinergia. Funziona in combinazione con gli altri nostri partner HTML5, <audio> e <video>. Tieni presente che non stiamo impostando un attributo src né includiamo elementi <source> sull'elemento <video>. Invece di fornire al video un URL che rimanda a un file multimediale, stiamo impostando srcObject all'oggetto LocalMediaStream che rappresenta la webcam.

Dico anche a <video> a autoplay, altrimenti l'elemento verrebbe bloccato il primo frame. Anche l'aggiunta di controls funziona come previsto.

Impostare i vincoli relativi ai contenuti multimediali (risoluzione, altezza, larghezza)

Il primo parametro di getUserMedia() può essere utilizzato anche per specificare più 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 richiedere anche lo stream ad alta definizione:

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 Vincoli.

Selezionare 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 la videocamera trovati vengono selezionati come fonte 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 la fantastica demo di Sam Dutton su come per consentire agli utenti di selezionare la fonte multimediale.

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 videocamera o al microfono. Ad esempio, qui è la finestra di dialogo delle autorizzazioni di Chrome:

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

Offerta di riserva

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

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