Enregistrer du contenu audio et vidéo au format HTML5

Introduction

L'enregistrement audio/vidéo est depuis longtemps le "célèbre" du développement Web. Depuis de nombreuses années, nous avons dû utiliser des plug-ins de navigateur (Flash ou Silverlight) pour accomplir notre travail. Allez !

Le HTML5 à la rescousse. Cela n'est peut-être pas visible, mais l'essor du HTML5 a entraîné une forte 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. Elles exposent des API JavaScript de haut niveau qui s'ajoutent aux 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.

Itinéraire vers getUserMedia()

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

Plusieurs variantes des API Media Capture ont évolué au cours des dernières années. Beaucoup de gens s'étaient rendu compte qu'il fallait pouvoir accéder à des appareils natifs sur le Web, mais cela a conduit chacun et leur mère à élaborer une nouvelle spécification. La situation devenait tellement désordonnée que le W3C a finalement décidé de former un groupe de travail. Leur seul but ? Donnez du sens à toute cette folie ! Le groupe de travail DAP (Device APIs Policy) a été chargé de consolider et de standardiser la pléthore de propositions.

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

Phase 1: Capture multimédia HTML

La capture multimédia HTML a été la première méthode utilisée par le DAP pour standardiser la capture multimédia sur le Web. Elle consiste à surcharger <input type="file"> et à ajouter 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 vidéo ou audio est similaire:

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

C'est pas mal, non ? J'aime particulièrement le fait qu'il réutilise une entrée de fichier. D'un point de vue sémantique, cela a beaucoup de sens. Cette "API" ne permet pas d'effectuer des effets en temps réel (par exemple, afficher des données de webcam en direct dans un <canvas> et appliquer des filtres WebGL). La capture multimédia HTML vous permet uniquement d'enregistrer un fichier multimédia ou de prendre un instantané à temps.

Assistance:

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

Série 2: Élément relatif à l'appareil

De nombreux utilisateurs pensaient que la capture multimédia HTML était trop restrictive. Une nouvelle spécification a donc émergé, prenant en charge tous les types d'appareils (futurs). Sans surprise, la conception a fait appel à 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 la capture vidéo basée 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 balise, cette fois-ci 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 groupe en publiant un Lab pour IE9 qui prend en charge 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 été intégré dans <device>. Une API de moins à s'inquiéter, je suppose...) <device> avait deux avantages : 1.) il était sémantique et 2.) il était facilement extensible pour prendre en charge bien plus que les appareils audio/vidéo.

Inspirez. Tout ça va vite !

3e manche: WebRTC

L'élément <device> a fini par suivre le chemin du Dodo.

La recherche d'une API de capture appropriée a été accélérée grâce aux efforts 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 proposent des implémentations.

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

Assistance:

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

Premiers pas

Avec navigator.mediaDevices.getUserMedia(), nous pouvons enfin profiter de l'entrée de la webcam et du micro sans plug-in. Accédez à l'appareil photo en un clin d'œil, et non plus en installant un appareil photo. Il est directement intégré au navigateur. Vous avez hâte ?

Détection de fonctionnalités

La détection de caractéristiques consiste à vérifier simplement 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, une demande d'autorisation est nécessaire. Le premier paramètre de navigator.mediaDevices.getUserMedia() est un objet qui spécifie les détails et les exigences de chaque type de contenu multimédia auquel vous souhaitez accéder. Par exemple, si vous voulez 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 synergie entre les nouvelles API HTML5. Il fonctionne avec nos autres assistants HTML5, <audio> et <video>. Notez que nous ne définissons pas d'attribut src et n'incluons pas d'éléments <source> dans l'élément <video>. Au lieu d'envoyer à la vidéo une URL vers un fichier multimédia, nous définissons srcObject sur l'objet LocalMediaStream représentant la webcam.

J'appelle également <video> à autoplay, sinon il serait figé sur le premier frame. L'ajout de controls fonctionne également comme prévu.

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

Vous pouvez également utiliser le premier paramètre getUserMedia() pour spécifier davantage d'exigences (ou de contraintes) pour le flux multimédia renvoyé. Par exemple, au lieu d'indiquer simplement que vous souhaitez accéder à la vidéo de base ({video: true}, par exemple), 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 plus de configurations, consultez l'API constraints.

Sélectionner une source multimédia

La méthode enumerateDevices() de l'interface MediaDevices demande la liste des périphériques d'entrée et de sortie multimédia disponibles, tels que les micros, les appareils photo, 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 détectés sont sélectionnés comme source de 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 expliquant comment 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 ou à leur micro. Voici par exemple la boîte de dialogue d'autorisation de Chrome:

Boîte de dialogue d&#39;autorisation dans Chrome
Boîte de dialogue d'autorisation dans Chrome

Fournir une création de remplacement

Pour les utilisateurs qui ne prennent pas en charge navigator.mediaDevices.getUserMedia(), une option consiste à revenir à 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;
}