Communication en temps réel avec WebRTC

1. Introduction

WebRTC est un projet Open Source qui permet la communication en temps réel de données audio, vidéo et de données dans des applications Web et natives.

WebRTC comporte plusieurs API JavaScript. Cliquez sur les liens pour voir les démonstrations.

Où puis-je utiliser WebRTC ?

Dans Firefox, Opera et Chrome sur ordinateur et Android. WebRTC est également disponible pour les applications natives sur iOS et Android.

Qu'est-ce que la signalisation ?

WebRTC utilise RTCPeerConnection pour communiquer des flux de données entre les navigateurs, mais a également besoin d'un mécanisme pour coordonner la communication et envoyer des messages de contrôle, un processus appelé "signalement". Les méthodes et protocoles de signalisation ne sont pas spécifiés par WebRTC. Dans cet atelier de programmation, vous allez utiliser Socket.IO pour la messagerie, mais il existe de nombreuses alternatives.

Que sont STUN et TURN ?

WebRTC est conçu pour fonctionner de pair à pair (peer-to-peer), afin que les utilisateurs puissent se connecter via la route la plus directe possible. Cependant, WebRTC est conçu pour gérer la mise en réseau réelle: les applications clientes doivent traverser les passerelles NAT et les pare-feu, et la mise en réseau peer-to-peer requiert des solutions de secours en cas d'échec de la connexion directe. Dans le cadre de ce processus, les API WebRTC utilisent des serveurs STUN pour obtenir l'adresse IP de votre ordinateur, et des serveurs TURN pour fonctionner comme des serveurs de relais en cas d'échec de la communication peer-to-peer. (voir la section WebRTC dans le monde réel pour plus d'informations).

WebRTC est-il sécurisé ?

Le chiffrement est obligatoire pour tous les composants WebRTC, et ses API JavaScript ne peuvent être utilisées qu'à partir d'origines sécurisées (HTTPS ou localhost). Les mécanismes de signalement ne sont pas définis par les normes WebRTC. C'est donc à vous de vous assurer d'utiliser des protocoles sécurisés.

2. Présentation

Créez une application permettant d'enregistrer des vidéos et des instantanés avec votre webcam, puis de les partager peer-to-peer via WebRTC. Vous apprendrez à utiliser les principales API WebRTC et à configurer un serveur de messagerie à l'aide de Node.js.

Points abordés

  • Recevoir une vidéo à partir de votre webcam
  • Lire des vidéos en streaming avec RTCPeerConnection
  • Diffuser des données par flux avec RTCDataChannel
  • Configurer un service de signalement pour échanger des messages
  • Combiner la connexion au pair et la signalisation
  • Prendre une photo et la partager via un canal de données

Prérequis

  • Chrome 47 ou version ultérieure
  • Serveur Web pour Chrome, ou utilisez le serveur Web de votre choix.
  • Exemple de code
  • Un éditeur de texte
  • Connaissances de base de HTML, CSS et JavaScript

3. Obtenir l'exemple de code

Télécharger le code

Si vous connaissez bien Git, vous pouvez télécharger le code de cet atelier de programmation depuis GitHub en le clonant:

git clone https://github.com/googlecodelabs/webrtc-web

Vous pouvez également cliquer sur le bouton suivant pour télécharger le code dans un fichier .zip:

Ouvrez le fichier ZIP téléchargé. Cette opération a pour effet de décompresser un dossier de projet (adaptive-web-media), qui contient un dossier pour chaque étape de cet atelier de programmation, ainsi que toutes les ressources dont vous aurez besoin.

Vous ferez tout votre travail de codage dans le répertoire nommé work.

Les dossiers step-nn contiennent une version terminée pour chaque étape de cet atelier de programmation. Ils sont fournis à titre de référence.

Installer et vérifier le serveur Web

Bien que vous soyez libre d'utiliser votre propre serveur Web, cet atelier de programmation est conçu pour fonctionner correctement avec le serveur Web Chrome. Si vous n'avez pas encore installé cette application, vous pouvez l'installer depuis le Chrome Web Store.

6ddeb4aee53c0f0e.png

Après avoir installé l'application Serveur Web pour Chrome, cliquez sur le raccourci des applications Chrome dans la barre des favoris, sur une page "Nouvel onglet" ou depuis le lanceur d'applications:

1d2b4aa977ab7e24.png

Cliquez sur l'icône du serveur Web:

27fce4494f641883.png

La boîte de dialogue suivante s'affiche. Elle vous permet de configurer votre serveur Web local:

Capture d'écran 18/02/2016 à 11.48.14 AM.png

Cliquez sur le bouton CHOISIR UN DOSSIER, puis sélectionnez le dossier professionnel que vous venez de créer. Cela vous permettra d'afficher votre travail en cours dans Chrome via l'URL mise en surbrillance dans la boîte de dialogue "Serveur Web" de la section URL du ou des serveurs Web.

Sous Options, cochez la case Afficher automatiquement index.html comme indiqué ci-dessous:

Capture d'écran 18/02/2016 à 11:56.30.png

Arrêtez ensuite le serveur, puis redémarrez-le en faisant glisser le bouton Web Server: STARTED (Serveur Web : DÉMARRÉ) vers la gauche, puis vers la droite.

Capture d'écran 18/02/2016 à 12:22/18.png

Accédez maintenant à votre site professionnel dans votre navigateur Web en cliquant sur l'URL de serveur Web mise en surbrillance. La page qui s'affiche doit ressembler à l'exemple suivant : work/index.html :

18a705cb6ccc5181.png

De toute évidence, cette application n'a encore rien d'intéressant. Pour l'instant, il ne s'agit que d'une structure minimale que nous utilisons pour vérifier que votre serveur Web fonctionne correctement. Vous ajouterez des fonctionnalités et des fonctionnalités de mise en page aux étapes suivantes.

4. Diffuser une vidéo depuis votre webcam

Points abordés

Au cours de cette étape, vous apprendrez à effectuer les opérations suivantes:

  • Enregistrez un flux vidéo à partir de votre webcam.
  • Manipuler la lecture du flux
  • Utilisez les fichiers CSS et SVG pour manipuler la vidéo.

Vous trouverez la version complète de cette étape dans le dossier step-01.

Un peu de HTML...

Ajoutez un élément video et un élément script au fichier index.html de votre répertoire work:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <video autoplay playsinline></video>

  <script src="js/main.js"></script>

</body>

</html>

...et un pincement de JavaScript

Ajoutez les éléments suivants au fichier main.js de votre dossier js:

'use strict';

// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
  video: true,
};

// Video element where stream will be placed.
const localVideo = document.querySelector('video');

// Local stream that will be reproduced on the video.
let localStream;

// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Essayer

Ouvrez le fichier index.html dans votre navigateur. Vous devriez obtenir un résultat semblable à celui-ci (avec, bien entendu, la vue de votre webcam):

9297048e43ed0f3d.png

Fonctionnement

Après l'appel getUserMedia(), le navigateur demande à l'utilisateur l'autorisation d'accéder à sa caméra (s'il s'agit de la première demande d'accès à la caméra pour l'origine actuelle). Si l'opération réussit, un flux MediaStream est renvoyé, et il peut être utilisé par un élément multimédia via l'attribut srcObject:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);


}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

L'argument constraints vous permet de spécifier le contenu multimédia à obtenir. Dans cet exemple, il ne s'agit que de la vidéo, car l'audio est désactivé par défaut:

const mediaStreamConstraints = {
  video: true,
};

Vous pouvez utiliser des contraintes pour des exigences supplémentaires telles que la résolution vidéo:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

La spécification MediaTrackConstraints répertorie tous les types de contraintes potentiels, bien que toutes les options ne soient pas compatibles avec tous les navigateurs. Si la résolution demandée n'est pas compatible avec la caméra actuellement sélectionnée, getUserMedia() sera refusé avec une OverconstrainedError et l'utilisateur ne sera pas invité à autoriser l'accès à sa caméra.

Si la requête getUserMedia() réussit, le flux vidéo de la webcam est défini comme source de l'élément vidéo:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Points bonus

  • L'objet localStream transmis à getUserMedia() est dans le champ d'application global. Vous pouvez donc l'inspecter depuis la console du navigateur: ouvrez la console, saisissez stream, puis appuyez sur Entrée. (Pour afficher la console dans Chrome, appuyez sur les touches Ctrl+Maj+J ou Commande+Option+J sur un Mac.)
  • Que renvoie localStream.getVideoTracks() ?
  • Essayez d'appeler le localStream.getVideoTracks()[0].stop().
  • Examinez l'objet de contrainte: que se passe-t-il lorsque vous le remplacez par {audio: true, video: true} ?
  • Quelle est la taille de l'élément vidéo ? Comment obtenir la taille naturelle de la vidéo à partir de JavaScript plutôt qu'à l'aide de la taille d'affichage ? Utilisez les outils pour les développeurs Chrome pour vérifier.
  • Essayez d'ajouter des filtres CSS à l'élément vidéo. Exemple :
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Essayez d'ajouter des filtres SVG. Exemple :
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Ce que vous avez appris

Au cours de cette étape, vous avez appris à:

  • Obtenez la vidéo à partir de votre webcam.
  • Définissez des contraintes multimédias.
  • Désigne l'élément vidéo.

Vous trouverez la version complète de cette étape dans le dossier step-01.

Conseils

  • N'oubliez pas l'attribut autoplay de l'élément video. Sans cela, vous ne verrez qu'une seule image.
  • Il existe de nombreuses autres options pour les contraintes getUserMedia(). Consultez la démonstration à l'adresse webrtc.github.io/samples/src/content/peerconnection/constraints. Comme vous le verrez, ce site contient de nombreux exemples WebRTC intéressants.

Bonne pratique

  • Assurez-vous que votre élément vidéo ne dépasse pas de son conteneur. Nous avons ajouté width et max-width afin de définir la taille préférée et la taille maximale de la vidéo. Le navigateur calcule automatiquement la hauteur:
video {
  max-width: 100%;
  width: 320px;
}

Étape suivante

Vous possédez une vidéo, mais comment la diffuser en streaming ? Pour le savoir, passez à l'étape suivante.

5. Lire des vidéos en streaming avec RTCPeerConnection

Points abordés

Au cours de cette étape, vous apprendrez à effectuer les opérations suivantes:

  • Éliminez les différences entre les navigateurs grâce au shim WebRTC, adapter.js.
  • Utilisez l'API RTCPeerConnection pour diffuser des vidéos en streaming.
  • Contrôlez la diffusion et la capture de contenus multimédias.

Vous trouverez la version complète de cette étape dans le dossier step-2.

Qu'est-ce que RTCPeerConnection ?

RTCPeerConnection est une API qui permet d'effectuer des appels WebRTC afin de diffuser des contenus vidéo et audio, et d'échanger des données.

Cet exemple configure une connexion entre deux objets RTCPeerConnection (appelés pairs) sur la même page.

Pas beaucoup d'utilisation pratique, mais bon pour comprendre le fonctionnement de RTCPeerConnection.

Ajouter des éléments vidéo et des boutons de commande

Dans le fichier index.html, remplacez l'élément vidéo par deux éléments vidéo et trois boutons:

<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>


<div>
  <button id="startButton">Start</button>
  <button id="callButton">Call</button>
  <button id="hangupButton">Hang Up</button>
</div>

Un élément vidéo affichera le flux de getUserMedia() et l'autre affichera la même vidéo diffusée en streaming via RTCPeerconnection. (Dans une application réelle, un élément vidéo affichera le flux local et l'autre le flux distant.)

Ajouter le shim adapt.js

Ajoutez un lien vers la version actuelle du fichier adapter.js au-dessus du lien vers main.js:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Le fichier Index.html doit maintenant se présenter comme suit:

<!DOCTYPE html>
<html>

<head>
  <title>Realtime communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Realtime communication with WebRTC</h1>

  <video id="localVideo" autoplay playsinline></video>
  <video id="remoteVideo" autoplay playsinline></video>

  <div>
    <button id="startButton">Start</button>
    <button id="callButton">Call</button>
    <button id="hangupButton">Hang Up</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

Installer le code RTCPeerConnection

Remplacez main.js par la version qui se trouve dans le dossier step-02.

Passer l'appel

Ouvrez le fichier index.html, cliquez sur le bouton Start (Démarrer) pour obtenir la vidéo de votre webcam, puis cliquez sur Call (Appeler) pour établir la connexion d'appairage. Vous devriez voir la même vidéo (de votre webcam) dans les deux éléments vidéo. Affichez la console du navigateur pour consulter la journalisation WebRTC.

Fonctionnement

Cette étape a beaucoup d'impact...

WebRTC utilise l'API RTCPeerConnection pour configurer une connexion afin de diffuser des vidéos entre des clients WebRTC, appelés pairs.

Dans cet exemple, les deux objets RTCPeerConnection se trouvent sur la même page: pc1 et pc2. Pas beaucoup d'utilisation pratique, mais bon pour démontrer le fonctionnement des API.

La configuration d'un appel entre des pairs WebRTC implique trois tâches:

  • Créez une connexion RTCPeerConnection pour chaque fin de l'appel et, à chaque extrémité, ajoutez le flux local de getUserMedia().
  • Obtenir et partager des informations réseau: les points de terminaison de connexion potentiels sont appelés candidats ICE.
  • Obtenir et partager des descriptions locales et distantes: métadonnées sur des contenus multimédias locaux au format SDP.

Imaginez qu'Alice et Bob souhaitent utiliser RTCPeerConnection pour organiser un chat vidéo.

Tout d'abord, Alice et Bob échangent des informations sur le réseau. L'expression "recherche de candidats" fait référence au processus de recherche des interfaces et des ports réseau à l'aide du framework ICE.

  1. Alice crée un objet RTCPeerConnection avec un gestionnaire onicecandidate (addEventListener('icecandidate')). Il correspond au code suivant issu du fichier main.js:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice appelle getUserMedia() et ajoute le flux transmis à celui-ci:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. Le gestionnaire onicecandidate de l'étape 1 est appelé lorsque des réseaux candidats sont disponibles.
  2. Alice envoie des données de candidats sérialisées à Bob. Dans une application réelle, ce processus (appelé signalement) s'effectue via un service de messagerie. Vous découvrirez comment procéder ultérieurement. Bien entendu, à cette étape, les deux objets RTCPeerConnection se trouvent sur la même page et peuvent communiquer directement sans passer par une messagerie externe.
  3. Lorsque Bob reçoit un message d'Alice sur un candidat, il appelle addIceCandidate() pour ajouter le candidat à la description du pair distant:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

Les pairs WebRTC doivent également rechercher et échanger des informations sur les médias audio et vidéo locaux et distants, comme la résolution et les capacités du codec. La signalisation pour échanger des informations de configuration de médias s'effectue par l'échange de blobs de métadonnées, appelés offre et réponse, à l'aide du format Session Description Protocol, connu sous le nom de SDP:

  1. Alice exécute la méthode createOffer() de RTCPeerConnection. La promesse renvoyée fournit une RTCSessionDescription: description de la session locale d'Alice:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. Si l'opération réussit, Alice définit la description locale à l'aide de setLocalDescription(), puis envoie cette description de session à Bob via son canal de signalement.
  2. Bob définit la description qu'Alice lui a envoyée comme description distante à l'aide de setRemoteDescription().
  3. Bob exécute la méthode createAnswer() de RTCPeerConnection, en lui transmettant la description distante qu'Alice lui a fournie, afin qu'une session locale compatible avec la sienne puisse être générée. La promesse createAnswer() transmet une RTCSessionDescription: Bob la définit comme description locale et l'envoie à Alice.
  4. Lorsque Alice reçoit la description de la session de Bob, elle la définit comme description distante avec setRemoteDescription().
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}
  1. Ding !

Points bonus

  1. Accédez à la page chrome://webrtc-internals. Cela fournit des statistiques et des données de débogage WebRTC. (La liste complète des URL Chrome est disponible à l'adresse chrome://about.)
  2. Appliquer un style à la page avec CSS:
  • Placez les vidéos côte à côte.
  • Faites en sorte que les boutons soient de la même largeur, avec du texte plus grand.
  • Assurez-vous que la mise en page fonctionne sur mobile.
  1. Dans la console des outils pour les développeurs Chrome, examinez localStream, localPeerConnection et remotePeerConnection.
  2. Dans la console, examinez localPeerConnectionpc1.localDescription. À quoi ressemble le format SDP ?

Ce que vous avez appris

Au cours de cette étape, vous avez appris à:

  • Éliminez les différences entre les navigateurs grâce au shim WebRTC, adapter.js.
  • Utilisez l'API RTCPeerConnection pour diffuser des vidéos en streaming.
  • Contrôlez la diffusion et la capture de contenus multimédias.
  • Partagez des informations multimédias et réseau entre pairs pour permettre un appel WebRTC.

Vous trouverez la version complète de cette étape dans le dossier step-2.

Conseils

  • Il y a beaucoup à apprendre à cette étape ! Pour découvrir d'autres ressources expliquant RTCPeerConnection plus en détail, rendez-vous sur webrtc.org. Si vous souhaitez utiliser WebRTC sans manipuler les API, vous trouverez sur cette page des suggestions pour les frameworks JavaScript.
  • Pour en savoir plus sur le shim adapt.js, consultez le dépôt GitHub adapter.js.
  • Vous voulez découvrir la meilleure application de chat vidéo au monde ? Examinez AppRTC, l'application canonique du projet WebRTC pour les appels WebRTC: app, code. Le temps de configuration de l'appel est inférieur à 500 ms.

Bonne pratique

  • Pour pérenniser votre code, utilisez les nouvelles API basées sur Promise et activez la compatibilité avec les navigateurs qui ne les acceptent pas à l'aide du fichier adapter.js.

Étape suivante

Cette étape explique comment utiliser WebRTC pour diffuser des vidéos entre pairs, mais cet atelier de programmation porte également sur les données.

À l'étape suivante, vous découvrirez comment diffuser des données arbitraires à l'aide de RTCDataChannel.

6. Utiliser RTCDataChannel pour échanger des données

Points abordés

  • Échange de données entre des points de terminaison WebRTC (pairs)

Vous trouverez la version complète de cette étape dans le dossier step-03.

Mettre à jour votre code HTML

Pour cette étape, vous allez utiliser les canaux de données WebRTC pour envoyer du texte entre deux éléments textarea sur la même page. Ce n'est pas très utile, mais cela montre comment WebRTC peut être utilisé pour partager des données ainsi que pour le streaming vidéo.

Supprimez les éléments vidéo et de bouton du fichier index.html, puis remplacez-les par le code HTML suivant:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

Une zone de texte servira à saisir du texte, l'autre affichera le texte tel qu'il est diffusé entre les pairs.

Le fichier index.html doit maintenant se présenter comme suit:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
  <textarea id="dataChannelReceive" disabled></textarea>

  <div id="buttons">
    <button id="startButton">Start</button>
    <button id="sendButton">Send</button>
    <button id="closeButton">Stop</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Mettre à jour votre code JavaScript

Remplacez main.js par le contenu de step-03/js/main.js.

Essayez de diffuser des données entre pairs: ouvrez le fichier index.html, appuyez sur Start (Démarrer) pour configurer la connexion au pair, saisissez du texte dans textarea à gauche, puis cliquez sur Send (Envoyer) pour transférer le texte à l'aide des canaux de données WebRTC.

Fonctionnement

Ce code utilise RTCPeerConnection et RTCDataChannel pour permettre l'échange de messages texte.

Le code de cette étape est en grande partie identique à celui de l'exemple RTCPeerConnection.

Les fonctions sendData() et createConnection() comportent la majeure partie du nouveau code:

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

La syntaxe de RTCDataChannel est volontairement semblable à celle de WebSocket, avec une méthode send() et un événement message.

Notez l'utilisation de dataConstraint. Les canaux de données peuvent être configurés pour permettre différents types de partage de données, par exemple en privilégiant une diffusion fiable plutôt que les performances. Pour en savoir plus sur les options, consultez le Mozilla Developer Network.

Points bonus

  1. Avec SCTP, le protocole utilisé par les canaux de données WebRTC, la diffusion de données fiable et ordonnée est activée par défaut. Dans quels cas RTCDataChannel doit-il fournir des données de manière fiable et quand les performances sont-elles plus importantes, même si cela implique de perdre certaines données ?
  2. Utiliser le CSS pour améliorer la mise en page et ajouter un attribut d'espace réservé à "dataChannelReceive" zone de texte.
  3. Testez la page sur un appareil mobile.

Ce que vous avez appris

Au cours de cette étape, vous avez appris à:

  • Établir une connexion entre deux pairs WebRTC
  • Échangez des données textuelles entre les pairs.

Vous trouverez la version complète de cette étape dans le dossier step-03.

En savoir plus

Étape suivante

Vous avez appris à échanger des données entre pairs sur la même page, mais comment le faire entre différentes machines ? Tout d'abord, vous devez configurer un canal de signalement pour échanger des messages de métadonnées. Découvrez comment procéder à l'étape suivante.

7. Configurer un service de signalement pour échanger des messages

Points abordés

Au cours de cette étape, vous apprendrez à effectuer les opérations suivantes:

  • Utilisez npm pour installer les dépendances du projet comme spécifié dans le fichier package.json.
  • Exécutez un serveur Node.js et utilisez node-static pour diffuser les fichiers statiques.
  • Configurer un service de messagerie sur Node.js à l'aide de Socket.IO.
  • Utilisez-le pour créer des "pièces" et échanger des messages.

Vous trouverez la version complète de cette étape dans le dossier step-04.

Concepts

Pour configurer et gérer un appel WebRTC, les clients (pairs) WebRTC doivent échanger des métadonnées:

  • Informations sur le candidat (réseau).
  • Messages d'offre et de réponse fournissant des informations sur les contenus multimédias, tels que la résolution et les codecs.

En d'autres termes, un échange de métadonnées est nécessaire pour que le streaming peer-to-peer de données audio, vidéo ou de données puisse avoir lieu. Ce processus est appelé signalement.

Lors des étapes précédentes, les objets RTCPeerConnection de l'émetteur et du destinataire se trouvent sur la même page, donc la "signalisation" consiste simplement à transmettre des métadonnées entre des objets.

Dans une application réelle, les connexions RTCPeerConnection de l'émetteur et du destinataire s'exécutent sur des pages Web sur différents appareils, et vous devez leur permettre de communiquer les métadonnées.

Pour ce faire, vous utilisez un serveur de signalisation, c'est-à-dire un serveur capable de transmettre des messages entre des clients WebRTC (pairs). Les messages réels sont du texte brut, c'est-à-dire des objets JavaScript concaténés.

Prérequis: Installer Node.js

Pour passer aux étapes suivantes de cet atelier de programmation (dossiers step-04 à step-06), vous devrez exécuter un serveur sur localhost à l'aide de Node.js.

Vous pouvez télécharger et installer Node.js à partir de ce lien ou via le gestionnaire de packages de votre choix.

Une fois installé, vous pourrez importer les dépendances requises pour les étapes suivantes (en exécutant npm install) et exécuter un petit serveur localhost pour exécuter l'atelier de programmation (en exécutant node index.js). Ces commandes seront indiquées ultérieurement, lorsqu'elles seront nécessaires.

À propos de l'application

WebRTC utilise une API JavaScript côté client, mais pour une utilisation réelle, nécessite également un serveur de signalisation (messagerie), ainsi que des serveurs STUN et TURN. Pour en savoir plus, cliquez ici.

Au cours de cette étape, vous allez créer un serveur de signalisation Node.js simple, en utilisant le module Socket.IO Node.js et la bibliothèque JavaScript pour la messagerie. Une expérience de Node.js et de Socket.IO sera utile, mais pas cruciale. les composants de messagerie sont très simples.

Dans cet exemple, le serveur (l'application Node.js) est mis en œuvre dans index.js, tandis que le client qui y est exécuté (l'application Web) est mis en œuvre dans index.html.

L'application Node.js de cette étape comporte deux tâches.

Tout d'abord, elle agit comme un relais de messages:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

Deuxièmement, il gère les "salons" de chat vidéo WebRTC :

if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

Notre application WebRTC simple permettra à un maximum de deux pairs de partager une salle.

HTML et JavaScript

Mettez à jour le fichier index.html pour qu'il se présente comme suit:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Vous ne verrez rien sur la page à cette étape: la journalisation est effectuée dans la console du navigateur. (Pour afficher la console dans Chrome, appuyez sur les touches Ctrl+Maj+J ou Commande+Option+J sur un Mac.)

Remplacez js/main.js par ce qui suit:

'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

Configurer Socket.IO pour qu'il s'exécute sur Node.js

Dans le fichier HTML, vous avez peut-être constaté que vous utilisez un fichier Socket.IO:

<script src="/socket.io/socket.io.js"></script>

En haut de votre répertoire work, créez un fichier nommé package.json avec le contenu suivant:

{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Il s'agit d'un fichier manifeste d'application qui indique à Node Package Manager (npm) les dépendances du projet à installer.

Pour installer des dépendances (telles que /socket.io/socket.io.js), exécutez la commande suivante à partir du terminal de ligne de commande, dans votre répertoire work (travail) :

npm install

Vous devriez voir un journal d'installation qui se termine comme suit:

3ab06b7bcc7664b9.png

Comme vous pouvez le voir, npm a installé les dépendances définies dans le fichier package.json.

Créez un fichier index.js au premier niveau de votre répertoire work (et non dans le répertoire js) et ajoutez le code suivant:

'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});

À partir du terminal de ligne de commande, exécutez la commande suivante dans le répertoire work:

node index.js

Dans votre navigateur, ouvrez localhost:8080.

À chaque fois que vous ouvrez cette URL, vous êtes invité à saisir un nom de pièce. Pour rejoindre la même salle, choisissez à chaque fois le même nom, par exemple "foo".

Ouvrez une page de nouvel onglet, puis ouvrez à nouveau localhost:8080. Choisissez le même nom pour la pièce.

Ouvrez localhost:8080 dans un troisième onglet ou une troisième fenêtre. Sélectionnez à nouveau le même nom de pièce.

Consultez la console dans chacun des onglets: vous devriez voir la journalisation à partir du code JavaScript ci-dessus.

Points bonus

  1. Quels autres mécanismes de communication pourraient être possibles ? Quels problèmes pourriez-vous rencontrer en utilisant WebSocket?
  2. Quels problèmes liés au scaling de cette application ? Pouvez-vous mettre au point une méthode permettant de tester simultanément des milliers ou des millions de demandes de salles ?
  3. Cette application utilise une invite JavaScript pour obtenir le nom d'une pièce. Trouvez un moyen d'obtenir le nom de la salle à partir de l'URL. Par exemple, localhost:8080/foo donne le nom de la salle foo.

Ce que vous avez appris

Au cours de cette étape, vous avez appris à effectuer les tâches suivantes:

  • Utiliser npm pour installer les dépendances du projet comme spécifié dans package.json
  • Exécutez des fichiers statiques de serveur à serveur Node.js.
  • Configurer un service de messagerie sur Node.js à l'aide de socket.io
  • Utilisez-le pour créer des "pièces" et échanger des messages.

Vous trouverez la version complète de cette étape dans le dossier step-04.

En savoir plus

Étape suivante

Découvrez comment utiliser la signalisation pour permettre à deux utilisateurs d'établir une connexion appairée.

8. Combiner la connexion au pair et la signalisation

Points abordés

Au cours de cette étape, vous apprendrez à effectuer les opérations suivantes:

  • Exécuter un service de signalement WebRTC à l'aide de Socket.IO exécuté sur Node.js
  • Utilisez ce service pour échanger des métadonnées WebRTC entre pairs.

Vous trouverez la version complète de cette étape dans le dossier step-05.

Remplacer HTML et JavaScript

Remplacez le contenu du fichier index.html par ce qui suit:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <div id="videos">
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Remplacez js/main.js par le contenu de step-05/js/main.js.

Exécuter le serveur Node.js

Si vous ne suivez pas cet atelier de programmation depuis votre répertoire work, vous devrez peut-être installer les dépendances du dossier step-05 ou de votre dossier de travail actuel. Exécutez la commande suivante à partir de votre répertoire de travail:

npm install

Une fois installé, si votre serveur Node.js n'est pas en cours d'exécution, démarrez-le en appelant la commande suivante dans le répertoire work:

node index.js

Assurez-vous d'utiliser la version du fichier index.js de l'étape précédente qui implémente Socket.IO. Pour en savoir plus sur les E/S de nœud et de socket, consultez la section "Configurer un service de signalement pour échanger des messages".

Dans votre navigateur, ouvrez localhost:8080.

Ouvrez à nouveau localhost:8080 dans un nouvel onglet ou une nouvelle fenêtre. Un élément vidéo affichera le flux local de getUserMedia() et l'autre affichera le flux "distant" diffusé en streaming via RTCPeerconnection.

Affichez la journalisation dans la console du navigateur.

Points bonus

  1. Cette application prend uniquement en charge le chat vidéo en tête-à-tête. Comment pouvez-vous modifier l'interface pour permettre à plusieurs personnes de partager la même salle de chat vidéo ?
  2. Dans cet exemple, le nom de chambre foo est codé en dur. Quel serait le meilleur moyen d'afficher d'autres noms de pièces ?
  3. Comment les utilisateurs partageraient-ils le nom de la salle ? Essayez de trouver une alternative au partage des noms des salons.
  4. Comment pourriez-vous modifier l'application

Ce que vous avez appris

Au cours de cette étape, vous avez appris à:

  • Exécuter un service de signalement WebRTC à l'aide de Socket.IO exécuté sur Node.js.
  • Utilisez ce service pour échanger des métadonnées WebRTC entre pairs.

Vous trouverez la version complète de cette étape dans le dossier step-05.

Conseils

  • Les statistiques et les données de débogage WebRTC sont disponibles sur la page chrome://webrtc-internals.
  • Vous pouvez utiliser test.webrtc.org pour vérifier votre environnement local, et tester votre caméra et votre micro.
  • Si vous rencontrez d'autres problèmes de mise en cache, procédez comme suit:
  • Effectuez une actualisation forcée en maintenant la touche Ctrl enfoncée et en cliquant sur le bouton Actualiser.
  • Redémarrez le navigateur.
  • Exécutez npm cache clean à partir de la ligne de commande.

Étape suivante

Découvrez comment prendre une photo, obtenir des données d'image et les partager avec des personnes qui travaillent à distance.

9. Prendre une photo et la partager via un canal de données

Points abordés

Au cours de cette étape, vous allez apprendre à:

  • Prenez une photo et récupérez ses données à l'aide de l'élément canevas.
  • Échangez des données d'image avec un utilisateur distant.

Vous trouverez la version complète de cette étape dans le dossier step-06.

Fonctionnement

Vous avez précédemment appris à échanger des SMS à l'aide de RTCDataChannel.

Cette étape permet de partager des fichiers entiers: dans cet exemple, des photos prises via getUserMedia().

Les principales étapes de cette étape sont les suivantes:

  1. Établir un canal de données. Notez que vous n'ajoutez aucun flux multimédia à la connexion au pair au cours de cette étape.
  2. Capturez le flux vidéo de la webcam de l'utilisateur avec getUserMedia():
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. Lorsque l'utilisateur clique sur le bouton Snap (Instantané), vous obtenez un instantané (une image vidéo) du flux vidéo et l'affiche dans un élément canvas:
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. Lorsque l'utilisateur clique sur le bouton Send (Envoyer), convertissez l'image en octets et envoyez-les via un canal de données:
function sendPhoto() {
  // Split data channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

  console.log('Sending a total of ' + len + ' byte(s)');
  dataChannel.send(len);

  // split the photo and send in chunks of about 64KB
  for (var i = 0; i < n; i++) {
    var start = i * CHUNK_LEN,
      end = (i + 1) * CHUNK_LEN;
    console.log(start + ' - ' + (end - 1));
    dataChannel.send(img.data.subarray(start, end));
  }

  // send the reminder, if any
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}
  1. Le côté réception reconvertit les octets du message du canal de données en image et la présente à l'utilisateur:
function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // trail is the element holding the incoming images
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

Obtenir le code

Remplacez le contenu du dossier work par celui de l'étape step-06. Le fichier index.html de votre fichier work doit maintenant se présenter comme suit :

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Si vous ne suivez pas cet atelier de programmation depuis votre répertoire work, vous devrez peut-être installer les dépendances du dossier step-06 ou de votre dossier de travail actuel. Il vous suffit d'exécuter la commande suivante à partir de votre répertoire de travail:

npm install

Une fois installé, si votre serveur Node.js n'est pas en cours d'exécution, démarrez-le en appelant la commande suivante depuis votre répertoire work:

node index.js

Assurez-vous d'utiliser la version de index.js qui implémente Socket.IO et n'oubliez pas de redémarrer votre serveur Node.js si vous apportez des modifications. Pour en savoir plus sur les E/S de nœud et de socket, consultez la section "Configurer un service de signalement pour échanger des messages".

Si nécessaire, cliquez sur le bouton Allow (Autoriser) pour autoriser l'application à utiliser votre webcam.

L'application va créer un identifiant de chambre aléatoire et l'ajouter à l'URL. Ouvrez l'URL à partir de la barre d'adresse dans un nouvel onglet ou une nouvelle fenêtre du navigateur.

Cliquez sur le bouton Snap & Envoyer, puis regardez la zone "Éléments entrants" dans l'autre onglet en bas de la page. L'application transfère les photos entre les onglets.

L'écran qui s'affiche devrait ressembler à ce qui suit :

911b40f36ba6ba8.png

Points bonus

  1. Comment modifier le code pour permettre le partage de n'importe quel type de fichier ?

En savoir plus

Ce que vous avez appris

  • Comment prendre une photo et en extraire des données à l'aide de l'élément canevas
  • Comment échanger ces données avec un utilisateur distant

Vous trouverez la version complète de cette étape dans le dossier step-06.

10. Félicitations

Vous avez créé une application permettant de diffuser des vidéos en temps réel et d'échanger des données.

Ce que vous avez appris

Dans cet atelier de programmation, vous avez appris ce qui suit :

  • Obtenez la vidéo à partir de votre webcam.
  • Visionnez des vidéos en streaming avec RTCPeerConnection.
  • Diffuser des données par flux avec RTCDataChannel
  • Configurez un service de signalement pour échanger des messages.
  • Combinez la connexion au pair et la signalisation.
  • Prenez une photo et partagez-la via un canal de données.

Étapes suivantes

En savoir plus

  • De nombreuses ressources pour vous familiariser avec WebRTC sont disponibles sur webrtc.org.