Introduction à Flash communication server
Écrit par Franck Sinatra | 12-06-2003

Introduction

On va commencer par un petit debriefing sur Flash communication Server.

What is Flash communication server ?

Flash Communication Server est une application qui comme son nom l'indique tourne "coté serveur". Avec flashcomm (c'est comme ça qu'on dit dans le milieu, vous avez le droit de dire FCS aussi) on peut développer des applications multi utilisateurs (tout comme avec des serveurs Xml Sockets). En revanche, avec flashcomm, il n'est pas nécessaire de reprogrammer chaque fois un serveur spécial pour une nouvelle application. Il faut souvent développer un comportement serveur différent pour chaque application et il faut connaitre un langage de programmation comme le C, le java, le perl ou autres pour développer un serveur Xml socket. Avec flashcomm, on code les comportements serveur en ActionScript et on peut définir une nouvelle application juste en créant un repertoire qui porte son nom. Contrairement aux serveur xml sockets, flashcomm permet également de publier/recevoir de la video et du son en "vrai" streaming. Jusqu'à présent quand on voyait de la vidéo dans du flash ce n'était que des vidéos directement integrées dans un swf de diffusion, soit des vidéos transformées en swf que l'on appellait avec des loadmovies. On pouvait croire à du streaming (et dans un sens c'est pas faux) mais en fait ce n'était que du "progressive download". En gros vous demandez à votre player flash de télécharger un swf qui arrive plus vite qu'il n'est lu. Et pour finir, FlashComm permet surtout de recevoir et de publier de la video et du son en live. C'est ça qu'est le mieux en fait :). Il y a bien sûr d'autres possibilités avec FlashComm mais là j'ai parlé du principal.

J'ai besoin de quoi ?

Pour développer des applications avec FlashComm, macromedia propose une version gratuite de demo ici.
Il vous faut aussi flash MX et Je vous conseille vivement de télécharger SciTE|Flash pour tout ce qui est actionScript server side. Il est préférable d'avoir une web cam pour ce tutorial.

Qu'allons nous faire ?

Dans ce tutorial, je vais vous expliquer comment faire un chat vidéo sans gestion du son. Pourquoi sans son ? tout simplement parce qu'apres avoir fait des tests on s'apercoit vite qu'avec les délais cela rend la discussion difficile (souvenez vous des liaisons télé avec l'autre bout de la terre où le journaliste parle pendant que l'autre repond etc..). Alors imaginez à 4 ou 5 ce que cela donne. Je ferais surement d'autres tutoriaux ou le son aura sa place.
Dans un premier temps, on va faire une connexion à flashcomm. Ensuite on verra la gestion des utilisateurs coté serveur. On fera un chat en utilisant un Shared Object puis on definira un ensemble de fonctions pour les clients et le server (arrivée et le depart des utilisateurs, diffusion de vidéo, provoquer un événement etc..). Et on ferra en sorte que tout marche au mieux. Pour ce tutorial il est préferable de posséder une web cam mais vous pouvez quand même suivre le debut sans (les histoire de connexions le chat et tout..).

 

Commençons

je pars du principe que vous avez réussi l'installation du serveur... (c'est pas dur).
Pensez aussi à installer les "outils" comme le "app inspector" qui est nul mais malheureusement obligatoire, et notez bien les login et pass lors de l'instalation ainsi que le port pour pouvoir vous connecter à cet app inspector.

Bon alors comme pour chaque nouveau projet la premiere chose à faire c'est de créer un repertoire qui porte le nom de l'application. Pour ce tutorial je prendrai le nom "videochat" (macromedia conseille de ne pas utiliser de majuscules ni de caracteres spéciaux comme les espaces etc..). Donc il faut créer ce repertoire dans le repertoire "applications" du serveur. Si vous avez fait une installation standard c'est dans "C:Program FilesMacromediaFlash communication Server MXapplications". Vous remarquerez au passage qu'il ya déjà plein de répertoires, ce sont les répertoires des applications macromedia. Vous remarquerez aussi que dans presque tous les répertoires, il y a un fichier "main.asc". C'est dans ce fichier qu'on codera les comportements serveur de notre application. Vous pouvez dès maintenant créer ce fichier avec SciTE|Flash (ou tout autre editeur de text genre note pad) et L'ENREGISTRER AU FORMAT UTF8. C'est très important sinon vous aurez par la suite des problèmes d'urlencodage (j'aime bien ce barbarisme) avec les accents et tout..

L'app inspector

Cet outil integré dans l'environement auteur permet à tout moment de se connecter à vos diverses applications flashcomm. On va se connecter à notre nouvelle application. Entrez dans le champ host l'adresse rtmp du server flashcomm sur lequel vous souhaitez travailler suivit de ":" et du port (choisi lors de l'installation). Si c'est sur votre propre ordinateur que tourne flashcomm, "localhost" marchera. Puis cliquez sur "connect". Vous tombez sur une deuxieme fenêtre qui présente la liste des instances d'applications qui tournent en ce moment. Normalement la liste est vide. Tapez le nom de votre application (videochat) dans le champ app/ins puis cliquez sur load. Le nom de l'application apparait dans la fenêtre et vous pouvez cliquez "view details" pour voir les détails de cette instance d'application. Vous arrivez enfin sur un troisième écran avec divers panneaux contenant des informations sur l'instance d'application sélectionée.

Le panneau live Log est une fenêtre de sortie tout comme dans flash MX. Un trace exécuté coté serveur (dans le main.asc ou autres fichiers server side) apparait dans cette fenêtre. FlashComm trace pas mal d'infos dans cette fenêtre, notemment les erreurs de scripts. Cette fenêtre est très importante pour le débogage. En cliquant sur "Reload App" on peut voir flashcomm tracer des trucs lors d'un "relancement" d'application. Les trois autres panneaux contiennent des informations sur les streams, les shared Objects et des stats. jettez un oeil vite fait pour la forme.

Connexion

bon ben maintenant qu'on a notre instance d'application et qu'on peut l'observer, on va s'y connecter. Et c'est pas dur.
Dans un nouveau fichier tapez ça sur la premiere image:

nc = new netConnection();
nc.connect("rtmp:/videochat/");

Je m'arrete dès maintenant. Ici j'ai utilisé une connéction locale. En effet si le serveur tourne sur votre ordinateur vous pouvez vous connecter aux instances d'applications en utilisant qu'un seul "/" suivit du nom de l'application sinon il faut faire:

nc = new netConnection();
nc.connect("rtmp://localhost/videochat/");

Lorsque vous lancez le swf ont peut voir dans les statistiques de l'app inspector qu'il y a un "active User".

Il est quand même plus intéressant de voir si la connexion a réussit ou pas instantanement. On peut créer un OnStatus sur la connexion pour savoir ce qu'il se passe. Flashcomm retourne au client flash un objet contenant des informations dans certain cas (lors d'une déconnexion, lorsque vous réussissez à vous y connecter, etc..). On peut tracer la propriété "code" de cet objet. Ces messages sont décrits dans l'aide flashcomm à la page "Client-Side Information Objects".

nc = new netConnection();
nc.onStatus = function(infos){
trace(infos.code);
}
nc.connect("rtmp://localhost/videochat/");

Très bien vous êtes connecté a flashcomm..

main.asc

Pour ce videochat, nous allons utiliser 3 gestionnaires d'événements serveur.
Le "onAppStart" qui est déclenché au lancement de l'application. Il sert souvent à l'initialisation des variables serveur et a la création/suppression de shared Objects.
Le "onConnect" qui est invoqué lors de la connexion d'un client au serveur (du moins lors de la tentative de connexion). C'est dans cet événement que pratiquement tout ce passe (on instancie les clients, on leur donne des méthodes, on incrémente/décrémente des variables serveur etc..).
Le "onDisconnect" qui lui est invoqué lors de la déconnexion d'un client. Lors de cet événement on met à jour les variables serveur , et souvent on previent les autres clients de la déconnexion de l'utilisateur qui a provoqué l'évenement.

Lorsque flashcomm accepte une connexion, il pousse dans un objet serveur l'instance du client (application.Clients). On pourrait se satisfaire de cet objet pour lister tous les clients mais on se rend vite compte qu'il est difficle de cibler les clients avec cet objet. On est obligé de faire des boucles sur cet objet pour trouver le bon client. Il parait plus judicieux de stocker les instances de clients dans un tableau associatif. Pour cette application on va stocker les clients dans un tableau "listeClients". On pourra par exemple cibler chaque client de cette maniere :

application.listeClients["jean-paul"]

On crée cet objet au démarage de l'application donc dans votre main.asc tapez:

application.onAppStart = function(){
this.listeClients = {};
}

c'est de tout ce qu'on aura besoin comme variable serveur. Un objet ou on stocke toutes les instances de clients.

Avant la connexion de l'utilisateur, on lui demandera d'entrer un identifiant (un pseudo par exemple). Cet identifiant sera unique sur le serveur et il sera une propriété de l'objet "listeClients" du serveur. En fait lors de la connexion on regardera si l'identifiant n'existe pas déjà. S'il existe déjà, on refusera la connexion. Sur la scéne principale, créez un champ de texte de type "input" et nommez le "login". Créez aussi un bouton et nommez le "btnConnect". Et pendant que vous y êtes créez aussi un champ de texte dynamique et appellez le "serverMsg". Il sera destiné à afficher les messages provenant du serveur.
Il faut maintenant que l'utilisateur clique sur le bouton "btnConnect" pour se connecter. Il faut aussi passer le nom de l'utilisateur au serveur pour qu'il puisse stocker l'instance de ce nouveau client dans son tableau "listeClient". Ca donne ça:

nc = new netConnection();
nc.onStatus = function(infos){
trace(infos.code);
}
btnConnect.onPress = function(){
if(login.text!=""){
nc.connect("rtmp://localhost/videochat/",login.text);
this.enabled = false;
}
}

J'ai rajouté juste un petit test pour vérifier que le champ ne soit pas vide au moment de la connexion. Et je désactive le bouton de connexion pour qu'il n'y ait pas de connexions intempestives.
Si vous testez l'animation, vous ne verrez pas grand chose de nouveau.. Il faut traiter cette demande de connexion du coté du serveur. Lorsque l'utilisateur clique sur le bouton, une liaison est établie et l'événement "onConnect" est appelé sur le serveur. On va pouvoir ajouter ce nouveau client dans notre liste. Il faut savoir que dans le gestionnaire d'événement "onConnect" le premier parametre est l'instance du client qui provoque l'événement. Apres ce parametre, suivent ceux passés lors de la connexion. Nous on en a qu'un c'est le pseudo du client. On va d'abord vérifier que le pseudo n'existe pas dans notre objet "listeclient". S'il existe, on refuse la connexion sinon on l'accepte.

application.onConnect = function(nouveauClient, nomDuClient){
//nouveauClient est l'instance du client qui cherche à se connecter.
//nomDuClient c'est le nom du client..


//on vérifie que cette entrée n'existe pas dans notre objet "listeClients".

if(this.listeClients[nomDuClient]!=null){
//ah ben la, pas de bol il y a déjà quelqu'un sur le serveur qui porte ce nom de client.
//on crée un message d'erreur
que flashcomm retourne à notre nouveauClient.
this.rejectconnexion(nouveauClient, {msgErreur:"ce pseudo existe déjà"});
}else{
//sinon on accepte la connexion du nouveauClient
this.acceptconnexion(nouveauClient);
//il est intéressant de pouvoir accéder au nom du client depuis son instance.
//donc on donne une propriété "nom" à notre instance de nouveauClient

nouveauClient.nom = nomDuclient;
//et on pousse l'instance du client dans notre tableau.
this.listeClients[nomDuClient] = nouveauClient;

}
}

Si le pseudo existe deja, flashcomm rejette la connexion et peut envoyer au client un message. Ce message est stocké dans l'objet "infos" envoyé au client lors de certain cas. Coté client, on a les moyens de savoir si la connexion a été acceptée ou refusée grace au message envoyé par flashcomm. Il suffit de tester les messages quand le onStatus est invoqué.

nc = new netConnection();
nc.onStatus = function(infos){
trace(infos.code);
if(infos.code=="netConnection.Connect.Success"){
//connexion réussie
}else if(infos.code=="netConnection.Connect.Rejected"){
//connexion refusée.
//je recoit aussi à ce moment la, le message prédéfini du serveur. Je peux l'afficher dans la fenêtre "serverMsg";
serverMsg.text = infos.application.msgErreur;
//je réactive le bouton de connexion
btnConnect.enabled = true;
}

N'oubliez pas de redémarrer l'application à chaque modification du main.asc.

Maintenant on remplis à chaque nouvelle connexion (si le pseudo n'existe pas déjà) notre tableau avec des instances de client. Mais il faut lors de la déconnexion de celui ci virer cette instance sous peine de trainer des "ghosts" sur notre serveur. On appelle "ghost" les images d'utilisateur non supprimées et qui laissent des traces d'elles sur le serveur alors qu'elles ne devraient plus etre la.. L'objet "Clients" de flashcomm se met à jour tout seul lors des déconnexions mais nous, vu que nous nous basons sur notre objet "listeClients" pour la gestions de nos clients, nous sommes obligé de supprimer manuellement l'instance du client deconnecté. Ajoutez à votre main.asc le troisieme gestionnaire d'événement, le "onDisconnect". Ce gestionnaire d'événement n'a qu'un seul parametre, c'est l'instance du client deconnecté. Ajoutez ceci a votre main.asc:

application.onDisconnect = function(ancienClient){
this.listeClients[ancienClient.nom] = null;
delete this.listeClients[ancienClient.nom];
}

voila pourquoi lors de la connexion on avait donné une propriété nom à notre instance de client c'était pour supprimer par la suite l'instance de l'objet "listeClient".

Télécharger le fla et le main.asc :

 flashcomm1.zip flashcomm1.zip (8.82 KB)

Avoir une bonne gestion des clients coté serveur est trés important, voila pour quoi j'ai attaché pas mal de temps à cette premiere partie. Passons à le chat.

Le chat

Pour le chat, vous aurez besoin de trois éléments en plus. Un champ input de saisie, un champ dynamique pour le chat et un nouveau bouton pour envoyer le texte. Créez donc ces instances et nommez les dans l'ordre "saisie", "leChat", "btnEnvois".
"Comment on fait un chat en flash ?", en voila une question qui est souvent posée ben je vais vous expliquer une des mutliples solutions. Ce que nous allons faire, c'est créer un shared Object sur le serveur. Tous les clients se connecteront à ce shared Object et à chaque fois qu'il changera, tous les clients en seront prévenu. C'est un peu le principe du chat, dès qu'il y a du nouveau, on informe les autres. On peut aussi enregistrer les shared Objects sur le serveur pour garder un historique du chat. Un shared Object peut avoir plusieur propriétés dans notre exemple il n'en aura qu'une ce qui evite de faire des boucles de testes pour trouver la propriété qui change. Avant de créer ce shared Object, on va créer une fonction pour la connexion.

nc = new netConnection();
nc.onStatus = function(infos) {
trace(infos.code);
if (infos.code == "netConnection.Connect.Success") {
msgServer.text = "message server : nVous êtes connecté";
//connexion réussie
} else if (infos.code == "netConnection.Connect.Rejected") {
/*connexion refusée.
je recoit àce moment la, le message prédéfini du serveur. Je peux l'afficher dans la fenêtre "serverMsg";*/

msgServer.text = "message server : n"+infos.application.msgErreur;
//je réactive le bouton de connexion
btnConnect.enabled = true;
}
};

//on declare la fonction de connexion
connexion = function () {
//on stocke le nom de l'utilisateur dans une variable
monNom = login.text;
//on se connecte
nc.connect("rtmp://localhost/videochat/", monNom);
};

//on définit les actions du bouton
btnConnect.onPress = function() {
if (login.text != "") {
//on appelle la fonction de connexion
connexion();
//je désactive le bouton
this.enabled = false;
}
};

Voila qui est mieux.
Lors de notre connexion on va créer ce fameux shared Object et s'y connecter. Dans la fonction "connexion", ajoutez ceci, j'explique juste apres:

connexion = function () {
//on stocke le nom de l'utilisateur dans une variable
monNom = login.text;
//on se connecte
nc.connect("rtmp://localhost/videochat/", monNom);
//création d'un shared Object et affectation de celui ci dans une variable
chat_so = SharedObject.getRemote("textPartage", nc.uri, false);
//on crée un gestionnaire d'événement à notre référence.
chat_so.onSync = function(liste){
//la y'aura des trucs apres.
}
//et on connecte notre shared Object a notre objet nc (nc, c'est notre connexion)
chat_so.connect(nc);
};

Avec la méthode getRemote de l'objet shared Object, si le shared Object "textPartage" n'existe pas sur le serveur, Flashcomm le crée aussitôt et provoque l'événement onSync du shared Object client qui l'a créé. Flashcomm retourne au client un tableau contenant un nombre d'objets égal au nombre de propriétés du shared Object existant plus un objet d'infos. Autrement dit vu que le shared Object est vide, on ne reçoit qu'un tableau avec un objet qui n'a qu'une seule propriété; la propriété "code" et qui est egale à "clear". Si le shared Object existe, flashcomm nous retournera un tableau composé d'objets comme indiqué juste au dessus. Mais comme notre shared objet existera et n'aura qu'une propriété (le text du chat) donc on ne recevra qu'un tableau à 2 entrées. Dans la methode getRemote on doit préciser en deuxieme parametre l'"uri" d'une connexion. L'uri d'une connexion c'est le nom d'instance d'une application créée lors d'une connexion. Dans notre connexion nous n'avons pas preciser d'uri et Flashcomm par defaut en crée une "_definst_". En fait il est possible avec flashcomm de créer plusieur instances de la même application. Il suffit de renseigner des noms d'instances d'applications different lors de la connexion. On aurait pu par exemple faire une connexion comme ça:

nc.connect("rtmp://localhost/videochat/lapin/", monNom);
//ou
nc.connect("rtmp://localhost/videochat/entrecote/", monNom);

Flashcomm aurait lors de ces connexions, créé des instances différentes de la même application. Alors pourquoi est ce que je vous dis ça et pourquoi faut il lors de la création ou de la connexion à un shared Object préciser l'uri ? Cela permet avec ce parametre d'utiliser plusieur shared Objects portant le même nom mais étant differencié par leur uri. Pour une gestion de "rooms" par exemple ça evite de faire trois applications différentes qui auraient les mêmes comportements. Le troisieme parametre de la methode getRemote est un booléen qui indique si le remote shared Object doit etre persistent ou temporaire.
Ensuite j'ai défini le gestionnaire d'événement onSync de mon shared Object. C'est cette fonction qui sera appelée à chaque fois que le shared Object "textPartage" sera modifié sur le server. Et enfin, on attache notre shared Object à notre connexion. En testant l'animation, et en vous connectant vous verrez apparaitre dans l'app inspector, dans le volet shared Object et dans l'emplacement "temporary" s'inscrire "textPartage". C'est que le shared Object est bien créé.

On a vu que l'événement onSync était appelé sur tous les clients connectés au shared Object à chaque modification d'une de ses propriétés on va maintenant modifier une propriété de celui ci.

Quand on cliquera sur "btnEnvois" on mettra à jour le shared Object contenant le le texte du chat. Tous les utilisateurs en seront alors informé.

btnEnvois.onPress = function() {
chat_so.data.leTexte = leChat.text+monNom+" : "+saisie.text+"n";
saisie.text = "";
};

Avec ce code, lors du clic on enverra la totalité du champ "leChat" plus le nom de l'utilisateur concaténé avec le contenu du champ de saisie. Et on stockera tout ce texte dans la propriété "leTexte" du shared Object.
Il ne reste plus qu'a mettre à jour le nouveau texte chez tous les clients. Ca se passe dans le onSync du shared Object.

chat_so.onSync = function(liste){
leChat.text = chat_so.data.leTexte;
leChat.scroll = leChat.maxscroll;
};

Et voila vous avez un chat. Je vais quand meme ajouter quelques trucs sur le onSync parce que la, je suis sur que vous êtes en train de vous dire "mais pourquoi il passe TOUT le texte et pas juste la dernière ligne ajoutée ?". La première réponse c'est qu'en passant tout le chat, ça permet d'avoir un petit historique. La deuxième c'est que flashcomm ne provoque le onSync chez les client QUE si le shared Object change. Autrement dit vous n'auriez pas pu envoyer deux fois la même phrase. Car le shared Object ne se trouvant pas modifié flashcomm n'aurait rien envoyé à personne.. enfin, plutot à tout le monde. Et la troisième réponse c'est que même s'il y a plusieur façons de faire un chat (sans même utiliser les shared Object), c'était un moyen d'expliquer un peu comment ca marche :).
Il faudra penser notament à ne garder qu'une certaine taille d'historique sous peine d'envoyer des centaines de ligne à Flashcomm.

Je vais aussi ajouter quelques infos sur le parametre "liste" du onSync. Une fois qu'un client est connecté à un shared Object, à chaque fois que quelqu'un modifie une des propriétés de celui ci, l'événement onSync est appelé et flashcomm envoit un tableau au client. Il contient autant d'objets que de propriétés qui ont été changées. Et chaque objet a une propriété "name", "code" et "oldValue". Pour "name" je pense que c'est pas trop dur d'imaginer qu'elle contient le nom de la propriété. Quand à "code" c'est une description de la propriété. Elle peut avoir 5 valeurs. Lors d'une modifiction de propriété d'un shared Object si c'est vous qui avez provoqué le changement le code sera egal à "success" si c'est quelqu'un d'autre il sera égal à "change". Si la propriété est supprimée, code sera égal à "delete" et si la tentative de modification a échouée elle sera égale à "reject". Et enfin, elle sera égale à "clear" lors de la connexion à un shared Object persistant. La troisème propriété est "oldValue", elle contient la valeure de la propriété avant sa derniere modification.

Télécharger le fla et le main.asc :

flashcomm2.zip flashcomm2.zip (9.90 KB) 

De la vidéo maintenant

Construction des fenêtres des utilisateurs

Je pense qu'on a déjà fait le plus dur, passons aux fioritures.. la vidéo !
A l'arrivée d'un nouvel utilisateur sur le chat vidéo il y deux choses à faire. Premierement, il faut que le nouvel utilisateur recoive les infos du chat (le nombre d'utilisateurs déjà connectés, et leurs pseudos). Deuxiemement, il faut avertir les autres utilisateurs déjà connectés de l'arrivée du nouveau client. Nous allons donc créer deux fonctions qui seront appellées par le serveur lors de la connexion. Pour que le serveur puisse appeller ses fonctions, il faut les définir sur l'objet "netConnection". La premiere est la fonction "arrivee", la seconde est "unNouvo". Chaque utilisateur sera représenté par une petite fenêtre et qui portera le nom de l'utilisateur.

Il vous faut un clip composé au minimum d'un champ de texte dynamique avec en parametre "var" : "pseudo" et un objet vidéo "laVideo". Donnez à ce clip un nom de liaison "clipVideo".
Apres avoir créé la connexion, on definit une fonction "arrivee" dans cet objet. On aura besoin de créer d'autres fenêtres par la suite donc on fait une fonction "creefenetre".

creefenetre = function(unClient){
_root.attachMovie("clipVideo", "fenetre_"+unClient.nom, prof, {pseudo:unClient.nom, _x:(Math.random()*178+148), _y:(Math.random()*124+61)}); prof++;
}
nc.arrivee = function(lesGens){
for (var i in lesGens) {
creefenetre(lesGens[i]);
}
}

On definit dans le main.asc l'appel de cette fonction lors de la connexion du client.

else{
//sinon on accepte la connexion du nouveauClient
this.acceptconnexion(nouveauClient);
//il est interessant aussi de pouvoir acceder au nom du client depuis son instance.
//donc on donne une propriété nom a notre instance de nouveauClient

nouveauClient.nom = nomDuClient;

//appel de la function "arrivee" sur le client.
nouveauClient.call("arrivee",null,this.listeClients);

//on pousse l'instance du client dans notre tableau.
this.listeClients[nomDuClient] = nouveauClient;
}

La méthode "call" sert à lancer des fonctions chez les clients. Le premier paramètre c'est le nom de la fonction à appeller. On peut mettre en second parametre un objet de resultat. Si par exemple la fonction invoquée chez le client doit renvoyer quelque chose au serveur, on peut créer sur le serveur un objet avec des methodes onResult et onStatus. On peut à partir du troisieme, passer autant de paramètre que l'on veut à la fonction appellée. Ici on passe juste le tableau contenant les références des autres clients déjà connectés.

On appelle "arrivee" avant d'avoir ajouter la référence du nouveau client pour ne pas avoir a faire un test lors du second appel de fonction (la fonction "unNouvo"). Pour l'instant, on ne recoit lors de la connexion que la liste des clients déjà connectés. Le tableau envoyé ne contient pas les informations du nouveau client. C'est pour cela que si vous testez l'animation, vous ne verrez pas de fenetre. Pour en voir une il faut qu'il y ait déja quelqu'un de connecté.

Le parametre "lesGens" dans la fonction "arrivee" c'est le tableau contenant toutes les références des clients connectés avant vous. On a donc acces à la propriété "nom". J'utilise une variable de profondeur "globale" pour ne pas etre embété à l'arrivée d'un nouveau client. Avec cette fonction on crée sur la scène principale, les fenêtres des utilisateurs et on leur donne un nom. A vous de voir comment les agencer, moi j'ai fait des positions aléatoires. On leur donnera un petit comportement pour pouvoir les deplacer.

On va maitenant avertir tous les clients de l'arrivée du nouveau. Pour cela, apres avoir cette fois ci pousser l'instance du client dans notre tableau "listeClient" sur le serveur, on va appeller la fonction "unNouvo". Il suffit de faire une petite boucle sur l'objet "listeClient" comme ceci dans le main.asc.

else{
//sinon on accepte la connexion du nouveauClient
this.acceptconnexion(nouveauClient);
//il est interessant aussi de pouvoir acceder au nom du client depuis son instance.
//donc on donne une propriété nom a notre instance de nouveauClient

nouveauClient.nom = nomDuClient;
//appel de la function "arrivee" sur le client.
nouveauClient.call("arrivee",null,this.listeClients);
//on pousse l'instance du client dans notre tableau.
this.listeClients[nomDuClient] = nouveauClient;

//et on appelle la function "unNouvo" sur tous les clients
for(var i in this.listeClients){
this.listeClients[i].call("unNouvo",null,nouveauClient)
}

}

Voila pourquoi on ne pousse la référence de l'instance du nouveau client qu'apres avoir appelé la fonction "arrivee". C'est tout simplement pour ne pas avoir une fenêtre en double.
Le parametre passé à la fonction "unNouvo" est la référence du nouvel arrivant. Il ne reste plus qu'a créer la function "unNouvo" sur notre objet connexion.

nc.unNouvo = function(leNouvo){
creeFenetre(leNouvo);
}

 

Comme au dessus on attache sur la scène principale une instance "clipVideo" on lui donne son nom et tout.
Voila on a une représentation graphique de tous les client présents. Vite fait on definit un comportement pour le deplacer.

Ajoutez dans la fonction "creeFenetre" ces lignes pour pouvoir deplacer les fenêtres:

creefenetre = function(unClient){
var ref = _root.attachMovie("clipVideo", "fenetre_"+unClient.nom, prof, {pseudo:unClient.nom, _x:(Math.random()*178+148), _y:(Math.random()*124+61)}); ref.onPress = function(){
this.startDrag();
}
ref.onRelease = function(){
this.stopDrag();
}
prof++;
}

bon bien sûr on peut faire mieux mais voila quoi je fais pas un turorial sur l'art du startDrag().

Il ne reste plus qu'a supprimer les fenêtres des gens qui partent. Une troisieme fonction appellée lors de l'événement onDisconnect sur le serveur.
Comme les fenêtres des utilisateurs portent le nom de l'utilisateur concaténé avec "fenetre_", il suffit lors de la déconnexion de ne passer que le nom de l'utilisateur qui se déconnecte.

nc.senva = function(qui) {
_root["fenetre_"+qui].removeMovieClip();
};

Et sur le serveur il faut prevenir tous les clients de la déconnexion d'un client.

application.onDisconnect = function(ancienClient){
this.listeClients[ancienClient.nom] = null;
delete this.listeClients[ancienClient.nom];

// on appelle la function "senva" sur tous les clients
for(var i in this.listeClients){
this.listeClients[i].call("senva",null,ancienClient.nom);
}

}

Et voila..

Télécharger le fla et le main.asc :

flashcomm3.zip flashcomm3.zip (10.40 KB) 

Les streams

Maintenant que nous avons des conteneurs pret à recevoir les vidéos, il ne suffit plus que d'attacher les vidéos des utilisateurs, aux fenêtres correspondantes. Il faudrait peut etre qu'avant de les recevoir, nous les publions. Lors de la connexion on va créer un objet Stream sur notre connexion, on choppera la Camera par defaut de l'utilisateur et on publiera le flux. On associe toujours les objets Streams à une connexion (comme pour les shared Object). Dans la fonction de connexion, on crée un objet Stream et on Publie la camera.

// on crée ensuite un objet Stream sur notre connexion
ns = new NetStream(nc);
// on definit un objet Camera
maCam = Camera.get();
// on attache l'objet camera au stream
ns.attachVideo(maCam);
//et finalement, on publie le stream qui portera le nom du client.
ns.publish(monNom);

Dans la méthode "publish", on peut préciser en parametre facultatif "record" pour enregistrer le flux sur le serveur. Flashcomm enregistre un monNom.flv. Si ce fichier existe déjà, il sera ecrasé. On peut passer "append" pour enregistrer le flux sur le serveur mais si monNom.flv existe déjà, flashcomm enregistrera le flux a la suite du monNom.flv existant. Si on ne passe rien ou "live" flashcom n'enregistre rien. En testant l'animation, le flash plyer nous demande si on veut bien laisser l'acces à la camera.. Si on répond oui, on peut voir dans l'app inspector un flux. Le flux a le même pseudo que vous :).

Dans notre fonction "creeFenetre" on va ajouter quelques lignes pour attacher les flux des clients aux objets vidéo correspondant.

//crée un nouvo stream et on l'attache la connexion
ref.ns = new NetStream(nc);
// on attahce ce stream a notre objet video
ref.laVideo.attachVideo(ref.ns);
//et on demande a notre stream de lire le flux du client
ref.ns.play(unClient.nom);

Voila. Ca marche. On a un vidéo chat. Bon il y a quand même des tests à faire. Par exemple, rien ne sert de créer et publier un flux vide.. On va faire un petit test. Déja si l'utilisateur possede une Camera par defaut puis s'il accepte que l'on y accede.

// on definit un objet Camera
maCam = Camera.get();
//on crée un stream sur notre connexion
ns = new NetStream(nc);
//on attache notre camera a notre stream

ns.attachVideo(maCam);
// si l'utilisateur possède une caméra..
if (maCam != null) {
// on crée un gestionnaire d'événement onStatus sur la cam.
maCam.onStatus = function(info) {
// si on autorise la camera
if (info.code == "Camera.Unmuted") {
// on publie notre flux
ns.publish(monNom);
// sinon
} else if (info.code == "Camera.Muted") {
// on arrete de publier
ns.publish(false);
}
};
}

Par contre par defaut nous tentons de lire TOUS les streams (meme les streams des clients qui ne publient pas). Pourquoi ? parce que meme si sur un stream on peut créer un OnStatus event, il ne sera pas évoqué si ce meme stream ne lit pas quelque chose (meme un flux vide ou inexistant). Le mieux serait de prevenir les clients de l'arret de publication d'un autre et de stopper la lecture du flux.
Mais si on fait cela, on ne peut plus etre prevenu de l'éventuelle reprise de publication du client. Car une fois que l'on stop la lecture d'un flux, on est plus "relié" au serveur par celui ci. C'est con.. Pour exemple : je me connecte, je decide de ne pas laisser acces a ma camera. En faisant, il est logique de prevenir les clients d'enface et de dire "HEHO ! Sinatra, il publie pas! stoppez moi ce flux qui sert a rien !". Apres reflexion, je decide de laisser au flashplayer l'acces a ma camera. Le flux étant stoppé chez tous les clients, le onStatus "publish notify" n'est pas appelé sur leur stream "sinatra". Su coup pas moyen de jouer avec les onStatus des stream pour savoir qui coupe et qui relance... Pour ce tutorial, je n'ai pas prévu de fonction speciale (stop/demmarre flux) mais c'est quand meme faisable.

De tout facon, les flux etant vides... flashcomm ne vous envoie rien car il detecte bien que rien ne change et la bande passante passante n'en souffre pas.

Avant de finir on va quand meme regler les parametre de la camera car pour l'instant on envoie de la vidéo par défaut. Je vous laisse choisir les parametres de compression et le nombre de frame par seconde pour un resultat optimal.

// on definit un objet Camera
maCam = Camera.get();
//on definit longueur, hauteur, et fps de capture.
macam.setMode(80, 60, 8)
//on definit la qualité et la bande passante maximum a utiliser
macam.setQuality(8192,50);

Dans le setMode, on définit donc: longueur, hauteur et fréquence du nombre d'images par seconde. On peut préciser un booléen en 4 ème parametre pour préciser comment la caméra doit gérer cette taille. Par defaut c'est à "true", la camera passe en 80*60 la totalité de ce quelle filme. Sinon elle envoie à flashcomm une fenêtre de 80*60 taillée dans votre vidéo. Dans le setQuality, on renseigne la bande passante et la qualité de la vidéo. Vous pouvez en passant 0 a l'un des deux parametres, obliger flash de garantir soit un débit, soit une qualité.

macam.setQuality(8192,0);
//vous assure que flash n'envera pas plus que 8K par seconde..
macam.setQuality(0,50);
// peut importe la bande passante que ça prendra, flash enverra une image de qualité 50.
macam.setQuality(8192,50);
// Flash envoie une image de qualité 50 sans jamais passer au dessus de 8K par seconde.

Télécharger le fla et le main.asc :

flashcomm4.zip flashcomm4.zip (10.39 KB) 

Conclusion

Ca y est, je pense que ceux qui ont lu ça en entier s'imaginent comment gérer autrement tout ce bordel et implémenter voir repenser tout ça. Ce tutorial est la pour ça.. juste pour montrer comment manipuler vite fait les objets mis à notre disposition. Apres, à vous d'adaptez tout ça suivant vos besoins.
Ce tutorial montre les trucs de base de flashcomm mais il manque quand même une ou deux choses importantes. Il manque par exemple l'appel de fonction serveur depuis un client.. Je referais un tutorial pour ça alors..

par Franck Sinatra (Alexis Clairet) [06/2003]

 
Dernière mise à jour : 11-08-2005