Activer la communication en temps réel avec WebRTC

1. Avant de commencer

Cet atelier de programmation vous explique comment créer une application permettant de générer des vidéos et de prendre des instantanés avec votre webcam, puis de les partager avec peer-to-peer avec WebRTC. Vous découvrirez également comment utiliser les principales API WebRTC et configurer un serveur de messagerie avec Node.js.

Prerequisites

  • Connaissances de base en HTML, CSS et JavaScript

Ce que vous allez faire

  • Récupérez la vidéo de votre webcam.
  • Lisez le flux vidéo avec RTCPeerConnection.
  • Lisez les données en streaming avec RTCDataChannel.
  • Configurez un service de signalisation pour échanger des messages.
  • Combinez la connexion et le signal du pair.
  • Prenez une photo et utilisez un canal de données pour le partager.

Ce dont vous avez besoin

  • Chrome version 47 ou ultérieure
  • Serveur Web pour Chrome ou un serveur Web de votre choix
  • Éditeur de texte de votre choix
  • Node.js

2. Obtenir l'exemple de code

Télécharger le code

  1. Si vous connaissez Git, exécutez la commande suivante pour cloner le code de cet atelier de programmation depuis GitHub:
git clone https://github.com/googlecodelabs/webrtc-web

Vous pouvez également cliquer sur ce lien pour télécharger un fichier ZIP du code:

  1. Ouvrez le fichier ZIP téléchargé pour décompresser un dossier de projet nommé webrtc-web-master, qui contient un dossier pour chaque étape de cet atelier de programmation et toutes les ressources dont vous avez besoin.

Vous effectuez tout votre travail de code dans le répertoire nommé work.

Les dossiers step-nn contiennent une version terminée pour chaque étape de cet atelier de programmation. Elles sont disponibles pour référence.

Installer et valider le serveur Web

Vous êtes libre d'utiliser votre propre serveur Web, mais cet atelier de programmation est conçu pour bien fonctionner avec Web Server pour Chrome.

  1. Si vous n'avez pas de serveur Web pour Chrome, cliquez sur ce lien pour l'installer à partir du Chrome Web Store:

D0a4649b4920cf3.png

  1. Cliquez sur Ajouter à Chrome pour installer le serveur Web pour Chrome et ouvrir automatiquement vos applications Google dans un nouvel onglet.
  2. Cliquez sur Web Server (Serveur Web) :

27fce4494f641883.png

Une boîte de dialogue vous permet de configurer votre serveur Web local:

A300381a486b9e22.png

  1. Cliquez sur Sélectionner un dossier.
  2. Sélectionnez le dossier work que vous avez créé.

Sous URL du ou des serveurs Web, vous voyez l'URL depuis laquelle vous pouvez consulter votre travail en cours.

Chrome,

  1. Sous Options (peut nécessiter un redémarrage), cochez la case Automatically show index.html.
  2. Sélectionnez l'option Serveur Web: démarré pour arrêter et redémarrer le serveur.

f23cafb3993dfac1.png

  1. Cliquez sur l'URL sous URL du serveur Web pour afficher votre travail dans votre navigateur Web.

Une page semblable à work/index.html devrait s'afficher:

18a/705cb6ccc5181.png

Évidemment, cette appli ne fait rien d'intéressant pour l'instant. Il ne s'agit que d'un squelette minimal pour garantir le bon fonctionnement de votre serveur Web. Vous ajouterez des fonctionnalités dans les étapes suivantes.

3. Diffuser le flux vidéo de votre webcam

Une version complète de cette étape se trouve dans le dossier step-01.

Ajouter un tiret HTML

Copiez ce code et collez-le dans le fichier index.html de votre répertoire work pour ajouter un élément video et script:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <video autoplay playsinline></video>

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

</body>

</html>

Ajouter un pinceau JavaScript

Copiez ce code et collez-le dans le fichier main.js de votre dossier js:

'use strict';

// In this codelab, you  only stream video, not audio (video: true).
const mediaStreamConstraints = {
  video: true,
};

// The video element where the stream is displayed
const localVideo = document.querySelector('video');

// The local stream that's displayed on the video
let localStream;

// Handle success and add the MediaStream to the video element
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

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

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

Essayer

Ouvrez le fichier index.html dans votre navigateur. Vous devriez obtenir un écran semblable à celui-ci, mais avec l'affichage de votre webcam, bien sûr:

9297048e43ed0f3d.png

Fonctionnement

Après l'appel de getUserMedia(), le navigateur demande l'autorisation d'accéder à votre 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, MediaStream est renvoyé. Un élément media peut être utilisé 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, le contenu multimédia est uniquement une vidéo, car le son 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, même si toutes les options ne sont 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() est refusé avec un OverconstrainedError, et vous êtes invité à autoriser l'accès à votre caméra.

Si l'élément getUserMedia() a réussi, le flux vidéo de la webcam est défini en tant que source de l'élément vidéo:

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

Obtenez des points bonus

  • L'objet localStream transmis à getUserMedia() est dans le champ d'application global. Vous pouvez donc l'inspecter à partir de la console du navigateur. Ouvrez la console, saisissez stream, et appuyez sur Enter (Return sur Mac). Pour afficher la console dans Chrome, appuyez sur Control+Shift+J (ou Command+Option+J sur Mac).
  • Que renvoie localStream.getVideoTracks() ?
  • Appeler localStream.getVideoTracks()[0].stop()
  • Examinez l'objet "Contraintes". 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 calculer la taille naturelle de la vidéo à partir de JavaScript au lieu de la taille d'affichage ? Pour le vérifier, utilisez les outils pour les développeurs Google Chrome.
  • Ajoutez des filtres CSS à l'élément vidéo, comme suit:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Ajoutez des filtres SVG, comme suit:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Conseils

Bonne pratique

Assurez-vous que votre élément vidéo ne dépasse pas son conteneur. Cet atelier de programmation a ajouté width et max-width pour définir une taille maximale et une taille maximale pour la vidéo. Votre navigateur calcule automatiquement la hauteur.

video {
  max-width: 100%;
  width: 320px;
}

4. Regarder des vidéos en streaming avec l'API RTCPeerConnection

Une version complète de cette étape se trouve dans le dossier step-2.

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

Dans le fichier index.html, remplacez l'élément video unique par deux éléments video et trois éléments button:

<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 affiche le flux de getUserMedia() et l'autre présente la même vidéo diffusée en streaming via RTCPeerconnection. Dans une application réelle, un élément video affichera le flux local et l'autre le flux distant.

Ajouter le shim adaptateur.js

Copiez cet élément de script et collez-le au-dessus de l'élément de script pour main.js :

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

Votre fichier index.html devrait se présenter comme suit:

<!DOCTYPE html>
<html>

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

<body>
  <h1>Real-time 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 du dossier step-02.

Passer l'appel

  1. Ouvrez le fichier index.html.
  2. Cliquez sur Démarrer pour voir la vidéo de votre webcam.
  3. Cliquez sur Appeler pour établir la connexion au pair.

Vous devriez voir la même vidéo dans les deux éléments video de votre webcam.

  1. Accédez à la console du navigateur pour consulter la journalisation WebRTC.

Fonctionnement

Cette étape est très complexe.

WebRTC utilise l'API RTCPeerConnection pour configurer une connexion pour diffuser des vidéos entre les clients WebRTC, appelés "peers".

Dans cet exemple, les deux objets RTCPeerConnection se trouvent sur la même page: pc1 et pc2.

La configuration des appels entre les pairs WebRTC implique trois tâches:

  1. Créez un RTCPeerConnection pour chaque extrémité de l'appel et ajoutez à chaque extrémité le flux local à partir de getUserMedia().
  2. Récupère et partage les informations concernant le réseau.

Les points de terminaison de connexion potentiels sont appelés des candidats ICE.

  1. Obtenez et partagez des descriptions locales et à distance.

Les métadonnées relatives aux médias locaux sont au format SDP (Session Description Protocol).

Imaginons qu'Alice et Bob souhaitent utiliser RTCPeerConnection pour configurer un chat vidéo.

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

  1. Alice crée un objet RTCPeerConnection avec un gestionnaire onicecandidate (addEventListener('icecandidate')).

Cela correspond au code main.js suivant:

let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice appelle getUserMedia() et ajoute le flux transmis à:
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 la première étape est appelé lorsque les candidats réseau seront disponibles.
  2. Alice envoie les données sérialisées des candidats à Bob.

Dans une application réelle, ce processus, appelé signalisation, s'effectue via un service de messagerie. Vous apprendrez à le faire plus tard. Bien sûr, à cette étape, les deux objets RTCPeerConnection se trouvent sur la même page et peuvent communiquer directement sans avoir à envoyer de messages externes.

  1. Lorsque Bob reçoit un message d'Alice d'Alice, il appelle addIceCandidate() pour ajouter le candidat à la description distante du pair:
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 explorer et échanger des informations sur les médias audio et vidéo locaux et à distance, telles que les capacités de résolution et de codec. La signature des échanges de données multimédias commence avec l'échange de blobs de métadonnées, connus sous le nom d'offre et de réponse, au format SDP.

  1. Alice exécute la méthode RTCPeerConnection createOffer().

La promesse renvoyée fournit une description de la session locale RTCSessionDescription—Alice's:

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. En cas de réussite, Alice définit la description locale à l'aide de setLocalDescription(), puis envoie cette description de session à Bob par le biais de son canal de signalisation.
  2. Bob définit la description qu'Alice lui a envoyée en tant que description à distance avec setRemoteDescription().
  3. Bob exécute la méthode RTCPeerConnection createAnswer() et lui transmet la description à distance qu'elle a obtenue d'Alice afin qu'une session locale soit générée et compatible avec la sienne.
  4. La promesse createAnswer() transmet un RTCSessionDescription, que Bob définit en tant que description locale et l'envoie à Alice.
  5. Lorsqu'Alice obtient la description de session de Bastien, elle la définit comme description à distance 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);
}

Obtenez des points bonus

  1. Accédez à chrome://webrtc-internals.

Cette page fournit des statistiques WebRTC et des données de débogage. (Pour obtenir la liste complète des URL Chrome, consultez la page chrome://about.)

  1. Définissez le style de la page avec CSS:
  2. Placez les vidéos côte à côte.
  3. Définissez la même largeur des boutons avec un texte plus grand.
  4. Assurez-vous que la mise en page fonctionne sur les appareils mobiles.
  5. Dans la console des outils pour les développeurs Chrome, accédez à localStream, localPeerConnection et remotePeerConnection.
  6. Dans la console, accédez à localPeerConnectionpc1.localDescription.

À quoi ressemble le format SDP ?

Conseils

  • Pour en savoir plus sur le shim adaptateur.js, consultez le dépôt GitHub adapter.js.
  • Examinez le AppRTCet le codede son projet, l'application canonique pour les appels WebRTC. La durée de la configuration de l'appel est inférieure à 500 ms.

Bonne pratique

Pour assurer la pérennité de votre code, utilisez les nouvelles API basées sur Promise et activez la compatibilité avec les navigateurs qui ne les acceptent pas avec adapter.js.

5. Utiliser un canal de données pour échanger des données

Une version complète de cette étape se trouve dans le dossier step-03.

Mettre à jour votre code HTML

Pour cette étape, vous allez utiliser des canaux de données WebRTC pour envoyer du texte entre deux éléments textarea sur la même page. Cette fonctionnalité n'est pas très utile, mais elle montre comment WebRTC peut être utilisé pour partager des données et diffuser des vidéos.

Supprimez les éléments video et button de 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>

Un premier champ textarea permet de saisir du texte, tandis que l'autre permet de l'afficher en streaming entre des pairs.

Votre fichier index.html devrait se présenter comme suit:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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

  1. Remplacez main.js par le contenu de step-03/js/main.js.
  1. Essayez la diffusion en flux continu entre des pairs:
  2. Ouvrez index.html.
  3. Cliquez sur Démarrer pour configurer la connexion au pair.
  4. Saisissez du texte dans le textarea à gauche.
  5. Cliquez sur Send (Envoyer) pour transférer le texte à l'aide d'un canal de données WebRTC.

Fonctionnement

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

Une grande partie du code de cette étape est identique à celle de l'exemple RTCPeerConnection. Les fonctions sendData() et createConnection() disposent de la plupart 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 délibérément semblable à 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 de manière à activer différents types de partage de données, par exemple pour privilégier la diffusion fiable par rapport aux performances.

Obtenez des points bonus

  1. Avec le protocole SCTP, le protocole utilisé par les canaux de données WebRTC est activé par défaut pour la diffusion de données fiable et triée. Quand RTCDataChannel devrait-il fournir des données de manière fiable et quand les performances sont-elles plus importantes, même si cela implique de perdre des données ?
  2. Utilisez le code CSS pour améliorer la mise en page et ajouter un attribut d'espace réservé à la dataChannelReceive textarea.
  3. tester la page sur un appareil mobile ;

En savoir plus

6. Configurer un service de signalisation pour échanger des messages

Vous avez appris à échanger des données entre pairs sur la même page, mais comment procéder entre différentes machines ? Vous devez d'abord configurer un canal de signalisation afin d'échanger des messages de métadonnées.

Une version complète de cette étape se trouve dans le dossier step-04.

À propos de l'application

WebRTC utilise une API JavaScript côté client, mais pour une utilisation réelle, il nécessite également un serveur de signalisation, 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 signalement Node.js simple à l'aide du module Node.js Socket.IO et de la bibliothèque JavaScript pour la messagerie.

Dans cet exemple, le serveur (l'application Node.js) est implémenté dans index.js, et le client qui l'exécute (l'application Web) est implémenté dans index.html.

L'application Node.js comporte deux tâches.

Tout d'abord, il sert de relais de message:

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

Ensuite, l'application 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);
}

Votre application WebRTC simple permet à un maximum de deux pairs de partager un salon.

HTML et JavaScript

  1. Mettez à jour index.html de sorte qu'il ressemble à ceci:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

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

</html>

Aucun élément ne s'affiche sur la page. Tous les enregistrements sont effectués dans la console du navigateur. Pour afficher la console dans Chrome, appuyez sur Control+Shift+J (ou Command+Option+J sur Mac).

  1. 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 un fichier Socket.IO à exécuter 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>
  1. 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) quel projet

dépendances à installer.

  1. Pour installer des dépendances telles que /socket.io/socket.io.js, exécutez la commande suivante depuis le terminal de ligne de commande de votre répertoire work:
npm install

Vous devriez voir un journal d'installation se terminant par ceci:

3a06b7bcc7663b9.png

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

  1. Créez un fichier index.js au niveau supérieur de votre répertoire work (et non dans le répertoire js), puis 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);
        }
      });
    }
  });

});
  1. À partir du terminal de ligne de commande, exécutez la commande suivante dans le répertoire work:
node index.js
  1. Dans un navigateur, accédez à http://localhost:8080.

Chaque fois que vous accédez à cette URL, vous êtes invité à saisir un nom de salon.

Pour rejoindre le même salon, saisissez le même nom de salon à chaque fois, par exemple foo.

  1. Ouvrez un nouvel onglet, accédez à http://localhost:8080, puis saisissez à nouveau le même nom de pièce.
  2. Ouvrez un nouvel onglet, accédez à nouveau à http://localhost:8080, puis saisissez à nouveau le même nom de pièce.
  3. Vérifiez la console dans chacun des onglets.

Vous devriez voir la journalisation à partir du code JavaScript.

Obtenez des points bonus

  • Quels autres messages sont possibles ? Quels problèmes pouvez-vous rencontrer lors de l'utilisation de WebSocket pur ?
  • Quels problèmes peuvent être liés au scaling de cette application ? Serez-vous capable de tester des milliers ou des millions de demandes de chambres simultanées ?
  • Cette application utilise une invite JavaScript pour obtenir le nom d'une salle. Déterminez comment obtenir le nom de la chambre à partir de l'URL. Par exemple, http://localhost:8080/foo donne le nom de salle foo.

En savoir plus

7. Associer les connexions et les signaux des pairs

Une version complète de cette étape se trouve dans le dossier step-05.

Remplacer le code HTML et JavaScript

  1. Remplacez le contenu de index.html par le code suivant :
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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>
  1. 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 à partir de votre répertoire work, vous devrez peut-être installer les dépendances pour le dossier step-05 ou votre dossier de travail actuel.

  1. Exécutez la commande suivante à partir de votre répertoire de travail:
npm install
  1. Une fois installé, si votre serveur Node.js ne s'exécute pas, démarrez-le en exécutant la commande suivante dans le répertoire work:
node index.js

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

  1. Depuis un navigateur, accédez à http://localhost:8080.
  2. Ouvrez un nouvel onglet et accédez à http://localhost:8080.

Un élément video affiche le flux local de getUserMedia() et l'autre la vidéo télécommande diffusée en streaming via RTCPeerconnection.

  1. Consultez la journalisation dans la console du navigateur.

Gagnez des points gagnés

  • Cette application accepte uniquement les chats vidéo en tête-à-tête. Comment changer la conception pour permettre à plusieurs personnes de partager le même salon de discussion vidéo ?
  • Dans cet exemple, le nom de la pièce est foo codé en dur. Quel serait le meilleur moyen d'activer les autres noms de pièce ?
  • Comment les utilisateurs partageraient-ils le nom du salon ? Essayez de créer une autre option que de partager des noms de salles.
  • Comment changer l'appli ?

Conseils

  • Recherchez des statistiques WebRTC et déboguer des données sur la page chrome://webrtc-internals.
  • Utilisez l'outil de dépannage WebRTC pour vérifier l'environnement local, et tester votre caméra et votre micro.
  • Si vous rencontrez des problèmes de mise en cache, essayez les solutions suivantes:
  1. Appuyez sur Control, puis sur Actualiser cette page.
  2. Redémarrez le navigateur.
  3. Exécutez npm cache clean à partir de la ligne de commande.

8. Prenez une photo et partagez-la via un canal de données

Une version complète de cette étape se trouve dans le dossier step-06.

Fonctionnement

Précédemment, vous avez appris à échanger des SMS avec RTCDataChannel. Cette étape permet de partager des fichiers entiers. Dans cet exemple, les photos sont prises avec getUserMedia().

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

  1. Établissez un canal de données.

À cette étape, vous n'ajoutez aucun flux multimédia à la connexion au pair.

  1. Capturez le flux vidéo de votre webcam 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. Cliquez sur Snap pour prendre un instantané (une vidéo) du flux vidéo et l'afficher 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. Cliquez sur Send (Envoyer) pour convertir l'image en octets et l'envoyer via un canal de données:
function sendPhoto() {
  // Split the 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 approximately 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 applicable.
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}

Le côté réception convertit les octets de messages de canal de données en images et les 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');
  // The trail is the element that holds 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

  1. Remplacez le contenu du dossier work par le contenu de step-06.

Votre fichier index.html dans work devrait se présenter comme suit**:**

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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>
  1. Si vous ne suivez pas cet atelier de programmation à partir de votre répertoire work, vous devrez peut-être installer les dépendances pour le dossier step-06 ou votre dossier de travail actuel. Exécutez simplement la commande suivante depuis votre répertoire de travail:
npm install
  1. Une fois installé, si votre serveur Node.js ne s'exécute pas, démarrez-le en exécutant la commande suivante à partir de votre répertoire work:
node index.js
    Make sure that you're using the version of `index.js` that implements Socket.IO and 

n'oubliez pas de redémarrer votre serveur Node.js si vous apportez des modifications.

Pour en savoir plus sur Node et Socket.IO, consultez la section "Configurer un signal".

pour échanger des messages.

  1. Si nécessaire, cliquez sur Autoriser pour autoriser l'application à utiliser votre webcam.

L'application crée un ID de pièce aléatoire et l'ajoute à l'URL.

  1. Ouvrez l'URL à partir de la barre d'adresse dans un nouvel onglet ou une nouvelle fenêtre de votre navigateur.
  2. Cliquez sur Snap & Send (Envoyer et prendre des photos). Ensuite, regardez les photos Inbound photos (Photos entrantes) en bas de la page.

L'application transfère les photos entre les onglets.

Ce type de message devrait s'afficher :

911b40f36ba6ba2.png

Obtenez des points bonus

Comment puis-je modifier le code pour partager tous les types de fichiers ?

En savoir plus

9. Félicitations

Vous avez créé une application pour diffuser des vidéos et échanger des données en temps réel.

Learn more