,

Portal : du jeu à la réalité, comment créer votre portail grâce à des technos web ? - partie 1

Que feriez-vous d’un Portal Gun* si vous en aviez un ? Homer Simpson, lui, a parfaitement compris son utilité et peut -grâce à son portail personnel- attraper l’une des bières de son frigo sans bouger de son sofa !

Le rêve de toute une vie devient (presque) réalité ! A défaut de pouvoir se servir directement dans votre frigo depuis votre canapé, vous pourrez le surveiller de près.

Pour « toucher avec les yeux » les bières qui sont au frais, nous allons réaliser ensemble des portails similaires à ceux des célèbres jeux « Portal » tout en se basant uniquement sur des technologies du Web ! Un bout de votre rêve se concrétise…

 

simpson

Le projet Portal WebRTC

Voici le rendu de ce que nous allons réaliser : le portail bleu voit ce qui se passe dans le portail orange et inversement :

portal bleu

portal rouge

Avant d’attaquer le code, regardons un peu de quoi nous avons besoin :

  1. Nous devons être capables de voir ce qu’il se passe de l’autre côté du portail
  2. Un “mur de flammes” entoure notre image
  3. Nous voulons nous télé-porter de l’autre côté du portail

 

Voyons maintenant comment nous pouvons répondre à ces différents  besoins à travers des technologies du Web :

  1. WebRTC : il s’agit d’une technologie de visio (mais pas que) et donc c’est idéal pour voir ce qu’il se passe à l’autre côté du portail
  2. Les canvas nous permettront de jouer efficacement pour simuler de façon performante notre “mur de flammes”.
  3. La teleportation API…. euh non rien ne permet de répondre à ce besoin…

/!\ Ce projet ne marchera que sous Chrome ou Firefox

Architecture du projet

architecture portal

  • Chaque ordinateur se trouve sur le réseau et se connecte à un serveur Web uniquement pour afficher le contenu de la page. Le serveur est un serveur NodeJS.
  • Ce Serveur expose aussi une WebSocket dont le rôle sera expliqué plus tard dans l’article
  • L’échange des données vidéos se fait en “direct” entre les 2 ordinateurs via la technologie WebRTC

WebRTC What ?

webRTC

WebRTC pour Real Time Communication est une des technologies les plus importantes du projet. Grâce à cette API, on peut  faire plusieurs choses :

  • Obtenir l’audio et la vidéo
  • Etablir une connexion entre 2 hôtes
  • Communiquer de la vidéo et de l’audio
  • Communiquer d’autres types de données

Une des forces du webRTC est que les données s’échangent directement entre les 2 ordinateurs et que ces dernières ne passent pas par un serveur ! Pour réussir cet exploit, la technologie WebRTC repose sur 3 APIS web :

  1. getUserMedia : cette API permet de récupérer les flux vidéos et audios d’un ordinateur
  2. RTCPeerConnection : cette API permet de faire communiquer des données entre 2 hôtes en tenant compte de tout un ensemble de contraintes telles que l’adresse IP d’une machine, ses codecs, sa connectivité, …
  3. RTCDataChannel : cette API permet de faire transiter sur une RTCPeerConnection des données textuelles ou binaires.

Pour notre projet, nous n’allons utiliser que les API getUserMedia et RTCPeerConnection.

GetUserMedia

Il s’agit d’une API qui permet de récupérer un ensemble de stream de médias synchronisés. Chaque stream peut être vidéo / audio.

var constraints = {video: true};

function successCallback(stream) {
var video = document.querySelector(« video »);
video.src = window.URL.createObjectURL(stream);
}
function errorCallback(error) {
console.log(« navigator.getUserMedia error: « ,
error);
}
navigator.getUserMedia(constraints,
successCallback,
errorCallback);

Dans l’exemple ci-dessus, nous ne récupérons que la vidéo et nous injectons le résultat de l’appel de getUserMedia dans une balise vidéo.

RTCPeerConnection

La RTCPeerConnection permet de gérer le transport des données. Pour initialiser une RTCPeerConnection, on répond au principe de l’offre et de la demande. Il y a d’une part, une notion d’offre et de demande pour communiquer mais aussi une notion de chemin à emprunter ! Ces 2 notions s’appellent le “Signaling”. Le Signaling a pour objectif de répondre à ces questions :

  • Quel type de média et format je supporte ?
  • Que puis-je envoyer ?
  • Quel est mon type d’infrastructure réseau ?

Pour faire cette étape, il suffit juste de trouver un moyen de passer ces informations à l’hôte distant. Une des technologies préconisées pour faire le signaling est “les WebSockets”. C’est donc ici qu’interviendra notre serveur de websockets

Voici comment se déroule le signaling :

Gestion de l’offre

  1. Alice appelle la méthode createOffer()
  2. Dans le callback, Alice appelle setLocalDesctiption()
  3. Alice sérialise l’offre et l’envoie à Eve
  4. Eve appelle la méthode setRemoteDescription() avec l’offre
  5. Eve appelle la méthode createAnswer()
  6. Eve appelle la méthode setLocalDescription() avec la réponse envoyée à Alice
  7. Alice reçoit la réponse et appelle setRemoteDescription()

Gestion du chemin Ice Candidate (ICE pour Interactive Connectivity Establishement)

  1. Alice & Eve ont leur RTCPeerConnection
  2. En cas de succès de chaque côté les IceCanditates sont envoyées
  3. Alice sérialise ses IceCandidates et les envoie à Eve
  4. Eve reçoit les IceCandidates d’Alice et appelle addIceCandidate()
  5. Eve sérialise ses IceCandidates et les envoie à Alice
  6. Alice reçoit les IceCandidates d’Eve et appelle addIceCandidate()
  7. Les 2 savent comment communiquer.

Plus d’infos

Si vous souhaitez plus d’informations sur le WebRTC :

Retour au Projet Portal

Après cette rapide introduction sur la technologie WebRTC. Nous allons maintenant nous intéresser à notre projet et nous allons voir comment réaliser notre “Portal”.

Comme tout bon projet, je me suis inspiré de ce que je trouvais sur le net afin de gagner du temps. Ainsi, plutôt que de vous noyer sous des montagnes de codes compréhensibles et/ou incompréhensibles, je vous donnerai les deltas que j’ai effectués et pourquoi je les ai faits.

Etape 1 : cloner les projets références

La base du projet WebRTC repose sur le codelab initialisé par Sam Dutton : ingénieur chez Google et travaillant sur l’implémentation de WebRTC dans Chrome : https://bitbucket.org/webrtc/codelab. De façon plus précise notre point de départ sera le step7 de ce codelab.

La base graphique des flammes repose sur le projet de Chris Longo : https://github.com/chrislongo/html5-canvas-demo

Nous allons donc commencer par cloner les 2 projets afin de récupérer une base de code propre et fonctionnelle que nous allons nettoyer petit à petit pour coller avec notre besoin.

Etape 2 : création du squelette de l’application

L’application est structurée comme suit :

  • assets/ : fichiers externes
    • fonts/ : les fonts spéciales utilisées pour le projet
    • images/ : les ressources graphiques utilisées pour le projet
  • css/ : le style de notre page
  • js/ : les fichiers javascripts utilisés par le projet
  • json : fichier des dépendances node utilisées pour le serveur node
  • js : le serveur nodeJS
  • html : notre application

Téléchargement des ressources annexes

Etape 3 : Ecriture du Serveur

Comme expliqué précédemment, nous allons baser notre travail sur le step7 du codelab.

./package.json

{

« author »: « jefBinomed »,

« name »: « protal-devfest-2013 »,

« dependencies »: {

« socket.io »: « ~0.9.14 »,

« node-static » : « ~0.6.9 »

}

}

./server.js

Nous reprenons le serveur tel qu’il est dans le codelab

Ce serveur fait donc 2 choses :

  1. Dans un premier temps, on va définir un serveur http pour servir notre contenu html
  2. On crée un serveur de webSockets afin d’assurer la partie “Signaling”. Un message transféré au serveur sera automatiquement partagé à l’autre client.

Il ne nous reste plus à qu’à récupérer les modules node avec l’instruction :

npm install

De cette manière, les dépendances nodes seront téléchargées dans notre projet

Etape 4 : Ecriture du projet WebRTC

Nous allons poser le style graphique de notre application :

./assets/fonts/stylesheet.css

@font-face {

font-family: ‘portalportal’;

src: url(‘portal.ttf’) format(‘truetype’);

font-weight: normal;

font-style: normal;

}

./css/main.css

body{

background-color:black;

font-family: « portalportal »;

text-align: center;

color: #636468;

margin: 0;

overflow: hidden;

}

#videos{

margin: auto;

}

header{

position: absolute;

left: -342px;

top: -100px;

font-size: 40px;

background-image: url(../assets/images/navigation_bg.png);

background-repeat: no-repeat;

background-size:750px;

height: 147px;

width: 750px;

}

.landscape header{

left:50%;

margin-left:-325px;

top:inherit;

}

header .textHeader{

position: absolute;

width: calc(100% * 2/3);

height: 100%;

top:40px;

right: calc(100% / 3);

text-align: right;

}

header .firstLine{

text-transform: uppercase;

}

header .secondLine{

font-size: 20px;

}

header .portalImg{

position: absolute;

right: calc(100% / 3 – 60px);

top: 40px;

}

#container{

position: absolute;

left: 137px;

height: 99%;

width: calc(100% – 60px – 137px);

}

footer{

background-image: url(../assets/images/showcase_bg.png);

background-repeat: no-repeat;

background-size: 750px;

position: absolute;

bottom: -100px;

right: -345px;

width: 750px;

font-size: 14px;

line-height: 60px;

height: 60px;

}

.landscape footer{

right: inherit;

left: 50%;

margin-left: -325px;

bottom: 0;

}

canvas{

position: absolute;

}

index.html

<!doctype html>

<html lang= »en »>

<head>

<meta charset= »utf-8″>

<title>Server presentation</title>

<!– Main Css–>

<link rel= »stylesheet » href= »assets/fonts/stylesheet.css »>

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

</head>

<body class= »landscape »>

<header>

<div class= »textHeader »>

<span class= »firstLine »>Projet PORTAL 2O15</span><br>

<span class= »secondLine »>une expérience interactive</span>

</div>

<img  src= »assets/images/bugdroidportal_2.png » height= »100px » class= »portalImg »></img>

</header>

 

<div id=’container’>

<video id=’remoteVideo’ autoplay muted style= »display:none; »></video>

<canvas id= »canvasFireLocalVideo »></canvas>

<canvas id= »canvasRemoteVideo »></canvas>

</div>

 

<footer>

Une création utilisant la technologie <a href= »www.webrtc.org »>WEBRTC&nbsp;<img width= »32px » src= »assets/images/webrtc.png »></a>. Credits to @Binomed

</footer>

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

<script src=’js/lib/adapter.js’></script>

<script src=’js/canvasFire.js’></script>

<script src=’js/app.js’></script>

 

</body>

</html>

./js/lib/adapter.js

Ce fichier doit être copié tel quel depuis le codelab car il s’agit de la classe Polyfill qui permet d’uniformiser l’API WebRTC entre Chrome & Firefox

./js/canvasFire.js

Initialisez ce fichier à vide afin d’avoir l’import depuis le fichier html qui fonctionne

./js/app.js

Nous allons partir du fichier issu du step7 : ./js/main.js.

Copiez l’intégralité du fichier et nous allons retirer ce qui ne nous intéresse pas.

LocalVideo

Dans notre projet, nous ne sommes pas intéressés pour afficher le retour de notre webCam à l’écran, nous allons donc supprimer toutes les références à cet élément.

var localVideo = document.querySelector(‘#localVideo’);

et

attachMediaStream(localVideo, stream);

dans la fonction handleUserMedia(stream)

DataChannel

De la même façon tout ce qui concerne le DataChannel ne nous sert pas

var sendChannel;
var sendButton = document.getElementById(« sendButton »);
var sendTextarea = document.getElementById(« dataChannelSend »);
var receiveTextarea = document.getElementById(« dataChannelReceive »);

sendButton.onclick = sendData;

Au début du projet. Ensuite

var pc_constraints = {
‘optional’: [
{‘DtlsSrtpKeyAgreement’: true},
{‘RtpDataChannels’: true}
]};
est à remplacer par :

var pc_constraints = {
‘optional’: [
{‘DtlsSrtpKeyAgreement’: true}
]};

Il faut supprimer également tout ce qui suit et qui se situe au niveau de la fonction createPeerConnection

if (isInitiator) {
try {
// Reliable Data Channels not yet supported in Chrome
sendChannel = pc.createDataChannel(« sendDataChannel »,
{reliable: false});
sendChannel.onmessage = handleMessage;
trace(‘Created send data channel’);
} catch (e) {
alert(‘Failed to create data channel. ‘ +
‘You need Chrome M25 or later with RtpDataChannel enabled’);
trace(‘createDataChannel() failed with exception: ‘ + e.message);
}
sendChannel.onopen = handleSendChannelStateChange;
sendChannel.onclose = handleSendChannelStateChange;
} else {
pc.ondatachannel = gotReceiveChannel;
}
}

function sendData() {
var data = sendTextarea.value;
sendChannel.send(data);
trace(‘Sent data: ‘ + data);
}

function gotReceiveChannel(event) {
trace(‘Receive Channel Callback’);
sendChannel = event.channel;
sendChannel.onmessage = handleMessage;
sendChannel.onopen = handleReceiveChannelStateChange;
sendChannel.onclose = handleReceiveChannelStateChange;
}

function handleMessage(event) {
trace(‘Received message: ‘ + event.data);
receiveTextarea.value = event.data;
}

function handleSendChannelStateChange() {
var readyState = sendChannel.readyState;
trace(‘Send channel state is: ‘ + readyState);
enableMessageInterface(readyState == « open »);
}

function handleReceiveChannelStateChange() {
var readyState = sendChannel.readyState;
trace(‘Receive channel state is: ‘ + readyState);
enableMessageInterface(readyState == « open »);
}

function enableMessageInterface(shouldEnable) {
if (shouldEnable) {
dataChannelSend.disabled = false;
dataChannelSend.focus();
dataChannelSend.placeholder = «  »;
sendButton.disabled = false;
} else {
dataChannelSend.disabled = true;
sendButton.disabled = true;
}
}
Et enfin pour finir :

var constraints = {‘optional’: [], ‘mandatory’: {‘MozDontOfferDataChannel’: true}};

 

de la fonction doCall() est à remplacer par :

 

var constraints = {‘optional’: [], ‘mandatory’: {}};

Tester

Nous pouvons à présent tester notre application pour vérifier que la vidéo passe bien à travers l’API WebRTC. Pour ce faire, il suffit simplement de lancer notre serveur à l’aide de la commande :

node server.js

Notre serveur tourne sur le port 2013. Il faut donc entrer dans notre navigateur l’url : http://localhost:2013. Il est très important d’accepter le partage de vidéo sinon cela ne pourra pas fonctionner.

A ce moment-là, vous devriez avoir un écran noir… à la place de vos potentielles bières ! En effet, comme nous ne faisons pas de retour visuel de notre propre caméra, nous devons ouvrir un deuxième onglet sur la même url pour vérifier le bon fonctionnement. Cependant, il y a une deuxième raison pour laquelle nous ne voyons rien, la balise video remoteVideo a un style « display:none’». Il faudra supprimer ce « display:none » le temps du test.

Si tout se passe bien, vous devriez avoir une vidéo sur les 2 onglets correspondants au rendu de votre webcam. Pour chaque futur test, je vous conseille de fermer les 2 onglets à chaque fois car le serveur Node stocke le nombre de clients connectés… et la limite a été fixée à 2 clients maximum !

 

La suite dans un 2e article….

Jean François Garreau

Consultant Expert Innovation SQLI Nantes

0 commentaires

votre commentaire

Se joindre à la discussion ?
Vous êtes libre de contribuer !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Inscription newsletter

Ne manquez plus nos derniers articles !