TP5 HTML5 M2 NTDP 2012-2013

De $1

Introduction

  Dans un premier temps vous prendrez contact avec les APIs d'orientation et de géolocalisation de HTML5. Dans un second vous découvrirez les WebSocket et la manière dont on peut réaliser des applications multi participants avec HTML5. Enfin, vous mixerez le tout avec une application qui peut tourner sur tablette / smartphone multi participante, qui utilise l'orientation et la localisation.

Pour ce TP vous aurez besoin d'un micro serveur qui gère les web sockets. On va utiliser NodeJS un serveur web très populaire en ce moment pour les applications multi-participantes HTML5. Il est écrit en Python mais il embarque l'interpréteur JavaScript V8 de Chrome. Le code applicatif côté serveur n'est ni du PHP, ni du Java, mais du ... JavaScript !

 

L'API d'orientation de HTML5

Travail à fairei:

  1. Découvrez en lisant cet article la manière dont fonctionnent les APIs d'orientation et de gestion des accéléromètres. Vous pourrez aussi lire cet article en français.
  2. Muni d'un ipad, d'un mac portable récent ou d'un device android récent, vous  pourrez tester les démonstrations proposées dansles articles cités mais également celle-ci : http://jsbin.com/uceciv/2/edit
    Nous vous conseillons de regarder ce dernier exemple en tenant le smartphone ou la tablette en position verticale, tenez le à bout de bras et tournez le autours d'un axe partant de votre poitrine, devant vous. Penchez le en avant et en arrière. Maintenant, imaginez que vous jouez à Mario Kart sur une Wii... vous voyez le topo ?

Nous laisserons de côté pour le moment la gestion de l'accéléromètre. Dans le TP nous nous contenterons de gérer l'orientation.

L'API de géolocalisation de HTML5

 L'API de géolocalisation de HTML5 utilise un des moyens disponibles (gps, wifi, 3G, IP), peut forcer l'utilisation du GPS, peut renvoyer à intervalle réguliers la position courante (utile pour faire du tracking), etc.

Vous commencerez par lire cet article en français puis vous executerez sur votre PC et / ou sur devices mobiles les exemples de ce tutorial HTML5. Regardez notamment le dernier exemple avec une carte interactive, il possède une fonction drawPosition(position) que vous réutiliserez par la suite.

Les curieux peuvent aller voir ce site qui est une référence sur l'utilisation de l'API de Google Maps : http://econym.org.uk/gmap/

 

WebSockets: etude d'un programme de chat

On va utiliser un micro serveur NodeJS

NodeJS est un serveur très très minimal par défaut mais on peut l'étendre en lui ajoutant des "modules". 

Exemple de modules que nous allons utiliser:

  • express donne la possibilité de servir des pages HTML avec CSS, JavaScript etc. Par défaut NodeJS ne sait pas faire cela.
  • socket.io : librairie pour les WebSockets. Donne aussi des solutions de fallbacks si le navigateur utilisé ne supporte pas les WebSockets (comet, ajax, flash sockets)
  • nowJS : permet de faire des appels de méthodes distantes en JavaScript, via des WebSockets.

Installation de NodeJS

Sous windows c'est assez compliqué à cause de certains modules qui nécessitent une recompilation à la volée. Pour ce TP au lieu d'installer NodeJS depuis le site officiel, puis utiliser la commande "npm" (Node Package Manager), qui télécharge les modules un par un, vous allez directement installer les logiciels suivants (aussi dispo sur une clé USB, demander à votre enseignant)

Une fois tout ceci installé, ouvrez une fenêtre DOS et tapez "node --version", cela devrait afficher  "node version 0.6.12"

Test d'une première application NodeJS : 

  • Créez qulelque part un fichier test.js file avec ce contenu :
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8124, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8124/');

Sauvez le, ouvrez une fenetre de commande, allez dans le répertoire où vous avez créé le fichier et exécutez la commande suivante :

node test.js


Vous venez juste de lancer le serveur NodeJS qui répond à cet URL : http://localhost:8124

Snap5.jpg

Ouvrez cet URL dans votre navigateur !

Vous devriez avoir :

Snap6.jpg

Vous venez d'exécuter votre première application NodeJS ;-)

Test d'une application de chat

Allez dans le répertoire où vous avez dézippé ChatNowJs.zip.

Exécutez la commande : "node simpleChat_server.js", et ouvrez http://localhost:8080 dans votre navigateurr. Ouvrez deux onglets sur cet URL, vous devez avoir un champ de saisie dans chaque page. Tapez quelque chose, cela devrait apparaitre dans l'autre fenêtre (les deux sont en réalité des clients connectés au même serveur de WebSockets ).

Etude de l'application de chat

Deux fichiers tout petits à étudier !

  1. simpleChat_server.js
  2. simpleChat.html

Ouvrez ces fichiers avec un éditeur : ouch que c'est... petit ! Oui, on utilise ici la notion d'objets distribués, le code client qui tourne dans le navigateur appelle du code qui tourne dans le serveur et inversement. Pas de réseau au milieu ! En fait si... mais on fait comme si il n'y en avait pas ! Le module NodeJS qui fait ceci est magique, il s'appelle NowJS !

NowJS définit une "poche magique" appelée "now" : en fait une variable JavaScript que l'on manipule depuis le code qui tourne sur le client, dans le navigateur, et qui contient les variables et méthodes et objets partagés entre le client et le serveur distant.

Le serveur peut utiliser une variable "everyone.now" pour définir et utiliser les variables et méthodes partagées.

Par exemple, côté serveur on définit la fonctionn distributeMessage dans la variable everyone.now Ceci va rendre visible cette fonction dans tous les codes clients tournant dans les navigateurs, à travers la variable now. "everyone.now" signifie : pour toutes les variables now de tous les clients, je rajoute la fonction distributeMessage dedans.

Serveur :

everyone.now.distributeMessage = function(message){
  everyone.now.receiveMessage(this.now.name, message);
};

Appel par un des clients (voir simpleChat.html) :

$("#send-button").click(function(){
    now.distributeMessage($("#text-input").val());
    $("#text-input").val("");
});

Ainsi un clic sur le bouton "send" dans la page exécute now.distributeMessage() qui s'exécute sur le serveur, avec pour paramètre d'apple le contenu du champs de saisie de la page, c'est le texte qu'on veut envoyer par le chat. Le serveur récupère ce paramètre dans la fonction everyone.now.distributeMessage, qui appelle à son tour everyone.now.receiveMessage, une fonction définie.... dans le code JavaScript de la page web des clients ! La partie"everyone" signifie qu'on appelle la méthode chez tous les clients connectés.

Client :

now.receiveMessage = function(name, message){
    $("#messages").append("<br>" + name + ": " + message);
}

Et cette fonction, localisée côté client, va juste afficher dans la page le texte du message reçu !

De la même manière, une variable déclarée côté client comme:

now.name = prompt("What's your name?", "");

Peut être récupérée directement par le serveur à l'aide de this.now :

Serveur :

everyone.now.distributeMessage = function(message){
  everyone.now.receiveMessage(this.now.name, message);
};

A l'aide de ce paradigme, NowJS permet de développer très rapidement des applications collaboratives avec des WebSockets. Remarquez que les objets sont envoyés tels quels, pas en XML ou en JSON ! (Ils sont en réalité transformés en JSON en coulisse).

Etudier la partie multi-participants du Paint du TP précédent (steps 7 et 8) 

NOTE: ceux qui ont fait le paint l'an dernier, cette partie est facultative, ou bien, faites là rapidement...

Dans cette partie on ne vous demande pas d'écrire du code, juste d'étudier comment on a intégré le chat dans le paint, et comment on a "copié" la manière dont le chat envoie et redistribue les messages, pour envoyer et redistribuer les ordres de dessin.

On ajoute la partie "chat" au programme de paint

Allez dans le répertoire "step 7". Arrêtez NodeJS si il tourne encore.

Regardez le fichier server.js :

// Use the express module for nodeJS
// Express is a framework for implementing a more
// complete web server with nodeJS
var express = require('express');

var app = express.createServer();

// Indicate where static files are located
app.configure(function () {
    app.use(express.static(__dirname + '/client/'));
});
app.get('/', function (req, res) {
    //res.render('client/paint.html');
    res.redirect("/paint.html");
});
app.listen(8080);

// Creation of a web socket server using nowJS
var nowjs = require("now");
var everyone = nowjs.initialize(app);


nowjs.on("connect", function () {
    console.log("Joined: " + this.user.clientId);
});

nowjs.on("disconnect", function () {
    console.log("Left: " + this.user.clientId);
});

// Function called by a chat client, passes the message to each client
// connected by calling its processIncomingChatMessage shared function
everyone.now.distributeMessage = function (message) {
    // send the message to everyone
    everyone.now.processIncomingChatMessage(this.now.name, message);
};

Ce code est similaire à celui du chat que nous avons étudié précédemment, à part que cette fois-ci nous utilisons le module "express" de NodeJS pour créer le serveur HTTP (13 premières lignes). Express permet de servir correctement des pages HTML qui incluent des fichiers JavaScript, des images ou des feuilles de style CSS. En effet, par défaut, ce que faisait l'exemple du chat, c'est uniquement servir un fichier html tout bête. Là, express saura envoyer les bons types Mime (content/html, application/javascript, text/css) lorsqu'il servira les différents fichiers. 

Exécutez la commande suivante : "node server.js", et ouvrez "http://localhost" dans votre navigateur.

Vous devriez voir le même programme s'exécuter que dans l'étape "step 6", mais avec en plus un chat en bas de la page !

REMARQUE: Pour ceux qui ont un Mac ou qui sont sous Linux, ou ceux qui veulent faire fonctionner les exemples avec Node 0.8.x et pas le node 0.6.x, sachez que le module NowJS n'est plus compatible avec le module "express version récente". On pourra simplement utiliser un module semblable à "express", le module "connect", voici le code qui va bien. Si vous travaillez avec le NodeJS qui vous a été donné, n'en faites rien !

var connect = require("connect");var nowjs = require("now");var http = require("http");var app = connect();app.use(connect.static(__dirname + '/client'));var httpApp = http.createServer(app).listen(8080);var everyone = nowjs.initialize(httpApp);

nowjs.on("connect", function () {
    console.log("Joined: " + this.user.clientId);
});

nowjs.on("disconnect", function () {
    console.log("Left: " + this.user.clientId);
});

// Function called by a chat client, passes the message to each client
// connected by calling its processIncomingChatMessage shared function
everyone.now.distributeMessage = function (message) {
    // send the message to everyone
    everyone.now.processIncomingChatMessage(this.now.name, message);
};

FIN DE LA REMARQUE 

Ouvrez deux onglets avec le même URL, testez le chat... A la fois le paint et le chat devraient fonctionner. Dans la prochaine étape, nous utiliserons le canal du chat pour transférer les ordres de dessin.

Regardez le code source maintenant. Nous avons isolé le code JavaScript du chat dans le fichier chat.js, et nous avons ajouté le champ de saisie du chat dans la page paint.html. On utilise un <div> pour afficher les messages.

Voici les additions au fichier paint.html :

<head>
    ...
    <!-- NowJS is a High level lib built on top of socket.io. 
         Uses WebSockets if available. The page needs to
         be served by the NodeJS server -->
    <script src="js/chat.js"></script>
    <script src="/nowjs/now.js"></script>

    <script type="text/javascript">
        // Run when the DOM is ready
        $(document).ready(function () {
            // Create the pseudo object which will handle the main canvas
            paint = new PaintObject("canvasMain");
            // Bind events to the canvas
            paint.bindMultiplexEvents();

            // Init the chat system
            now.ready(function () {
                // We must be sure "now" exists. So we prompt the user only
                // when the shared variable "now" is ready !

                // prompts a dialog and asks for a nickname
                now.name = prompt("What's your name?", "");

                //Create the pseudo object which will handle the chat
                chat = new ChatObject();
            });
        });
    </script>
</head>

Remarquez que paint.html inclut un fichier now/now.js file qui est servi par NodeJS, ce fichier ne fait pas partie du projet, il est envoyé par le serveur, par son module NowJS. Le fichier now.js est en réalité localisé dans le répertoire node_modules/now dans le répertoire step 7. Sans ce répertoire node_modules, il serait impossible de faire fonctionner notre application. De même, si on ouvre paint.html en tant que file://, cela ne peut pas non plus fonctionner.

Remarquez qu'on initialise le chat qu'une fois que le DOM est prêt, dans la fonction $(document).ready() de jQuery. C'est la manière de jQuery d'implémenter le <body onload=...>

Et voilà les quatre lignes à la fin du fichier paint.html, pour le chat, ce sont les mêmes qu'on avait dans l'exemple du chat tout seul:

<div id="chat">
    <div id="chatMessages"></div>
     <input type="text" id="chatTextInput">
     <button class="menuButton" type="button" id="chatSendButton">Send</button>
</div>

Voici le code qui gère le click sur le bouton, et qui envoie le texte entré dans le champs de saisie. C'est le même que dans le chat tout seul, il est situé dans le fichier chat.js: 

var chat;

function ChatObject() {
    // Handle message input for the chat
    // When the send button has been clicked... or when the enter key has been 
    // pressed -> send content to the chat
    // server
    $("#chatTextInput").keypress(function (e) {
        var code = (e.keyCode ? e.keyCode : e.which);
        if (code == 13) {
            sendChatMessage();
        }
    });

    $("#chatSendButton").click(function () {
        sendChatMessage();
    });

    function sendChatMessage() {
        if ($("#chatTextInput").val() != "") {
            // This function is defined on the JavaScript code that runs on the server
            now.distributeMessage($("#chatTextInput").val());
            $("#chatTextInput").val("");
        }
    }

    // This function is called when a chat message arrives. Called by the server !
    now.processIncomingChatMessage = function (username, message) {
        // appends the incoming message to the messageLogs
        $("#chatMessages").append("<p><strong>" + username + "</strong> : " + 
                                   message + "</p>");
        $("#chatMessages").get(0).scrollTop = $("#chatMessages").get(0).scrollHeight;
    }
};

 Step 8 : Broadcaster les ordres de dessin

Allez dans le répertoire "step 8". Arrêtez NodeJS si il tourne encore.

Exécutez le programme comme pour le step 7.

Coté serveur : on broadcaste les ordres de dessin, sauf à l'envoyeur

Cette fois-ci, sans rien toucher au code du chat, on a rajouté juste une simple méthode appelable depuis les clients, pour broadcaster les ordres de dessin. Les ordres de dessin sont des objets JavaScript qui contiendront tout ce qu'il faut pour que le récepteur puisse reproduire le dessin quand il recevra l'ordre. Ainsi, si quatre personnes sont connectées, et que la première trace une ligne, les trois autres recevront un objet paintCommand comme {type:line, x1:20, y1:50, x2:123, y2:240}, il suffira de tester paintCommand.type pour tracer une ligne de (paintCommand.x1, paintCommand.y1) jusqu'au pixel (paintCommand.x2, paintCommand.y2) 

 

Serveur (fichier server.js) :

everyone.now.distributePaintCommand = function (paintCommand) {
    // send the paint command to everyone except sender
    everyone.exclude(this.user.clientId).now.processPaintCommand(paintCommand);
};

On utilise aussi une autre feature très pratique du module NowJS : la possibilité d'exclure quelqu'un lors du broadcast: ici l'expéditeur. La partie everyone.exclude(this.user.clientId).now.processPaintCommand(paintCommand) signifie "appelle la fonction  processPaintCommand de chaque client connecté, sauf moi ! (moi étant le client qui a émis le message).

En effet, si un client dessine une ligne, elle est déjà dessiné sur son écran, pas la peine qu'il recoive son propre message pour dessiner une ligne déjà dessinée. C'est la seule différence avec le chat où on renvoit les messages à tout le monde y compris à l'expéditeur.

Envoi des ordres de dessin

Dans le client, l'envoi des ordres de dessin est situé dans chaque drawing tool (crayon, ligne, cercle, etc). Regardons juste l'outil "ligne" dansle fichier drawingtools.js :

// The Line Drawing Tool Object
setOfDrawingTools.line = function () {
    ...

    this.mousemove = function (event) {
        ...
        sendLineMove(mousePos);
    };

    // Send the line to other clients.
    function sendLineMove(mousePos) {
        var paintCommand = {};
        paintCommand.type = 'lineMove';
        paintCommand.previousMousePos = previousMousePos;
        paintCommand.currentMousePos = mousePos;
        paintCommand.properties = paint.getCurrentContextProperties();
        now.distributePaintCommand(paintCommand);
    }

    this.mouseup = function (event) {
        ...
        paint.sendOrderToDrawFrontCanvasOnMainCanvas();
    }
};

On a ajouté une méthode sendLineMove qui est appelée pour chaque déplacement de la souris. Cette méthode construit un objet paintCommand qui a un attribut type égal à 'lineMove', et qui contient les coordonnées de départ de de fin de la ligne, et un champs nommé 'properties' qui contiendra les couleurs de dessin, de remplissage, l'épaisseur de trait courant etc. Ces propriétés sont retournées par la méthode paint.getCurrentContextProperties().

Une fois construit, on envoie cet objet au serveur pour qu'il le broadcaste, en appelant la méthode  now.distributeCommand(paintCommand) qui est sur le serveur.

Traitement des ordres de dessin qu'on reçoit du serveur

Premièrement, on a définit dans le fichier paint.html une méthode appelable par le serveur: now.processPaintCommand (paintCommand) . As it is defined as a method of the now object, it is callable by the server code.

<script type="text/javascript">
        // Run when the DOM is ready
        $(document).ready(function () {

            // prompts a dialog and asks for a nickname
            now.ready(function () {
                ...

                now.processPaintCommand = function (paintCommand) {
                    paint.processPaintCommand(paintCommand);
                };

               ...
            });
        });
    </script>

Ainsi, chaque fois que le serveur appelle processPaintCommand() chez les clients connectés, cette méthode appelera à son tour paint.processPaintCommand(paintCommand), une méthode de l'objet paint, localisée dans le fichier the paint.js. Elle reproduit le dessin correspondant à l'ordre reçu:

this.processPaintCommand = function (paintCommand) {
    // save contexts on a stack, method provided by the canvas API
    mainContext.save();
    frontContext.save();

    // change current contexts so that they are same as sender contexts
    this.changeContextsProperties(paintCommand.properties);

    switch (paintCommand.type) {
        ...
        case 'lineMove' :
            // clear the content of the front canvas
            frontContext.clearRect(0, 0, frontCanvas.width, frontCanvas.height);

            // draw the line, using values received in the paintCommand...
            ...
            break;
            ...
        case 'drawFrontCanvasOnMainCanvas' :
            // Got the order to draw front canvas on main canvas
            this.drawFrontCanvasOnMainCanvas();
            break;
        }

        // restore contexts, current color, etc. From canvas API
        mainContext.restore();
        frontContext.restore();
}

C'est un concept très simple: je reçois l'ordre de dessiner une ligne, je trace une ligne ! En fait, il y a une toute petite complication concernant les propriétés du dessin, si on est en train de dessiner en vert et qu'on reçoit l'ordre de dessiner en rouge, il faudra changer momentanément la couleur pour du rouge, puis remettre comme c'était avant, en vert !!!! On appelle cela "changer les contextes"... voici au final ce que l'on fait quand on reçoit un ordre de dessin:

  1. On sauvegarde le contexte courant  (par exemple, couleur = bleue, mode fil de fer, épaisseur du trait = 1),
  2. On change le contexte comme pour l'ordre reçu (par exemple, couleur = rouge, mode plein, épaisseur du trait = 5),
  3. On dessine,
  4. On restaure le contexte avec la valeur sauvegardée dans l'étape 1

Pour la sauvegarde et la restauration, on utilise les méthodes save() et restore() du contexte des canvas, elles sont dans l'API des canvas. Ces méthodes permettent de sauvegarder sur une pile plusieurs contextes consécutifs et de les dépiler.

Vous pouvez aussi regarder comment nous avons traité les ordres de dessin pour les différents outils implémentés (rectangle, cercle, etc).

Utilisation de la webcam pour dessiner avec l'image courante de la webcam et la transmettre

Juste pour le fun on a ajouté la possibilité de dessiner des rectangles avec pour contenu l'image courante de la webcam. Ceci ne marche qu'avec Opera ou Chrome.

Initialiser l'API getUserMedia

Comme dans les exemples du cours HTML5 concernant la webcam, on a ajouté une ligne pour initialiser la webcam dans le fichier paint.html, et on a mis le code JavaScript propre à la webcam dans le fichier webcam.js

<script src="js/webcam.js"></script>
...    
<script type="text/javascript">
        // Run when the DOM is ready
        $(document).ready(function () {
            ...
            // init web cam, works only in last test versions of chrome and opera.
            initWebCam();
        });
    </script>

Voici le fichier webcam.js. L'élément d'id "output" est un tag <video> situé lui aussi dans la page paint.html, c'est lui qui affiche la vidéo de la webcam, sur la droite de l'écran.

function initWebCam() {
    var n = navigator,
    is_webkit = false;

    function onSuccess(stream) {
        var output = document.getElementById('output'),
            source;

        output.autoplay = true;

        if (!is_webkit) {
            source = stream;
        }
        else {
            source = window.webkitURL.createObjectURL(stream);
        }

        output.src = source;
    }

    function onError() {
        // womp, womp :(
    }

    if (n.getUserMedia) {
        // opera users (hopefully everyone else at some point)
        n.getUserMedia({video:true, audio:true}, onSuccess, onError);
    }
    else if (n.webkitGetUserMedia) {
        // webkit users
        is_webkit = true;
        n.webkitGetUserMedia({video:true, audio:true}, onSuccess, onError);    }
}

Principe d'utilisation de la webcam :

  1. Appeler navigator.getUserMedia(params, onSuccess, onError)
  2. Dans la fonction de callback onSuccess, on récupère en paramètre le stream vidéo live. On a plus qu'à le mettre dans l'attribut src de l'élément <video>
  3. On commence par récupérer dans le DOM l'élément vidéo par son id (il a comme id "output"):
  4. var output = document.getElementById('output');
  5. On met le stream dans l'attribut src de l'élément <video> :
  6. output.src = stream
  7. Noter que c'est différent selon qu'on est sous Opera ou sous Chrome/Webkit, on est encore dans des impl
  8. output.src = window.webkitURL.createObjectURL(stream);

and you are done !

Ces lignes suffisent à afficher la webcam dans la page.

Peindre avec des images en provenance de la vidéo de la webcam

On a juste ajouté un "webcam" drawing tool dans drawingtools.js :

setOfDrawingTools.webcam = function() {
    var mousePos, previousMousePos, x, y;
    // ref to the video element that displays webcam real time content
    var video =document.getElementById('output');
    ...

    this.mousedown = function (event) {
        previousMousePos = getMousePos(paint.getFrontCanvas(), event);
        paint.started = true;
    }

    this.mousemove = function (event) {
        mousePos = getMousePos(paint.getFrontCanvas(), event);
        // Draw only if we clicked somewhere
        if (paint.started) {
            // clear the content of the front canvas
            paint.getFrontContext().clearRect(0, 0, paint.getFrontCanvas().width, 
                                              paint.getFrontCanvas().height);

            // Size and pos of the elastic rectangle with video snapshot inside
            var imageProperties = computeProperties(previousMousePos, mousePos);

            // Draw video content on front canvas
            paint.getFrontContext().drawImage(video,imageProperties.x,imageProperties.y, 
                  imageProperties.width,imageProperties.height);
        }
    }

    // Compute the coordinates of the top left corner and the size of the image drawn.
	function computeProperties(previousMousePos, mousePos){
	    var properties = {};
        properties.x = Math.min(previousMousePos.x, mousePos.x);
        properties.y = Math.min(previousMousePos.y, mousePos.y);
        properties.width = Math.abs(previousMousePos.x - mousePos.x);
        properties.height = Math.abs(previousMousePos.y - mousePos.y);
        return properties;
    }

    ...

    this.mouseup = function (event) {
        paint.started = false;
        paint.drawFrontCanvasOnMainCanvas();
        ...
    }
};

On utilise la même chose que pour l'outil rectangle sauf qu'on utilise la fonction pour dessiner des images (paint.getFrontContext().drawImage(video, .....)), avec comme paramètre le tag vidéo. Cela revient, comme dans les exemples du cours où on prend des snapshots d'une vidéo qui joue, à dessiner l'image courante de la vidéo.

Envoie des images pour broadcast. On les envoie en format base64 en tant que dataURL

L'astuce ici consiste à dessiner l'image de la vidéo dans un canvas invisible, qui fait la taille du rectangle qu'on dessine. Ceci permet de n'envoyer qu'une petite image, et pas une image pleine résolution. On récupère le contenu de ce canvas, sous la forme d'un dataURL, et on l'envoie au serveur pour broadcast.

On utilise ma méthode offScreenCanvas.toDataURL("image/png") pour récupérer l'image png du canvas non visible encodée en base64. Les WebSockets sont sur HTTP et HTTP est un protocole texte ! Et le format base64 c'est du texte !

On a ajouté quelques lignes au webcam drawing tool:

setOfDrawingTools.webcam = function() {
    var mousePos, previousMousePos, x, y;
    // ref to the video element that displays webcam real time content
    var video =document.getElementById('output');

    // An off screen canvas for getting webcam data
    var offScreenCanvas= document.createElement('canvas');
    var offScreenContext = offScreenCanvas.getContext('2d');

    this.mousedown = function (event) {
        previousMousePos = getMousePos(paint.getFrontCanvas(), event);
        paint.started = true;
    }

    this.mousemove = function (event) {
        mousePos = getMousePos(paint.getFrontCanvas(), event);
        // Draw only if we clicked somewhere
        if (paint.started) {
            ...

            // Size and pos of the elastic rectangle with video snapshot inside
            var imageProperties = computeProperties(previousMousePos, mousePos);

            // Draw video content on front canvas
            paint.getFrontContext().drawImage(video,imageProperties.x,imageProperties.y, 
                                     imageProperties.width,imageProperties.height);

            // draw in the offscreen canvas a snapshot of current picture 
            // displayed in the video element
            offScreenCanvas.width = imageProperties.width;
            offScreenCanvas.height = imageProperties.height;
            offScreenContext.drawImage(video, 0,0, imageProperties.width,
                                                   imageProperties.height);
            // Get this snapshot as a base64 picture
            var imageData = offScreenCanvas.toDataURL("image/png");

           // send image data through websocket
           sendWebcamMove(imageData,imageProperties);
        }
    }

   ...

    // Send the rectangle to other clients.
    function sendWebcamMove(imageData,properties) {
         var paintCommand = {};
        paintCommand.type = 'webcamMove';
        paintCommand.imageData = imageData;
        paintCommand.x = properties.x;
        paintCommand.y = properties.y;
        paintCommand.width = properties.width;
        paintCommand.height = properties.height;
        now.distributePaintCommand(paintCommand);
    }

    this.mouseup = function (event) {
        ...
        paint.sendOrderToDrawFrontCanvasOnMainCanvas();
    }
};

Traiter les images reçues en base64 pour les dessiner

On a ajouté quelques lignes dans paint.js , là où on traite les ordres de dessin reçus. Remarquez que lorsqu'on reçoit une image encodée, on reconstruit un objet de type image (var img = new Image(); et img.src = paintCommand.data... On ne peut dessiner l'image que dans le callback img.onload(), voir les exemples du cours sur le dessin d'image, on ne peut faire que de cette manière...

this.processPaintCommand = function (paintCommand) {
       ...

        switch (paintCommand.type) {
            
            case 'webcamMove' :
                // clear the content of the front canvas
                frontContext.clearRect(0, 0, frontCanvas.width, frontCanvas.height);
                // build a temporary image of the right size
                var img = new Image();
                img.width = paintCommand.width;
                img.height = paintCommand.height;

                // Listener called when the image is ready to be drawn
                img.onload = function () {
                    // draw the received picture
                    frontContext.drawImage(img, paintCommand.x, paintCommand.y);
                };

                // will call the onload listener
                img.src = paintCommand.imageData;
                break;
            ...
        }

        ...
};

 

Faire en sort que NodeJS "serve" les pages HTML5 d'exemples de l'API d'orientation et de l'API de géolocalisation

Nous vous avons préparé un petit projet pour nodeJS qui contient l'exemple à peine modifié de l'API d'orientation, dans lequel nous avons intégré le chat de la section précédente, et aussi ajouté une fonction permettant d'envoyer depuis la tablette l'orientation au PC via un appel de méthode distance. Récupérez le fichier suivant (note : il contient un répertoire node_modules pour windows, si vous êtes sur mac ou linux, reprenez celui de la question précédente).

  • L'exemple est testable sur http://ipad.gexsoft.com
  • Récupérez et dézippez cette archive : TP_interactions.zip (1.8Mo)
  • Dans ce répertoire, lancez la commande "node Server.js", le serveur se lance sur le port 80 de votre machine, changez le port si nécessaire. Pour vérifier que le serveur fonctionne correctement, ouvrez la page pointée par le lien "je suis un PC" dans un onglet de votre navigateur, entrez un nom. Puis ouvrez un autre onglet et entrez un autre nom, testez le chat. Cela doit fonctionner.

Tester l'interaction avec une tablette ou un smartphone

Nous abordons ici un point un peu délicat car pour qu'une tablette puisse parler à un serveur situé sur votre machine, dans les locaux de l'EPU c'est un peu compliqué, c'est pour cela que votre enseignant a apporté un routeur WIFI. Suivez les étapes suivantes :

  1. Connectez vous au réseau local intitulé "DLINK", la clé est "61LeMoMagique07"
  2. Ce réseau local donne des adresse en 10.0.0.xxx, regardez quelle est l'adresse IP qui vous a été allouée (ipconfig /all sous windows, etc.)
  3. Depuis la tablette, invoquez le serveur depuis un navigateur web, par exemple lors des tests de ce TP j'ai invoqué http://10.0.0.101 et j'ai cliqué sur "je suis une tablette".
  4. Entre la même adresse dans un onglet du navigateur web tournant sur le PC, et cliquez sur "je suis un PC".

Normalement vous devriez avoir le logo HTML5 qui s'affiche sur la tablette et sur le PC et lorsqu'on oriente la tablette, le logo doit bouger sur la tablette mais aussi sur le PC.

Ouvrez un autre onglet avec la même adresse, les logos des deux onglets bougent en même temps... Essayez d'ouvrir cette page depuis un autre onglet d'un navigateur tournant sur un autre PC de la salle, regardez le résultat !!! Oui, les évènements sont broadcastés à tout le monde sauf à l'émetteur. 

Maintenant essayez de connecter une seconde tablette. Que se passe-t-il ?

Pour terminer le TP : faire pareil avec la géolocalisation !

Reprenez l'exemple qui utilisait l'API de géolocalisation en début de TP.

  1. Insérez dans la page Html pour tablette le code qui récupère la position géographique de la tablette. Juste cela.
  2. Affichez cette position en texte dans la page pour vérifier que cela fonctionne.
  3. Ajoutez dans le code serveur une fonction appelable depuis les clients, qui broadcaste la position recu, de la même manière qu'on a procédé avec l'orientation,
  4. Dans la page pc.html, récupérez la position et affichez là, affichez aussi le nom de la personne emettrice.
  5. Regardez les exemples en début de TP pour afficher la position sur une google map,
  6. Essayez en connectant plusieurs tablettes de récupérer les différentes positions et de les afficher. Dommage qu'on ne puisse pas accèder au réseau local en se promenant dehors avec un tel + GPS n'est-ce pas ?

Si néanmoins cette matière vous a plus, vous pouvez heberger votre application chez un un des hébergeurs d'applis nodeJS comme nodester.com, no.de ou heroku.com. Ici un tutorial fait par un élève de l'an dernier pour héberger sur nodester L'application qu'il héberge est la suivante : http://mathieumiollan.nodester.com, un paint multi-participants.

 

 

Mots clés:
FichierTailleDateAttaché par 
 ChatNowJs.zip
Aucune description
1807.92 Ko05:20, 6 Déc 2012MichelBuffaActions
 node-v0.6.12.msi
Aucune description
3.01 Mo05:20, 6 Déc 2012MichelBuffaActions
 vcredist_x86.exe1
Aucune description
4.84 Mo05:21, 6 Déc 2012MichelBuffaActions
Images (0)
 
Commentaires (0)
Vous devez être connecté pour poster un commentaire.