Choisir les caméras en Java Script avec l'API mediaDevices

April 19, 2018
Rédigé par
Phil Nash
Twilion

Choisir les caméras en Java Script avec l'API mediaDevices

La plupart des smartphones possèdent un appareil photo sur l’arrière ainsi qu’une caméra frontale. Lorsque vous développez une application vidéo mobile, vous voudrez sûrement pouvoir choisir ou pouvoir switcher entre les deux.

Si vous construisez une application de chat, vous voudrez sûrement sélectionner la caméra frontale, mais si vous construisez une application d’appareil photo alors vous serez plus intéressé par l’arrière. Dans cet article, nous allons voir comment choisir ou switcher entre les caméras en utilisant l’API mediaDevices et les contraintes du support.

Ce dont vous aurez besoin

Pour suivre ce tutoriel, vous aurez besoin :

  • D’un appareil iOS ou Android avec deux caméras pour pouvoir faire les tests. Si vous avez deux webcams, cela fonctionnera aussi sur votre ordinateur
  • ngrok afin de pouvoir accéder au projet facilement depuis votre appareil mobile (et aussi parce que je trouve que ngrok est génial)
  • Du code dans ce répertoire GitHub pour pouvoir vous lancer

Pour avoir le code, clonez le projet et faites un checkout du starter project tag.

git clone https://github.com/philnash/mediadevices-camera-selection.git -b initial-project
cd mediadevices-camera-selection

Ce starter project vous donne un peu de HTML et de CSS afin que vous puissiez vous concentrer sur le JavaScript. Vous pouvez directement ouvrir le fichier index.html mais je vous conseille de servir ces fichier via un webserver. Pour ma part, j’aime bien utiliser le module npm serve. Je l’ai aussi inclus dans le répertoire. Pour l’utiliser, installez d’abord la dépendance avec npm et ensuite, démarrez le serveur.

npm install
npm start

Une fois que vous exécutez le serveur, ouvrez un tunnel vers lui en utilisant ngrok. serve héberge les fichiers sur le port 5000. Pour faire un tunnel vers ce port avec ngrok, entrez ce qui suit sur le terminal, dans une nouvelle fenêtre :

ngrok http 5000

Maintenant que vous avez une version du site disponible publiquement, vous pouvez l’ouvrir sur votre appareil mobile, afin de la tester plus tard. Assurez-vous d’ouvrir l’URL HTTPS comme l’API que nous utilisons s’exécute seulement dans un contexte sécurisé.

La fenêtre ngrok affiche deux URL que vous pouvez utiliser, choisissez celle HTTPS.

L’application devrait ressembler à ça :

 L'application doit avoir un titre indiquant « Camera fun » avec un bouton et une liste déroulante vide.

Obtenir le flux de média

Notre premier challenge est d’obtenir le stream vidéo (flux de vidéo) de n’importe quelle caméra sur l’écran. Une fois cela complété, nous investiguerons les options pour sélectionner la caméra spécifique. Ouvrez app.js et commencez par choisir le bouton et les éléments vidéo à partir du DOM.

// app.js
const video = document.getElementById('video');
const button = document.getElementById('button');

Vous requêterez l’accès à la caméra en utilisant l’API mediaDevices lorsque l’utilisateur clique ou touche au bouton. Pour ce faire, vous appellerez navigator.mediaDevices.getUserMedia passant un objet de contraintes de support. Vous ne voulez que la vidéo alors configurez la vidéo sur true et l’audio sur false.

getUserMedia renvoie une promesse, lorsqu’elle est résolue, nous avons accès au flux de média de la caméra. Réglez le srcObj de la vidéo sur le stream et nous le verrons à l’écran.

button.addEventListener('click', event => {
  const constraints = {
    video: true,
    audio: false
  };
  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(stream => {
      video.srcObject = stream;
    })
    .catch(error => {
      console.error(error);
    });
});

Enregistrez le fichier, rechargez la page et cliquez sur le bouton. Cela devrait vous présenter un dialogue de permissions qui requête l’accès à votre caméra. Une fois que les permissions sont accordées, votre vidéo apparaîtra à l’écran. Essayez ça sur votre ordinateur et sur votre téléphone ; lorsque j’ai essayé avec mon iPhone, la caméra sélectionnée était la caméra frontale.

L'application appareil photo, maintenant avec mon visage dans l'espace auparavant vide !

Si vous utilisez un iPhone, assurez-vous de vérifier dans Safari, comme ça ne semble pas fonctionner avec d’autres navigateurs.

Quelles sont les caméras disponibles ?

L’API mediaDevices donne une façon d’énumérer tous les appareils disponibles pour les entrées audio et vidéo. Vous allez utiliser la fonction enumerateDevices pour  construire un ensemble d’options pour une case <select>, dans le but de pouvoir choisir les caméras que vous voulez voir. Ouvrez app.js à nouveau et commencez par choisir le <select> dans le DOM :

const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');

enumerateDevices renvoie une promesse, alors il faut écrire une fonction que vous pouvez utiliser pour recevoir le résultat de cette promesse. Cette fonction prendra une liste d’appareils médias comme argument.

La première chose à faire est de vider <select> de n’importe quelle option existante et y ajouter une <option> vide. Ensuite, il faut faire boucler à travers les appareils, filtrant tous ceux qui ne sont pas du genre “videoinput”. Après ça, créez une <option> en utilisant l’ID de l’appareil comme valeur et le label de l’appareil comme texte. Nous gérons aussi le cas où un appareil ne déclare pas de label en générant un simple libellé “Caméra n”.

const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');

function gotDevices(mediaDevices) {
  select.innerHTML = '';
  select.appendChild(document.createElement('option'));
  let count = 1;
  mediaDevices.forEach(mediaDevice => {
    if (mediaDevice.kind === 'videoinput') {
      const option = document.createElement('option');
      option.value = mediaDevice.deviceId;
      const label = mediaDevice.label || `Camera ${count++}`;
      const textNode = document.createTextNode(label);
      option.appendChild(textNode);
      select.appendChild(option);
    }
  });
}

Au bout de app.js, passez l’appel à enumerateDevices.

navigator.mediaDevices.enumerateDevices().then(gotDevices);

Rafraichissez la page et jetez un œil au menu déroulant à côté du bouton. Si vous êtes sur Android, ou que vous utilisez Chrome Firefox, vous verrez le nom des caméras disponibles.

Cependant, sur un iPhone, vous verrez les noms génériques “Caméra 1” et “Caméra 2” de notre fonction. Sur iOS, vous n’aurez pas les libellés des caméras jusqu’à ce que vous accordiez au site la permission d’utiliser au moins une caméra. Cela rend notre interface moins pratique pour sélectionner une caméra comme vous ne pouvez pas définir quelle caméra est laquelle - même si vous obtenez l’ID des appareils.

Sur l&#x27;iPhone, vous ne voyez que les étiquettes que nous avons créées, « Caméra 1 » et « Caméra 2 ».

Vous n'avez pas encore configuré le menu déroulant pour qu’il change la caméra. Avant de le faire, voyons une autre façon d’influencer sur la caméra que vous voulez sélectionner.

Mode frontal

Une approche alternative que nous pouvons utiliser pour sélectionner une caméra est la contrainte facingMode. C’est une façon moins précise de choisir une caméra, plutôt que d’avoir son ID à partir de la fonction enumerateDevices, mais elle fonctionne bien pour les appareils mobiles. Voici quatre options que vous pouvez utiliser pour la contrainte : user, environment, left et right. Les contraintes sont expliquées dans la documentation MDN. Pour les besoins de ce post, nous utiliserons user et environment comme elles mappent facilement les caméras frontales et arrière d’un appareil mobile.

Pour utiliser la contrainte facingMode, nous avons besoin de modifier les constraints dont nous nous servons dans notre appel à getUserMedia. Au lieu de juste dire true pour video nous avons besoin d’un objet de ces contraintes. Mettez le code à jour pour sélectionner la caméra frontale comme suit :

button.addEventListener('click', event => {
  const videoConstraints = {
    facingMode: 'user'
  };
  const constraints = {
    video: videoConstraints,
    audio: false
  };

Faites maintenant un test à partir de votre appareil mobile. Vous devriez voir la caméra frontale sélectionnée. Mettez à jour le facingMode sur environment et essayez à nouveau. Maintenant, c’est la caméra arrière qui devrait être sélectionnée.

Assemblez le code avec les résultats obtenus à partir de enumerateDevices au dessus pour construire un switcher de caméra, une fois que vous avez eu la permission de lire les données de la caméra.

Changer de caméra

Vous avez le code pour sélectionner une caméra utilisateur ou d’environnement sur la première sélection mais si vous voulez changer de caméra, il y a un peu plus de travail à fournir.

Tout d’abord, vous devez retenir une référence au stream actuel afin que nous puissions l’arrêter lorsque nous switchons sur un autre. Ajoutez une autre variable et fonction utilitaire en haut de app.js pour arrêter les pistes dans un stream.

const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');
let currentStream;

function stopMediaTracks(stream) {
  stream.getTracks().forEach(track => {
    track.stop();
  });
}

La fonction stopMediaTracks prend un stream et loop à travers chaque track média dans le stream, arrêtant chacune d'entre elles.

Vous changerez les caméras en appuyant sur le même bouton, donc il faut mettre à jour le listener d’événement. D’abord, si vous avez un currentStream, vous devriez l’arrêter. Ensuite, vérifiez le <select> pour voir si vous choisissez un appareil particulier et pour pouvoir construire les contraintes vidéos en fonction de ça.

Mettez à jour le handler du click sur le bouton et les contraintes vidéos comme suit :

button.addEventListener('click', event => {
  if (typeof currentStream !== 'undefined') {
    stopMediaTracks(currentStream);
  }
  const videoConstraints = {};
  if (select.value === '') {
    videoConstraints.facingMode = 'environment';
  } else {
    videoConstraints.deviceId = { exact: select.value };
  }
  const constraints = {
    video: videoConstraints,
    audio: false
  };

Lorsque vous voudrez sélectionner un appareil par son ID, vous utiliserez la contrainte exact. Cependant, évitez de faire ça pour la contrainte facingMode, comme cela pourrait tomber sur un appareil qui ne reconnaît pas le fait d’avoir un mode frontal “utilisateur” ou “environnement”, vous laissant sans média du tout.

Toujours dans le handler du clic, lorsque vous obtenez la permission d’utiliser la vidéo, vous allez changer encore quelques petites choses. Paramétrez le currentStream sur le nouveau stream passé à la fonction, afin de pouvoir l’arrêter plus tard et déclencher un autre appel à enumerateDevices.

enumerateDevices renvoie une promesse, donc vous pouvez la renvoyer de votre fonction then et enchaîner un nouveau then pour le résultat qui sera ensuite géré par votre fonction gotDevices.

Remplacez votre appel existant à getUserMedia par ce qui suit :

button.addEventListener('click', event => {
  if (typeof currentStream !== 'undefined') {
    stopMediaTracks(currentStream);
  }
  const videoConstraints = {};
  if (select.value === '') {
    videoConstraints.facingMode = 'environment';
  } else {
    videoConstraints.deviceId = { exact: select.value };
  }
  const constraints = {
    video: videoConstraints,
    audio: false
  };

  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(stream => {
      currentStream = stream;
      video.srcObject = stream;
      return navigator.mediaDevices.enumerateDevices();
    })
    .then(gotDevices)
    .catch(error => {
      console.error(error);
    });
});

Une fois que vous avez ajouté tout ce code, votre app.js devrait ressembler à celle-ci. Si vous actualisez la page, vous pouvez maintenant vous amuser à sélectionner et changer de caméra ! Ça fonctionne à la fois sur le mobile et sur le bureau.

Le résultat final, il s&#x27;agit d&#x27;une animation montrant que vous pouvez sélectionner une caméra puis changer et passer de la visualisation de la caméra arrière à la caméra avant

Prochaines étapes

Nous avons vu comment sélectionner la caméra d’un utilisateur avec les contraintes  facingMode ou deviceId. Rappelez-vous bien que facingMode est plus fiable avant d’avoir la permission d’utiliser la caméra, mais sélectionner un deviceId est plus précis. Vous pouvez obtenir tout le code de cet article dans le répertoire GitHub et vous pouvez essayer l’application en direct ici.

Si vous utilisez Twilio Video pour construire une application vidéo, vous pouvez vous servir de ces contraintes en appelant soit connect, soit createLocalVideoTrack.

Sélectionner ou changer de caméra est une fonctionnalité utile pour les chats vidéo, qui permet aux utilisateurs de choisir la caméra exacte qu’ils souhaitent utiliser dans l’interface de votre application, et peut aussi s’associer avec le partage d’écran lors d’un appel vidéo.

Aimeriez-vous voir d’autres fonctionnalités qui seraient utiles dans les vidéos chats ? Ou bien, si vous avez des questions sur ce post, contactez-moi sur Twitter : @philnash !