Enregistrer du contenu audio et vidéo au format HTML5

Introduction

La capture audio/vidéo est depuis longtemps le "Saint Graal" du développement Web. Pendant de nombreuses années, nous avons dû nous appuyer sur des plug-ins de navigateur (Flash ou Silverlight) pour y parvenir. Allez !

HTML5 à la rescousse. Cela n'est peut-être pas évident, mais l'essor du HTML5 a entraîné une augmentation de l'accès au matériel des appareils. La géolocalisation (GPS), l'API Orientation (accéléromètre), WebGL (GPU) et l'API Web Audio (matériel audio) en sont de parfaits exemples. Ces fonctionnalités sont incroyablement puissantes et exposent des API JavaScript de haut niveau qui s'appuient sur les capacités matérielles sous-jacentes du système.

Ce tutoriel présente une nouvelle API, GetUserMedia, qui permet aux applications Web d'accéder à l'appareil photo et au micro d'un utilisateur.

La route vers getUserMedia()

Si vous ne connaissez pas son histoire, la façon dont nous sommes arrivés à l'API getUserMedia() est une histoire intéressante.

Plusieurs variantes des API Media Capture ont évolué au cours des dernières années. De nombreuses personnes ont reconnu la nécessité de pouvoir accéder aux appareils natifs sur le Web, mais cela a conduit tout le monde à élaborer une nouvelle spécification. Les choses sont devenues si compliquées que le W3C a finalement décidé de former un groupe de travail. Leur seul objectif ? Donnez du sens à la folie ! Le groupe de travail sur les règles des API d'appareil (DAP) a été chargé de consolider et de standardiser la multitude de propositions.

Je vais essayer de résumer ce qui s'est passé en 2011…

Round 1 : HTML Media Capture

HTML Media Capture a été la première tentative du DAP de standardiser la capture multimédia sur le Web. Il fonctionne en surchargeant <input type="file"> et en ajoutant de nouvelles valeurs pour le paramètre accept.

Si vous souhaitez permettre aux utilisateurs de prendre un instantané d'eux-mêmes avec la webcam, vous pouvez utiliser capture=camera :

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

L'enregistrement d'une vidéo ou d'un contenu audio est similaire :

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

Plutôt sympa, non ? J'aime tout particulièrement qu'il réutilise une entrée de fichier. Sémantiquement, cela est tout à fait logique. Cette "API" ne permet pas d'appliquer des effets en temps réel (par exemple, afficher les données d'une webcam en direct sur un élément <canvas> et appliquer des filtres WebGL). HTML Media Capture vous permet uniquement d'enregistrer un fichier multimédia ou de prendre un instantané.

Assistance :

  • Navigateur Android 3.0 : l'une des premières implémentations. Regardez cette vidéo pour voir comment faire.
  • Chrome pour Android (0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari et Chrome (compatibilité partielle)

2e manche : Élément de l'appareil

De nombreuses personnes ont estimé que la capture de contenus multimédias HTML était trop limitée. Une nouvelle spécification a donc été créée pour prendre en charge tous les types d'appareils (futurs). Sans surprise, la conception a nécessité un nouvel élément, l'élément <device>, qui est devenu le prédécesseur de getUserMedia().

Opera a été l'un des premiers navigateurs à créer des implémentations initiales de capture vidéo basées sur l'élément <device>. Peu de temps après (le même jour pour être précis), le WHATWG a décidé de supprimer la balise <device> au profit d'une autre technologie prometteuse, une API JavaScript appelée navigator.getUserMedia(). Une semaine plus tard, Opera a publié de nouvelles versions compatibles avec la spécification getUserMedia() mise à jour. Plus tard dans l'année, Microsoft a rejoint le mouvement en publiant un Lab pour IE9 compatible avec la nouvelle spécification.

Voici à quoi aurait ressemblé <device> :

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

Assistance :

Malheureusement, aucun navigateur n'a jamais inclus <device>. Une API de moins à gérer, j'imagine :) <device> avait toutefois deux atouts majeurs : 1) elle était sémantique et 2) elle était facilement extensible pour prendre en charge d'autres appareils que les appareils audio/vidéo.

Inspirez, expirez. Les choses évoluent vite !

3e manche : WebRTC

L'élément <device> a fini par disparaître.

La recherche d'une API de capture adaptée s'est accélérée grâce à l'effort plus important de WebRTC (Web Real Time Communications). Cette spécification est supervisée par le groupe de travail WebRTC du W3C. Google, Opera, Mozilla et quelques autres ont des implémentations.

getUserMedia() est lié à WebRTC, car il s'agit de la passerelle vers cet ensemble d'API. Il permet d'accéder au flux de la caméra/du micro local de l'utilisateur.

Assistance :

getUserMedia() est compatible depuis Chrome 21, Opera 18 et Firefox 17.

Premiers pas

Grâce à navigator.mediaDevices.getUserMedia(), nous pouvons enfin exploiter les entrées de la webcam et du micro sans plug-in. L'accès à l'appareil photo est désormais possible via un appel, et non une installation. Il est intégré directement au navigateur. Vous avez hâte de commencer ?

Détection de caractéristiques

La détection de fonctionnalités est une simple vérification de l'existence de navigator.mediaDevices.getUserMedia :

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

Accéder à un périphérique d'entrée

Pour utiliser la webcam ou le micro, nous devons demander l'autorisation. Le premier paramètre de navigator.mediaDevices.getUserMedia() est un objet spécifiant les détails et les exigences pour chaque type de contenu multimédia auquel vous souhaitez accéder. Par exemple, si vous souhaitez accéder à la webcam, le premier paramètre doit être {video: true}. Pour utiliser à la fois le micro et la caméra, transmettez {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. Que se passe-t-il ici ? La capture multimédia est un parfait exemple de nouvelles API HTML5 fonctionnant ensemble. Il fonctionne en conjonction avec nos autres amis HTML5, <audio> et <video>. Notez que nous ne définissons pas d'attribut src ni n'incluons d'éléments <source> sur l'élément <video>. Au lieu de fournir à la vidéo l'URL d'un fichier multimédia, nous définissons srcObject sur l'objet LocalMediaStream représentant la webcam.

J'indique également à <video> de autoplay, sinon il serait figé sur la première image. L'ajout de controls fonctionne également comme prévu.

Définir des contraintes multimédias (résolution, hauteur, largeur)

Le premier paramètre de getUserMedia() peut également être utilisé pour spécifier d'autres exigences (ou contraintes) sur le flux multimédia renvoyé. Par exemple, au lieu d'indiquer simplement que vous souhaitez un accès de base à la vidéo (par exemple, {video: true}), vous pouvez également exiger que le flux soit en 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);

Pour en savoir plus sur les configurations, consultez l'API Constraints.

Sélectionner une source multimédia

La méthode enumerateDevices() de l'interface MediaDevices demande une liste des périphériques d'entrée et de sortie multimédias disponibles, tels que les micros, les caméras, les casques, etc. La promesse renvoyée est résolue avec un tableau d'objets MediaDeviceInfo décrivant les appareils.

Dans cet exemple, le dernier micro et la dernière caméra trouvés sont sélectionnés comme source du flux multimédia :

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

Découvrez l'excellente démonstration de Sam Dutton sur la façon de permettre aux utilisateurs de sélectionner la source multimédia.

Sécurité

Les navigateurs affichent une boîte de dialogue d'autorisation lors de l'appel de navigator.mediaDevices.getUserMedia(), qui permet aux utilisateurs d'accorder ou de refuser l'accès à leur caméra/micro. Par exemple, voici la boîte de dialogue d'autorisation de Chrome :

Boîte de dialogue d'autorisation dans Chrome
Boîte de dialogue d'autorisation dans Chrome

Fournir une solution de remplacement

Pour les utilisateurs qui ne sont pas compatibles avec navigator.mediaDevices.getUserMedia(), une option consiste à utiliser un fichier vidéo existant si l'API n'est pas compatible et/ou si l'appel échoue pour une raison quelconque :

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