Enregistrer du contenu audio et vidéo au format HTML5

Introduction

La capture audio et 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 accomplir cette tâche. Allez !

HTML5 va vous aider. Cela peut ne pas être évident, 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 des exemples parfaits. Ces fonctionnalités sont incroyablement puissantes et exposent des API JavaScript de haut niveau qui reposent sur les fonctionnalités matérielles sous-jacentes du système.

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

Accéder à getUserMedia()

Si vous ne connaissez pas son histoire, celle de l'API getUserMedia() est 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é d'accéder aux appareils natifs sur le Web, mais cela a conduit tout le monde à créer une nouvelle spécification. Les choses sont devenues si désordonnées que le W3C a finalement décidé de former un groupe de travail. Leur seul but ? Comprenez le sens de la 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...

Manche 1 : Capture multimédia HTML

La capture multimédia HTML a été la première tentative de la DAP de normaliser la capture multimédia sur le Web. Son fonctionnement consiste à surcharger <input type="file"> et à ajouter de nouvelles valeurs pour le paramètre accept.

Si vous souhaitez autoriser les utilisateurs à se prendre en photo avec la webcam, c'est possible avec 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'apprécie particulièrement qu'il réutilise une entrée de fichier. Sémantiquement, cela a beaucoup de sens. Lorsque cette "API" spécifique 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 ne vous permet d'enregistrer un fichier multimédia ou de prendre une capture d'écran qu'à un moment donné.

Assistance:

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

2e tour: élément de l'appareil

De nombreux utilisateurs pensaient que la capture multimédia HTML était trop limitée. Une nouvelle spécification est donc apparue, compatible avec 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 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é d'abandonner la balise <device> au profit d'une autre nouvelle venue, cette fois une API JavaScript appelée navigator.getUserMedia(). Une semaine plus tard, Opera a publié de nouvelles versions compatibles avec la nouvelle spécification getUserMedia(). Plus tard dans l'année, Microsoft a rejoint la fête en publiant un laboratoire pour IE9 compatible avec la nouvelle spécification.

Voici à quoi ressemblait <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 publié n'a jamais inclus <device>. Une API de moins à gérer, je suppose :) <device> avait cependant deux avantages : 1.) elle était sémantique, et 2.) elle pouvait être facilement étendue pour prendre en charge plus que des appareils audio/vidéo.

Inspirez, expirez. Les choses évoluent vite !

3e tour : 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 aux efforts plus importants déployés pour 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. Il permet d'accéder au flux local de l'appareil photo/micro de l'utilisateur.

Assistance :

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

Premiers pas

Avec navigator.mediaDevices.getUserMedia(), nous pouvons enfin utiliser l'entrée de la webcam et du micro sans plug-in. L'accès à la caméra est désormais disponible en un appel, et non en une installation. Il est intégré directement au navigateur. Vous avez hâte de commencer ?

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

Obtenir l'accès à 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 exemple parfait d'utilisation combinée des nouvelles API HTML5. Il fonctionne 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> dans l'élément <video>. Au lieu d'alimenter la vidéo avec une URL vers un fichier multimédia, nous définissons srcObject sur l'objet LocalMediaStream représentant la webcam.

J'indique également à <video> la valeur 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)

Le premier paramètre de getUserMedia() peut également être utilisé pour spécifier davantage d'exigences (ou de contraintes) sur le flux multimédia renvoyé. Par exemple, au lieu de simplement indiquer 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 détecté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);
}

Regardez la démonstration géniale de Sam Dutton qui explique 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(), ce qui permet aux utilisateurs d'accorder ou de refuser l'accès à leur appareil photo/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 des créations 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 prise en charge 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;
}