Pratique d'Action Script 3 - part 2


Pratique d'Action Script 3 - part 2

 

Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 1 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org 14 Chargement et envoi de données LA CLASSE URLLOADER ................................................................................... 1 CHARGER DU TEXTE................................................................................................ 3 L?ENCODAGE URL................................................................................................ 13 CHARGER DES VARIABLES .................................................................................... 14 CHARGER DES DONNÉES XML.............................................................................. 20 CHARGEMENT DE DONNEES ET SECURITE.............................................. 30 CHARGER DES DONNÉES BINAIRES ............................................................ 32 CONCEPT D?ENVOI DE DONNEES ................................................................. 37 ENVOYER DES VARIABLES..................................................................................... 38 LA METHODE GET OU POST ................................................................................ 45 ENVOYER DES VARIABLES DISCRÈTEMENT............................................................ 48 RENVOYER DES DONNEES DEPUIS LE SERVEUR...................................................... 50 ALLER PLUS LOIN.................................................................................................. 56 ENVOYER UN FLUX BINAIRE.................................................................................. 57 TELECHARGER UN FICHIER.......................................................................... 58 PUBLIER UN FICHIER ............................................................................................. 68 PUBLIER PLUSIEURS FICHIERS ............................................................................... 73 CREATION DE LA CLASSE ENVOIMULTIPLE........................................................... 80 RETOURNER DES DONNEES UNE FOIS L?ENVOI TERMINE........................................ 89 ALLER PLUS LOIN.............................................................................................. 93 La classe URLLoader Afin de concevoir une application dynamique nous avons besoin de pouvoir envoyer ou charger des données externes au sein du lecteur Flash. Le chargement dynamique de données offre une grande souplesse de développement en permettant la mise à jour complète du contenu d?une application sans recompiler celle-ci. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 2 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Parmi les formats les plus couramment utilisés nous pouvons citer le texte ainsi que le XML mais aussi un format brut comme le binaire. Nous reviendrons en détail sur la manipulation de données binaire au cours du chapitre 20 intitulé ByteArray. ActionScript 3 offre la possibilité de charger et d?envoyer ces différents types de données grâce à la classe flash.net.URLLoader qui remplace l?objet LoadVars ainsi que les différentes fonctions loadVariables, loadVariablesNum utilisées dans les précédentes versions d?ActionScript. Nous retrouvons ici l?intérêt d?ActionScript 3 consistant à centraliser les fonctionnalités du lecteur. Il est important de noter que contrairement à la classe Loader, la classe URLLoader diffuse directement les événements et ne dispose pas d?objet LoaderInfo interne. Ainsi l?écoute des différents événements se fait directement auprès de l?objet URLLoader. En revanche, les événements diffusés sont quasiment les mêmes que la classe LoaderInfo. Voici le détail de chacun d?entre eux : ? Event.OPEN : diffusé lorsque le lecteur commence à charger les données. ? ProgressEvent.PROGRESS : diffusé lorsque le chargement est en cours. Celui-ci renseigne sur le nombre d?octets chargés et totaux. ? Event.COMPLETE : diffusé lorsque le chargement est terminé. ? HTTPStatusEvent.HTTP_STATUS : indique le code d?état de la requête HTTP. ? IOErrorEvent.IO_ERROR : diffusé lorsque le chargement échoue. ? SecurityErrorEvent.SECURITY_ERROR : diffusé lorsque le lecteur tente de charger des données depuis un domaine non autorisé. Tout en gérant les différentes erreurs pouvant survenir lors du chargement de données, nous allons commencer notre apprentissage en chargeant de simples données au format texte, puis XML. Puis nous traiterons en détail l?envoi et réception de variables mais aussi de fichiers grace aux classes flash.net.FileReference et flash.net.FileReferenceList. A retenir Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 3 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les fonctions et méthodes loadVariables, loadVariablesNum et la classe LoadVars sont remplacées par la classe URLLoader. ? La classe URLLoader permet de charger des données au format texte, XML et binaire. Charger du texte Afin de charger des données nous créons une instance de la classe URLLoader, puis nous utilisons la méthode load dont voici la signature : public function load(request:URLRequest):void Comme nous l?avons vu lors du précédent chapitre intitulé chargement de contenu, toute url doit être spécifiée au sein d?un objet flash.net.URLRequest. A l?aide d?un éditeur tel le bloc notes, nous créons un fichier texte nommé donnees.txt ayant le contenu suivant : Voici le contenu du fichier texte ! Dans un nouveau document Flash CS3, nous créons une instance de la classe URLLoader puis nous chargeons le fichier texte : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // chargement des données chargeurDonnees.load ( new URLRequest ("donnees.txt") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { trace("données chargées"); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 4 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } A l?instar de la classe flash.display.LoaderInfo la classe URLLoader diffuse deux événements, une fois les données chargées. L?événement HTTPStatsEvent.HTTP_STATUS puis l?événement Event.COMPLETE. Souvenez-vous qu?en local la propriété status de l?objet événementiel HTTPStatusEvent vaut toujours 0, même si le chargement échoue. Dans le code suivant, nous chargeons le même fichier texte depuis un serveur, la propriété status de l?objet événementiel renvoie 200 : // chargement des données // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // chargement des données chargeurDonnees.load ( new URLRequest ("http://www.monserveur.org/donnees.txt") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { trace("données chargées"); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 200 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 5 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } Si le lecteur ne parvient pas à charger le fichier distant, l?événement IoErrorEvent.IO_ERROR est diffusé ainsi que l?événement HTTPStatusEvent.HTTP_STATUS. Dans ce cas la propriété status contient le code d?erreur HTTP permettant de connaître la raison de l?echec. Pour un tableau récapitulatif des différents codes d?erreurs possibles reportez-vous au chapitre 13 intitulé Chargement de contenu. Afin d?accéder aux données chargées nous utilisons la propriété data de l?objet URLLoader : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // chargement des données chargeurDonnees.load ( new URLRequest ("donnees.txt") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // affiche : Voici le contenu du fichier texte ! trace( contenu ); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 6 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } La propriété dataFormat de l?objet URLLoader permet de définir quel format de données nous chargeons. Celle-ci a la valeur text par défaut : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // affiche : text trace( chargeurDonnees.dataFormat ); Il est recommandé pour des questions de lisibilité de code et de travail en équipe de toujours spécifier le type de données que nous chargeons même si il s?agit de données texte. Pour cela nous utilisons trois propriétés constantes de la classe flash.net.URLLoaderDataFormat. Voici le détail de chacune d?entre elles : ? URLLoaderDataFormat.BINARY : permet de charger des données au format binaire. ? URLLoaderDataFormat.TEXT : permet de charger du texte brut. ? URLLoaderDataFormat.VARIABLES : permet de charger des données url encodées. Ainsi, même si nous souhaitons charger du texte, nous préférons l?indiquer pour des questions de lisibilité : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // nous souhaitons charger des données texte chargeurDonnees.dataFormat = URLLoaderDataFormat.TEXT; // chargement des données chargeurDonnees.load ( new URLRequest ("donnees.txt") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // affiche : Voici le contenu du fichier texte ! trace( contenu ); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 7 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : text trace( pEvt.target.dataFormat ); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } Dans les précédentes versions d?ActionScript la classe LoadVars était utilisée afin de charger des données externes. Celle-ci possédait un événement onData nous permettant de récupérer les données chargées brutes, sans passer par une interprétation des données chargées. L?équivalent n?existe plus en ActionScript 3. Si nous souhaitons charger des données brutes, nous utilisons le format URLLoaderDataFormat.BINARY. Dans certains cas, le chargement de fichier texte peut être utile, en particulier lors de chargement de fichiers comme le CSV. Le CSV est un format simple de représentation de données sous forme de valeurs séparées par des virgules. Il est couramment utilisé en format d?export de logiciels comme Microsoft Excel ou Microsoft Outlook. Dans l?exemple suivant nous chargeons un fichier CSV exporté depuis Microsoft Excel contenant des statistiques. Voici un aperçu du contenu du fichier : 100 133.46 144.02 148 94.04 87.17 92.27 96.83 98.81 Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 8 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org 113.8 113.2 Dans le code suivant nous chargeons le fichier CSV en tant que données texte, puis nous transformons la chaîne en un tableau à l?aide de la méthode split de la classe String : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // nous souhaitons charger des données texte chargeurDonnees.dataFormat = URLLoaderDataFormat.TEXT; // chargement des données chargeurDonnees.load ( new URLRequest ("donnees.csv") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // transformation de la chaîne en un tableau en séparant les données à chaque saut de ligne var tableauDonnees:Array = contenu.split("\n"); // affiche : 100 trace( tableauDonnees[0] ); // affiche : 94 trace( tableauDonnees.length ); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 9 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Nous calculons l?amplitude du graphique en extrayant les valeurs minimum et maximum : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // nous souhaitons charger des données texte chargeurDonnees.dataFormat = URLLoaderDataFormat.TEXT; // chargement des données chargeurDonnees.load ( new URLRequest ("donnees.csv") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); var hauteurMaximum:Number = 180; function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // transformation de la chaîne en un tableau en séparant les données à chaque saut de ligne var tableauDonnees:Array = contenu.split("\n"); // extraction des valeurs minimum et maximum var valeurMinimum:Number = calculeMinimum ( tableauDonnees ); var valeurMaximum:Number = calculeMaximum ( tableauDonnees ); // calcul de l'amplitude du graphique var ratio:Number = hauteurMaximum / ( valeurMaximum - valeurMinimum ); // affiche : 1.2699308593198815 trace( ratio ); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 10 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org function calculeMaximum ( pTableau:Array ):Number { var lng:int = pTableau.length; var valeurMaximum:Number = Number.MIN_VALUE; while ( lng-- ) valeurMaximum = Math.max ( valeurMaximum, pTableau[lng] ); return valeurMaximum; } function calculeMinimum ( pTableau:Array ):Number { var lng:int = pTableau.length; var valeurMinimum:Number = Number.MAX_VALUE; while ( lng-- ) valeurMinimum = Math.min ( valeurMinimum, pTableau[lng] ); return valeurMinimum; } Puis nous dessinons le graphique à l?aide de la fonction dessineGaphique : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // nous souhaitons charger des données texte chargeurDonnees.dataFormat = URLLoaderDataFormat.TEXT; // chargement des données chargeurDonnees.load ( new URLRequest ("donnees.csv") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); var hauteurMaximum:Number = 180; function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // affiche : Voici le contenu du fichier texte ! var tableauDonnees:Array = contenu.split("\n"); // extraction des valeurs minimum et maximum var valeurMinimum:Number = calculeMinimum ( tableauDonnees ); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 11 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org var valeurMaximum:Number = calculeMaximum ( tableauDonnees ); // calcul de l'amplitude du graphique var ratio:Number = hauteurMaximum / ( valeurMaximum - valeurMinimum ); // dessine le graphique dessineGraphique ( ratio, valeurMaximum, tableauDonnees ); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } function calculeMaximum ( pTableau:Array ):Number { var lng:int = pTableau.length; var valeurMaximum:Number = Number.MIN_VALUE; while ( lng-- ) valeurMaximum = Math.max ( valeurMaximum, pTableau[lng] ); return valeurMaximum; } function calculeMinimum ( pTableau:Array ):Number { var lng:int = pTableau.length; var valeurMinimum:Number = Number.MAX_VALUE; while ( lng-- ) valeurMinimum = Math.min ( valeurMinimum, pTableau[lng] ); return valeurMinimum; } var courbe:Shape = new Shape(); courbe.graphics.lineStyle ( 1, 0x990000, 1 ); var conteneurGraphique:Sprite = new Sprite(); conteneurGraphique.addChild( courbe ); conteneurGraphique.opaqueBackground = 0xFCEEBC; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 12 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org addChild ( conteneurGraphique ); function dessineGraphique ( pRatio:Number, pMaximum:Number, pDonnees:Array ):void { for ( var i:int = 0; i< pDonnees.length; i++ ) { if ( i == 0 ) courbe.graphics.moveTo ( i, (pMaximum-pDonnees[0]) * pRatio ); else courbe.graphics.lineTo ( i * 4, (pMaximum-pDonnees[i]) * pRatio ); } // centre le graphique conteneurGraphique.x = ( stage.stageWidth - conteneurGraphique.width ) / 2; conteneurGraphique.y = ( stage.stageHeight - conteneurGraphique.height ) / 2; } La figure 14-1 illustre le graphique généré depuis les données extraites du fichier CSV : Figure 14-1. Courbe de statistique. Si nous modifions la valeur de la variable hauteurMaximum : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 13 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org var hauteurMaximum:Number = 80; Nous voyons que le graphique est dessiné en conservant les proportions, comme l?illustre la figure 14-2 : Figure 14-2. Courbe de statistique réduite. Bien entendu, d?autres formats de données peuvent être utilisés. Nous allons nous intéresser au chargement de variables encodées au format URL. A retenir ? Afin de charger des données au format texte, nous passons la valeur URLLoaderDataFormat.TEXT à la propriété dataFormat. ? L?objet URLLoader ne possède pas d?objet LoaderInfo interne. ? Afin d?accéder aux données chargées, nous utilisons la propriété data de l?objet URLLoader. L?encodage URL L'encodage URL est le standard de transmition d'informations par formulaire. Il permet de rendre compatible différents paramètres avec le protocole HTTP. L?encodage respecte les règles suivantes : ? Les champs sont séparés par des esperluettes (caractère &) Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 14 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ? Un champ comporte un nom de champ, suivi de = puis de la valeur. ? Les espaces sont remplacés par des +. ? Les caractères non alphanumériques sont remplacés par %XX où XX représente le code ASCII en hexadécimal du caractère. L?encodage URL va être utilisé au cours des prochaines parties afin de charger ou d?envoyer des variables. Charger des variables Le chargement de données texte brut convient lorsque nous chargeons des données non structurées. Pour des données plus détaillées nous pouvons utiliser le format d?encodage URL détaillé précédemment. La chaîne suivante est une chaîne encodée URL : titre=Voici un titre&contenu=Voici le contenu ! Nous allons placer la chaîne précédente au sein d?un fichier texte nommé donnees_url.txt. Au sein d?un nouveau document Flash CS3, nous chargeons le fichier texte à l?aide de la méthode load : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // nous souhaitons charger des données texte chargeurDonnees.dataFormat = URLLoaderDataFormat.TEXT; // chargement des données chargeurDonnees.load ( new URLRequest ("donnees_url.txt") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // affiche : titre=Voici un titre&contenu=Voici le contenu ! trace( contenu ); } function codeHTTP ( pEvt:HTTPStatusEvent ):void { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 15 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } Les données sont ainsi chargées en tant que données texte brut. Il nous est difficile d?accéder pour le moment à la valeur de chaque variable titre ou contenu. Afin d?interpréter et d?extraire des données de la chaîne encodée, deux possibilités s?offrent à nous : La première consiste à décoder les données texte à l?aide de la classe flash.net.URLVariables. En passant la chaîne encodée URL au constructeur de celle-ci nous la décodons afin d?extraire la valeur de chaque variable : function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var contenu:String = pEvt.target.data; // décodage de la chaîne encodée url sous forme d'objet var variables:URLVariables = new URLVariables ( contenu ); // itération sur chaque variable décodée /* affiche : titre --> Voici un titre contenu --> Voici le contenu ! */ for ( var p in variables ) trace( p, "--> " + variables[p] ); } Une fois l?objet URLVariables créé, nous accédons à chaque variable par la syntaxe pointée. Nous retrouvons ici le même comportement que la classe LoadVars des précédentes versions d?ActionScript. Les variables chargées deviennent des propriétés de l?objet URLVariables. Dans le code suivant, nous accédons manuellement aux deux variables titre et contenu devenues propriétés : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 16 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var donnees:String = pEvt.target.data; // décodage de la chaîne url encodée sous forme d'objet var variables:URLVariables = new URLVariables ( donnees ); var titre:String = variables.titre; // affiche : Voici un titre trace( titre ); var contenu:String = variables.contenu; // affiche : Voici le contenu ! trace( contenu ); } Au sein de notre document, nous plaçons deux champs texte dynamique auxquels nous associons deux noms d?occurrence. Puis nous remplissons dynamiquement ces derniers avec le contenu chargé correspondant : function chargementTermine ( pEvt:Event ):void { // accès aux données chargées var donnees:String = pEvt.target.data; // décodage de la chaîne url encodée sous forme d'objet var variables:URLVariables = new URLVariables ( donnees ); var titre:String = variables.titre; var contenu:String = variables.contenu; // affectation des données chargées au champs texte champTitre.text = titre; champContenu.text = contenu; } La figure 14-3 illustre le résultat : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 17 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-3. Contenu texte chargé dynamiquement. Une seconde approche plus rapide consiste à spécifier au préalable à la propriété dataFormat la valeur URLLoaderDataFormat.VARIABLES. Ainsi, les données chargées sont automatiquement décodées par un objet URLVariables : // création de l'objet URLLoader var chargeurDonnees:URLLoader = new URLLoader(); // nous souhaitons charger des données url encodées chargeurDonnees.dataFormat = URLLoaderDataFormat.VARIABLES; // chargement des données chargeurDonnees.load ( new URLRequest ("donnees_url.txt") ); // écoute de l'événement Event.COMPLETE chargeurDonnees.addEventListener( Event.COMPLETE, chargementTermine ); // écoute de l'événement HTTPStatusEvent.HTTP_STATUS chargeurDonnees.addEventListener( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); // écoute de l'événement IOErrorEvent.IO_ERROR chargeurDonnees.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementTermine ( pEvt:Event ):void { // accès à l?objet URLVariables var variables:URLVariables = pEvt.target.data; var titre:String = variables.titre; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 18 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org var contenu:String = variables.contenu; // affectation des données chargées au champs texte champTitre.text = titre; champContenu.text = contenu; } function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } Lorsque nous demandons explicitement de charger les données sous la forme de variables encodées URL, la propriété data de l?objet URLLoader retourne un objet URLVariables créé automatiquement par le lecteur Flash. La classe URLVariables définie une méthode decode appelée en interne lors de l?interprétation de la chaîne encodée. Celle-ci peut aussi être appelée manuellement lorsque l?objet URLVariables est déjà créé et que nous souhaitons décoder une nouvelle chaîne. Au cas où les variables chargées ne seraient pas formatées correctement, l?appel de celle-ci lève une erreur à l?exécution. Dans le cas de la chaîne suivante : Voici un titre&contenu=Voici le contenu ! Nous avons omis volontairement la première variable titre associée au contenu Voici un titre. Si nous testons à nouveau le code précédent, l?erreur suivante est levée lors de l?événement Event.COMPLETE : Error: Error #2101: La chaîne transmise à URLVariables.decode() doit être une requête au format de code URL contenant des paires nom/valeur. Si le texte chargé contient le caractère & et que nous ne souhaitons pas l?interpréter comme séparateur mais comme caractère composant une chaîne, nous pouvons indiquer son code caractère ASCII au format héxadécimal à l?aide du symbole %. Ainsi, pour ajouter comme titre la chaîne de caractère suivante : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 19 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Bobby & The Groove Orchestra Nous remplaçons le caractère & par son code caractère ASCII de la manière suivante : titre=Bob %26 The Groove Orchestra&contenu=Sortie de l?album en Août 2008 ! La figure 14-4 illustre le résultat : Figure 14-4. Caractère spécial encodé en hexadécimal. Malheureusement, pour des questions de lisibilité et d?organisation, le format encodé URL s?avère rapidement limité. Nous préférerons l?utilisation du format XML qui s?avère être un format standard et universel adapté à la représentation de données complexes. A retenir Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 20 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ? Afin de décoder une chaîne encodée URL nous utilisons un objet URLVariables. ? Afin de charger des variables encodées URL, nous passons la valeur URLLoaderDataFormat.VARIABLES à la propriété dataFormat. ? Afin de décoder une chaine de caractères encodée URL nous pouvons la passer au constructeur de la classe URLVariables où à la méthode decode. Charger des données XML Si nous devions représenter une petite partie de la discographie de Stevie Wonder au format encodé URL nous obtiendrons la chaîne suivante : &album_1=Talking Book&artiste_1=Stevie Wonder&label_1=Motown&annee_1=1972&album_2=Songs in the key of life&artiste_2=Stevie Wonder&label_2=Motown&annee_2=1976 Les mêmes données au format XML seraient formatées de la manière suivante : <DISCOGRAPHIE> <ALBUM> <TITRE> Talking Book </TITRE> <ANNEE DATE="1972"/> <ARTISTE NOM="Wonder" PRENOM="Stevie"/> <LABEL NOM="Motown"/> </ALBUM > <ALBUM> <TITRE> Songs in the key of life </TITRE> <ANNEE DATE="1976"/> <ARTISTE NOM="Wonder" PRENOM="Stevie"/> <LABEL NOM="Motown"/> </ALBUM > </DISCOGRAPHIE> La représentation XML est plus naturelle et plus adaptée dans le cas de données structurées. Nous avons manipulé le format XML au cours du chapitre 2 intitulé Langage et API du lecteur Flash. Nous allons découvrir comment charger dynamiquement un fichier XML afin de construire une interface graphique. En ActionScript 3, la classe XML ne fait plus partie de l?API du lecteur Flash, mais appartient au langage ActionScript 3 reposant sur ECMAScript, c?est donc une classe haut niveau. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 21 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org La classe XML ne gère donc plus le chargement de données comme c?était le cas dans les précédentes versions d?ActionScript. Afin de charger des données XML, nous chargeons simplement une chaîne de caractères sous la forme de texte brut, puis nous la transformons en objet XML. Dans un nouveau document Flash CS3, nous associons la classe de document suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { } } } A côté du document Flash en cours nous sauvons un fichier XML sous le nom donnees.xml contenant les données suivantes : <MENU> <BOUTON legende="Accueil" couleur="0x887400" vitesse="1" swf="accueil.swf"/> <BOUTON legende="Photos" couleur="0x005587" vitesse="1" url="photos.swf"/> <BOUTON legende="Blog" couleur="0x125874" vitesse="1" url="blog.swf"/> <BOUTON legende="Liens" couleur="0x59CCAA" vitesse="1" url="liens.swf"/> <BOUTON legende="Forum" couleur="0xEE44AA" vitesse="1" url="forum.swf"/> </MENU> Puis nous le chargeons : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.events.Event; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 22 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private var chargeur:URLLoader; public function Document () { chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.TEXT; chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.load ( new URLRequest ("donnees.xml") ); } private function chargementTermine ( pEvt:Event ) :void { var donnneesXML:XML = new XML ( pEvt.target.data ); /* affiche : <MENU> <BOUTON legende="Accueil" couleur="0x887400" vitesse="1" url="accueil.swf"/> <BOUTON legende="Photos" couleur="0x005587" vitesse="1" url="photos.swf"/> <BOUTON legende="Blog" couleur="0x125874" vitesse="1" url="blog.swf"/> <BOUTON legende="Liens" couleur="0x59CCAA" vitesse="1" url="liens.swf"/> <BOUTON legende="Forum" couleur="0xEE44AA" vitesse="1" url="forum.swf"/> </MENU> */ trace( donnneesXML ); } private function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } private function erreurChargement ( pEvent:IOErrorEvent ):void { trace("erreur de chargement"); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 23 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } } Lorsque la méthode écouteur chargementTermine se déclenche nous accédons aux données chargées puis nous transformons la chaîne de caractères en objet XML. Nous allons reprendre le menu construit lors du chapitre 10 intitulé Diffusion d?événements personnalisés afin de charger dynamiquement les données du menu depuis un fichier XML. Afin de créer notre menu, nous reprenons la classe Bouton utilisée lors du chapitre 10 puis nous l?importons ainsi que la classe Sprite : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.ui.Bouton; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.events.Event; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.display.Sprite; public class Document extends ApplicationDefaut { private var chargeur:URLLoader; private var conteneurMenu:Sprite; public function Document () { conteneurMenu = new Sprite(); addChild ( conteneurMenu ); chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.TEXT; chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.load ( new URLRequest ("donnees.xml") ); } private function chargementTermine ( pEvt:Event ) :void Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 24 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { var donnneesXML:XML = new XML ( pEvt.target.data ); creerMenu ( donnneesXML ); } private function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } private function erreurChargement ( pEvent:IOErrorEvent ):void { trace("erreur de chargement"); } private function creerMenu ( pXML:XML ):void { var i:int = 0; var monBouton:Bouton; for each ( var enfant:XML in pXML.* ) { // récupération des infos var legende:String = enfant.@legende; var couleur:Number = enfant.@couleur; var vitesse:Number = enfant.@vitesse; var swf:String = enfant.@url; // création des boutons monBouton = new Bouton( 60, 40, swf, couleur, vitesse, legende ); // positionnement monBouton.y = (monBouton.height + 5) * i; // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); i++; } } } } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 25 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 14-4 illustre le résultat : Figure 14-4. Menu dynamique XML. Afin d?écouter chaque clic bouton, nous importons la classe EvenementBouton créée lors du chapitre 10 puis nous ciblons l?événement EvenementBouton.CLICK en utilisant la phase de capture : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.ui.Bouton; import org.bytearray.evenements.EvenementBouton; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.events.Event; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.display.Sprite; public class Document extends ApplicationDefaut { private var chargeur:URLLoader; private var conteneurMenu:Sprite; public function Document () Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 26 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { conteneurMenu = new Sprite(); addChild ( conteneurMenu ); conteneurMenu.addEventListener ( EvenementBouton.CLICK, clicBouton, true ); chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.TEXT; chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.load ( new URLRequest ("donnees.xml") ); } private function chargementTermine ( pEvt:Event ) :void { var donnneesXML:XML = new XML ( pEvt.target.data ); creerMenu ( donnneesXML ); } private function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } private function erreurChargement ( pEvent:IOErrorEvent ):void { trace("erreur de chargement"); } private function creerMenu ( pXML:XML ):void { var i:int = 0; var monBouton:Bouton; for each ( var enfant:XML in pXML.* ) { // récupération des infos Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 27 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org var legende:String = enfant.@legende; var couleur:Number = enfant.@couleur; var vitesse:Number = enfant.@vitesse; var swf:String = enfant.@url; // création des boutons monBouton = new Bouton( 60, 40, swf, couleur, vitesse, legende ); // positionnement monBouton.y = (monBouton.height + 5) * i; // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); i++; } } private function clicBouton( pEvt:EvenementBouton ):void { // affiche : photos.swf trace( pEvt.lien ); } } } Nous avons ici réutilisé la classe Bouton créée lors du chapitre 10, en prévoyant un modèle simple d?utilisation nous avons pu réutiliser cette classe sans aucun problème. Afin de désactiver totalement notre menu, nous devons supprimer l?objet conteneurMenu de la liste d?affichage, puis passer sa référence à null : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.ui.Button; import org.bytearray.events.ButtonEvent; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.events.Event; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.events.MouseEvent; import flash.display.Sprite; public class Document extends ApplicationDefaut Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 28 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { private var chargeur:URLLoader; private var conteneurMenu:Sprite; public function Document () { conteneurMenu = new Sprite(); addChild ( conteneurMenu ); stage.doubleClickEnabled = true; stage.addEventListener ( MouseEvent.DOUBLE_CLICK, desactive ); conteneurMenu.addEventListener ( ButtonEvent.CLICK, clicBouton, true ); chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.TEXT; chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.load ( new URLRequest ("donnees.xml") ); } private function chargementTermine ( pEvt:Event ) :void { var donnneesXML:XML = new XML ( pEvt.target.data ); creerMenu ( donnneesXML ); } private function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } private function erreurChargement ( pEvent:IOErrorEvent ):void { trace("erreur de chargement"); } private function creerMenu ( pXML:XML ):void Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 29 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { var i:int = 0; var monBouton:Button; for each ( var enfant:XML in pXML.* ) { // récupération des infos var legende:String = enfant.@legende; var couleur:Number = enfant.@couleur; var vitesse:Number = enfant.@vitesse; var swf:String = enfant.@url; // création des boutons monBouton = new Button( 60, 40, swf, couleur, vitesse, legende ); // positionnement monBouton.y = (monBouton.height + 5) * i; // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); i++; } } private function clicBouton( pEvt:ButtonEvent ):void { // affiche : photos.swf trace( pEvt.lien ); } private function desactive ( pEvt:MouseEvent ):void { removeChild ( conteneurMenu ); conteneurMenu = null; } } } Souvenez-vous que pour correctement désactiver un élément interactif, nous devons supprimer toutes ses références. Dans cet exemple, les boutons sont seulement référencés de par leur présence au sein de l?objet graphique conteneurMenu. En désactivant ce dernier nous rendons alors inaccessible ses enfants. Ces Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 30 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org derniers deviennent ainsi éligible à la suppression par le ramasse miettes. Nous avons vu lors du chapitre précédent que le modèle de securité du lecteur intégrait des restrictions conçernant le chargement de contenu externe. Nous allons au cours de la partie suivante nous intéresser aux restrictions de securité dans un contexte de chargement de données. A retenir ? Afin de charger un flux XML, nous passons la valeur URLLoaderDataFormat.TEXT à la propriété dataFormat de l?objet URLLoader. ? La classe XML ne s?occupe plus du chargement du flux XML. ? Une fois la chaîne de caractères chargée par l?objet URLLoader, nous créons un objet XML à partir de celle-ci. Chargement de données et securité Le chargement de données est soumis aux mêmes restrictions de sécurité que le chargement de contenu. Imaginons le scénario suivant : Vous devez développer une application Flash permettant de lire des flux XML provenant de différents blogs. Cette application sera hebergée sur votre serveur et ne peut pour des raisons de securité accéder aux flux distants. Au cours du chapitre 13, nous avons vu qu?il était possible d?autoriser de trois manières un SWF tentant de charger du contenu depuis un serveur distant : ? Par un fichier de régulation (XML). ? Par l?appel de la méthode allowDomain de la classe flash.system.Security dans le SWF à charger. ? Par l?utilisation d?un fichier de proxy. Attention, dans notre cas, nous ne chargeons plus de contenu SWF. Il est donc impossible d?appeler la méthode allowDomain de l?objet Security. Ainsi, dans le cas de chargement de flux XML, texte, ou autres seules deux méthodes d?autorisations s?offrent à vous : Par un fichier de régulation XML comme l?illustre la figure 14-5 : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 31 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-5. Autorisation par fichier de régulation. Ou bien par l?utilisation d?un fichier proxy : Figure 14-6. Utilisation d?un proxy. Reportez vous au chapitre 13 intitulé chargement de contenu pour plus d?informations relatives au modèle de sécurité du lecteur Flash 9 et l?utilisation de fichier de régulation ou proxy. A retenir Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 32 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les mêmes restrictions de securité liées au chargement de contenu, s?appliquent lors du chargement de données. ? Il est impossible de placer au sein des données chargées, un appel à la méthode allowDomain de l?objet Security. Charger des données binaires La grande puissance du lecteur Flash 9 en ActionScript 3 réside dans la lecture de données au format binaire grâce à la classe bas niveau flash.utils.ByteArray. Afin de charger des données binaires brutes, nous devons passer à la propriété dataFormat la valeur URLLoaderDataFormat.BINARY. Dans un nouveau document Flash CS3, nous chargeons un fichier PSD en associant une classe du document contenant le code suivant : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.URLRequest; import flash.utils.ByteArray; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.events.Event; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var chargeur:URLLoader; public function Document () { chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.BINARY; chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.load ( new URLRequest ("maquette.psd") ); } private function chargementTermine ( pEvt:Event ) :void { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 33 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org var donneesBinaire:ByteArray = pEvt.target.data; // affiche : 182509 trace( donneesBinaire.length ); } private function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } private function erreurChargement ( pEvent:IOErrorEvent ):void { trace("erreur de chargement"); } } } La variable donneesBinaire contient le flux binaire du fichier PSD chargé. En créant une classe EntetePSD, nous allons lire l?entête du fichier afin d?extraire différentes informations comme la taille du document, la version, ainsi que le modèle de couleur utilisé. Pour cela, nous créons une classe EntetePSD contenant le code suivant : package org.bytearray.psd { import flash.utils.ByteArray; public class EntetePSD { private var flux:ByteArray; private var _signature:String; private var _version:int; private var _canal:int; private var _hauteur:int; private var _largeur:int; private var _profondeur:int; private var _mode:int; private static const MODES_COULEURS:Array = new Array ("Bitmap", "Mode niveaux de gris", "Indexé", "RVB", "CMJN", "Multi Canal", "Deux tons", "Lab"); public function EntetePSD ( pFlux:ByteArray ) Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 34 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { flux = pFlux; // extrait la signature de l'entête PSD (doit être 8BPS) _signature = flux.readUTFBytes(4); // extrait la version (doit êter égal à 1) _version = flux.readUnsignedShort(); // nous sautons 6 octets flux.position += 6; // extrait le canal utilisé _canal = flux.readUnsignedShort(); // extrait la largeur du document _largeur = flux.readInt(); // extrait la hauteur du document _hauteur = flux.readInt(); // bpp _profondeur = flux.readUnsignedShort(); // mode colorimétrique (Bitmap=0, Mode niveaux de gris=1, Indexé=2, RVB=3, CMJN=4, Multi Canal=7, Deux tons=8, Lab=9) _mode = flux.readUnsignedShort(); } public function toString ( ):String { return "[EntetePSD signature : " + signature +", version : " + version + ", canal : " + canal + ", largeur : " + largeur + ", hauteur : " + hauteur + ", profondeur : " + profondeur + ", mode colorimétrique : " + MODES_COULEURS [ mode ] +"]"; } public function get signature ():String { return _signature; } public function get version ():int { return _version; } public function get canal ():int { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 35 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org return _canal; } public function get largeur ():int { return _largeur; } public function get hauteur ():int { return _hauteur; } public function get profondeur ():int { return _profondeur; } public function get mode ():int { return _mode; } } } Le flux binaire généré par le lecteur Flash est passé au constructeur, puis nous lisons le flux à l?aide des méthodes de la classe ByteArray. Nous reviendrons sur celles-ci au cours du chapitre 20 intitulé ByteArray. Afin d?extraire les informations du PSD nous instancions la classe EntetePSD en passant le flux binaire au constructeur : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.psd.EntetePSD; import flash.net.URLRequest; import flash.utils.ByteArray; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.events.Event; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 36 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var chargeur:URLLoader; public function Document () { chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.BINARY; chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, codeHTTP ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.load ( new URLRequest ("maquette.psd") ); } private function chargementTermine ( pEvt:Event ) :void { var donneesBinaire:ByteArray = pEvt.target.data; var infosPSD:EntetePSD = new EntetePSD( donneesBinaire ); // affiche : [EntetePSD signature : 8BPS, version : 1, canal : 3, largeur : 450, hauteur : 562, profondeur : 8, mode colorimétrique : RVB] trace( infosPSD ); } private function codeHTTP ( pEvt:HTTPStatusEvent ):void { // affiche : 0 trace("code HTTP : " + pEvt.status); } private function erreurChargement ( pEvent:IOErrorEvent ):void { trace("erreur de chargement"); } } } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 37 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Grâce à la classe ByteArray nous pouvons charger n?importe quel type de fichiers puis en extraire des informations. Nous pourrions imaginer une application RIA permettant d?héberger tout type de fichier. Celle-ci pourrait extraire les informations provenant de fichiers PSD, AI, FLA ou autres. Nous pourrions optimiser la classe EntetePSD en diffusant un événement personnalisé EvenementEntetePSD.INFOS. L?objet événementiel diffusé contiendrait toutes les propriétés nécessaires à la description du fichier PSD. La classe ByteArray ouvre des portes à toutes sortes de possibilités. Nous reviendrons en détail sur la puissance de cette classe au cours du chapitre 20 intitulé ByteArray. A retenir ? Afin de charger un flux binaire, nous passons la valeur URLLoaderDataFormat.BINARY à la propriété dataFormat de l?objet URLLoader. ? Le flux binaire généré est representé par la classe flash.utils.ByteArray. Concept d?envoi de données Comme nous l?avons vu lors du chapitre 13 intitulé Chargement de contenu, toute URL doit être spécifiée au sein d?un objet URLRequest. Celui-ci offre pourtant bien d?autres fonctionnalités que nous n?avons pas encore exploitées. Nous allons nous intéresser au cours des prochaines parties au concept d?envoi de données. Pour cela, voyons en détail les propriétés de la classe URLRequest : ? contentType : type de contenu MIME des données envoyées en POST. ? data : contient les données à envoyer. Peut être de type String, URLVariables ou ByteArray. ? method : permet d?indiquer si les données doivent être envoyées par la méthode GET ou POST. ? requestHeaders : tableau contenant les entêtes HTTP définies par des objets flash.net.URLRequestHeader. ? url : contient l?url à atteindre. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 38 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Nous allons au cours des exercices suivants utiliser ces différentes propriétés afin de découvrir leurs intérêts. Au sein du lecteur Flash nous pouvons distinguer trois types d?envoi de données : ? Envoi simple : les variables sont envoyées à l?aide d?une nouvelle fenêtre navigateur. ? Envoi discret : l?envoi des données est transparent pour l?utilisateur. Aucune fenêtre navigateur n?est ouverte durant l?envoi. ? Envoi discret avec validation : l?envoi des données est transparent, le lecteur Flash reçoit un retour du serveur permettant une validation d?envoi des données au sein de l?application Flash. Nous allons traiter chacun des cas et comprendre les différences entre chaque approche. Envoyer des variables Nous allons commencer par un envoi simple de données en développant un formulaire permettant d?envoyer un email depuis Flash. Ce type de développement peut être intégré dans une rubrique « Contactez-nous » au sein d?un site. Nous verrons qu?il est possible sous certaines conditions d?envoyer un email depuis le lecteur Flash sans passer par un script serveur grâce à la classe flash.net.Socket que nous traiterons au cours du chapitre 19 intitulé Les sockets. Par défaut, le lecteur Flash n?a pas la capacité d?envoyer un email de manière autonome. Afin d?y parvenir, nous devons passer les informations nécessaires à un script serveur afin que celui-ci puisse envoyer le message. Dans un nouveau document Flash CS3, nous associons la classe du document suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 39 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } } Puis nous plaçons trois champs texte de saisie et un bouton afin de créer une interface d?envoi de mail. La figure 14-7 illustre l?interface : Figure 14-7. Formulaire d?envoi d?email. Chaque instance est définie au sein de la classe du document : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.display.SimpleButton; import flash.text.TextField; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; public function Document () Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 40 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { } } } Nous allons créer à présent le code PHP nous permettant de réceptionner les variables transmises depuis Flash. Nous allons utiliser dans un premier temps la méthode GET. Les variables sont ainsi accessible par l?intermédiaire du tableau associatif $_GET : <?php $destinataire = $_GET ["destinataire"]; $sujet = $_GET ["sujet"]; $message = $_GET ["message"]; if ( isset ( $destinataire ) && isset ( $sujet ) && isset ( $message ) ) { echo $destinataire. "<br>". $sujet. "<br>" . $message; } else echo "Variables non transmises"; ?> Il convient de placer ce script sur un serveur local ou distant afin de pouvoir tester l?application. Dans notre cas, le script serveur est placé sur un serveur local. Le script est donc accessible en localhost : http://localhost/mail/envoiMail.php Nous ajoutons à présent le code nécessaire afin d?envoyer les variables à notre script distant : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.display.SimpleButton; import flash.text.TextField; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.events.MouseEvent; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 41 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org public function Document () { boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } private function envoiMail ( pEvt:MouseEvent ):void { // affectation des variables à envoyer coté serveur var destinaireEmail:String = destinataire.text; var sujetEmail:String = sujet.text; var messageEmail:String = message.text; // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // ouvre une nouvelle fenêtre navigateur et envoi les variables navigateToURL ( requete ); } } } Nous stockons le destinataire, le sujet ainsi que le message au sein de trois variables. Puis nous les ajoutons en fin d?url du script distant de la manière suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.display.SimpleButton; import flash.text.TextField; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.events.MouseEvent; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; public function Document () { boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 42 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private function envoiMail ( pEvt:MouseEvent ):void { // affectation des variables à envoyer coté serveur var destinaireEmail:String = destinataire.text; var sujetEmail:String = sujet.text; var messageEmail:String = message.text; var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php?destinataire="+destinaireEmail+"&sujet= "+sujetEmail+"&message="+messageEmail); // ouvre une nouvelle fenêtre navigateur et envoi les variables navigateToURL ( requete ); } } } Le point d?interrogation au sein de l?URL indique au navigateur que le texte suivant contient les variables représentées par des paires noms/valeurs séparées par des esperluettes (caractère &). Une fois les informations saisies comme l?illustre la figure 14-8 : Figure 14-8. Formulaire d?envoi d?email. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 43 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Nous cliquons sur le bouton boutonEnvoi, une nouvelle fenêtre navigateur s?ouvre, les variables sont automatiquement placées en fin d?url : http://localhost/mail/envoiMail.php?destinataire=thibault@bytearray.org&sujet =Infos%20Album%202008%20!&message=Quand%20pr%C3%A9voyez%20vous%20le%20retour% 20de%20Peter%20mc%20Bryan%20%C3%A0%20la%20basse%20? Le script serveur récupère chaque variable et affiche son contenu comme l?illustre la figure 14-9 : Figure 14-9. Variables récupérées. Nous remarquons que les caractères spéciaux ne s?affichent pas correctement. Cela est du au fait que le lecteur Flash fonctionne en UTF-8, tandis que PHP fonctionne par défaut en ISO-8859-1. Nous devons donc décoder la chaîne UTF-8 à l?aide de la méthode PHP utf8_decode : <?php $destinataire = $_GET["destinataire"]; $sujet = $_GET["sujet"]; $message = $_GET["message"]; if ( isset ( $destinataire ) && isset ( $sujet ) && isset ( $message ) ) { echo utf8_decode($destinataire)."<br>".utf8_decode($sujet)."<br>".utf8_decode($mes sage); } else echo "Variables non transmises"; ?> Si nous testons à nouveau le code précédent, les chaînes sont correctement décodées : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 44 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-10. Variables correctement décodées. Une fois assuré que les variables passées sont bien réceptionnées, nous pouvons ajouter le code nécessaire pour envoyer l?email. Pour cela, nous ajoutons l?appel à la fonction PHP mail en passant le destinataire, le sujet ainsi que le contenu du message : <?php $destinataire = $_GET["destinataire"]; $sujet = $_GET["sujet"]; $message = $_GET["message"]; if ( isset ( $destinataire ) && isset ( $sujet ) && isset ( $message ) ) { if ( @mail ( utf8_decode($destinataire), utf8_decode($sujet), utf8_decode($message) ) ) echo "Mail bien envoyé !"; else echo "Erreur d'envoi !"; } else echo "Variables non transmises"; ?> Nous ajoutons le caractère @ devant la fonction mail afin d?éviter que celle-ci lève une exception PHP en cas de mauvaise configuration du serveur SMTP. En testant notre application formulaire Flash, nous voyons que le mail est bien envoyé si les variables sont correctement transmises : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 45 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-11. Email envoyé. Même si le code ActionScript précédent fonctionne, il ne fait pas usage de la classe URLVariables recommandée dans un contexte d?envoi de variables. Grâce à celle-ci nous allons pouvoir choisir quelle méthode utiliser afin d?envoyer les variables. Mais avant d?aller plus loin, qu?entendons nous par méthode d?envoi ? A retenir ? Nous pouvons envoyer des variables à un script serveur en les ajoutant en fin d?URL. ? Le format d?encodage URL doit alors être respecté. La méthode GET ou POST Lorsque des données sont passées à un script serveur. Celles-ci peuvent être transmises de deux manières différentes. Par l?intermédiaire du tableau GET ou POST. En utilisant la méthode GET, les variables sont obligatoirement ajoutées en fin d?url. Dans le cas d?un site de vente en ligne, l?adresse suivante permet d?accéder à un article spécifique : http://www.funkrecords.com/index.php?rubrique=soul&langue=fr&article=Breakwater Un script serveur récupère les variables passées en fin d?URL afin d?afficher le disque correspondant. Lorsque la méthode GET est utilisée, les variables en fin d?URL sont automatiquement accessible en PHP au sein du tableau associatif $_GET. Il est important de noter que la méthode GET possède une limitation de 1024 caractères, il n?est donc possible de passer un grand volume de données par cette méthode. Au contraire, lorsque les données sont transmises par la méthode POST, les variables n?apparaissent pas dans l?URL du navigateur. Ainsi, cette méthode ne souffre pas de la limitation de caractères. Le W3C définit à l?adresse suivante quelques règles concernant l?utilisation des deux méthodes : http://www.w3.org/2001/tag/doc/whenToUseGet.html Il est conseillé d?utiliser la méthode GET lorsque les données transmises ne sont pas sensibles ou ne sont pas liées à un processus d?écriture. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 46 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org A l?inverse, la méthode POST est préférée lorsque les données transmises n?ont pas à être exposées et qu?une écriture en base de données intervient par la suite. L?utilisation d?un objet URLVariables va nous permettre de spécifier la méthode à utiliser dans nos échanges entre le lecteur Flash et le script serveur. Au lieu de passer les variables directement après l?url du script distant : var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php?destinataire="+destinaireEmail+"&sujet= "+sujetEmail+"&message="+messageEmail); Nous allons préférer l?utilisation d?un objet URLVariables qui permet de stocker les variables à transmettre. Cet objet est ensuite associé à la propriété data de l?objet URLRequest utilisé. Nous pouvons spécifier la méthode à utiliser grâce à la propriété method de la classe URLRequest et aux constantes de la classe flash.net.URLRequestMethod. Nous modifions le code précédent en utilisant un objet URLVariables tout en conservant l?envoi des données avec la méthode GET : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.display.SimpleButton; import flash.text.TextField; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLRequestMethod; import flash.events.MouseEvent; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; public function Document () { boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } private function envoiMail ( pEvt:MouseEvent ):void Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 47 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { // création d?un objet URLVariables var variables:URLVariables = new URLVariables(); // affectation des variables à envoyer coté serveur variables.sujet = sujet.text; variables.destinataire = destinataire.text; variables.message = message.text; // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // nous passons les variables dans l'url (tableau GET) requete.method = URLRequestMethod.GET; // nous associons les variables à l'objet URLRequest requete.data = variables; // ouvre une nouvelle fenêtre navigateur et envoi les variables navigateToURL ( requete ); } } } Si nous testons à nouveau notre application Flash. Nous remarquons que le script serveur reçoit de la même manière les informations, le mail est bien envoyé. Afin d?utiliser la méthode POST, nous passons la valeur URLRequestMethod.POST à la propriété method de l?objet URLRequest : // nous passons les variables dans l'url (tableau POST) requete.method = URLRequestMethod.POST; Désormais les données seront envoyées au sein du tableau associatif $_POST. Nous devons donc impérativement modifier le script serveur afin de réceptionner les variables au sein du tableau correspondant : <?php $destinataire = $_POST ["destinataire"]; $sujet = $_POST ["sujet"]; $message = $_POST ["message"]; if ( isset ( $destinataire ) && isset ( $sujet ) && isset ( $message ) ) { if ( @mail ( utf8_decode($destinataire), utf8_decode($sujet), utf8_decode($message) ) ) echo "Mail bien envoyé !"; else echo "Erreur d'envoi !"; } else echo "Variables non transmises"; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 48 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ?> Lorsque la méthode POST est utilisée, les variables sont automatiquement accessible en PHP au sein du tableau associatif $_POST. Attention, si nous tentons d?envoyer des variables à l?aide de la fonction navigateToURL à l?aide de la méthode POST depuis l?environnement de test de Flash CS3, celles-ci seront tout de même envoyées par la méthode GET. Il convient donc de toujours tester au sein du navigateur, une application Flash utilisant la fonction navigateToURL et la méthode POST. Nous venons de traiter le moyen le plus simple d?envoyer des variables à un script serveur. Dans la plupart des applications, nous ne souhaitons pas ouvrir de nouvelle fenêtre navigateur lors de la transmission des données. Nous préférerons généralement un envoi plus « discret ». Afin d?envoyer discrètement des données à un script distant, nous préférerons l?utilisation de la classe URLLoader à la fonction navigateToURL. A retenir ? Lors de l?envoi de variables par la méthode GET, les variables sont automatiquement ajoutés en fin d?url. ? Lors de l?envoi de variables par la méthode POST, les variables ne sont pas affichées au sein de l?url. ? Il convient d?utiliser la méthode GET pour la lecture de données. ? Il convient d?utiliser la méthode POST pour l?écriture de données. Envoyer des variables discrètement Nous allons modifier notre formulaire développé précédemment afin d?envoyer les informations au script distant, sans ouvrir de nouvelle fenêtre navigateur. Ainsi l?expérience utilisateur sera plus élégante. Notre application donnera l?impression de fonctionner de manière autonome : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 49 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.display.SimpleButton; import flash.text.TextField; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLRequestMethod; import flash.net.URLLoader; import flash.events.MouseEvent; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; public var echanges:URLLoader; public function Document () { echanges = new URLLoader(); boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } private function envoiMail ( pEvt:MouseEvent ):void { var variables:URLVariables = new URLVariables(); // affectation des variables à envoyer coté serveur variables.sujet = sujet.text; variables.destinataire = destinataire.text; variables.message = message.text; // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // nous passons les variables dans l'url (tableau POST) requete.method = URLRequestMethod.POST; // associe les variables à l'objet URLRequest requete.data = variables; // envoi les données de manière transparente, sans ouvrir de nouvelle fenêtre navigateur echanges.load ( requete ); } } } Aussitôt la méthode load exécutée, les variables sont transmises au script serveur, l?email est envoyé. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 50 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org En plus de permettre le chargement de données, la méthode load permet aussi l?envoi. Cela diffère des précédentes versions d?ActionScript où la classe LoadVars possédait une méthode load pour le chargement et send pour l?envoi. Nous allons continuer l?amélioration de notre formulaire en ajoutant un mécanisme de validation. Ne serait-il pas intéressant de pouvoir indiquer à Flash que le mail a bien été envoyé ? Pour le moment, notre code n?intègre aucune gestion du retour serveur. C?est ce que nous allons ajouter dans la partie suivante. A retenir ? Afin d?envoyer des données de manière transparente, nous utilisons la méthode load de l?objet URLLoader. ? La méthode load permet le chargement de données mais aussi l?envoi. Renvoyer des données depuis le serveur Nous allons à présent nous intéresser à l?envoi de données avec validation. Afin de savoir au sein de Flash si l?envoi du message a bien été effectué, nous devons simplement écouter l?événement Event.COMPLETE de l?objet URLLoader. Celui-ci est diffusé automatiquement lorsque le lecteur reçoit des informations de la part du serveur : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.display.SimpleButton; import flash.text.TextField; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLRequestMethod; import flash.net.URLLoader; import flash.events.Event; import flash.events.MouseEvent; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 51 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org public var echanges:URLLoader; public function Document () { echanges = new URLLoader(); echanges.addEventListener ( Event.COMPLETE, retourServeur ); boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } private function envoiMail ( pEvt:MouseEvent ):void { var variables:URLVariables = new URLVariables(); // affectation des variables à envoyer coté serveur variables.sujet = sujet.text; variables.destinataire = destinataire.text; variables.message = message.text; // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // nous passons les variables dans l'url (tableau POST) requete.method = URLRequestMethod.POST; // associe les variables à l'objet URLRequest requete.data = variables; // envoi les données de manière transparente, sans ouvrir de nouvelle fenêtre navigateur echanges.load ( requete ); } private function retourServeur ( pEvt:Event ):void { sujet.text = destinataire.text = message.text = ""; message.text = pEvt.target.data; } } } La méthode retourServeur est exécutée lorsque le serveur retourne des informations à Flash par l?intermédiaire du mot clé PHP echo. En testant à nouveau l?application, nous obtenons un retour dans le champ message nous indiquant le mail a bien été envoyé, comme l?illustre la figure 14-12 : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 52 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-12. Accusé de réception de l?envoi du message. Pour des questions de localisation, il n?est pas recommandé de conserver ce script serveur. Car si l?application devait être traduite nous serions obligé de modifier le script serveur. Afin d?éviter cela, nous allons renvoyer la valeur 1 lorsque le mail est bien envoyé, et 0 le cas échéant. Nous renvoyons la valeur 2 lorsque les variables ne sont pas bien récéptionnées : <?php $destinataire = $_POST ["destinataire"]; $sujet = $_POST ["sujet"]; $message = $_POST ["message"]; if ( isset ( $destinataire ) && isset ( $sujet ) && isset ( $message ) ) { if ( @mail ( utf8_decode($destinataire), utf8_decode($sujet), utf8_decode($message) ) ) echo "resultat=1"; else echo "resultat=0"; } else echo "resultat=2"; ?> Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 53 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Nous décidons ainsi côté client quel message afficher. Nous renvoyons donc une chaîne encodée URL qui devra être décodée coté client. Pour cela nous demandons à l?objet URLLoader de décoder toutes les chaînes reçues afin de pouvoir facilement extraire le contenu de la variable resultat. Nous modifions la méthode retourServeur afin d?afficher le message approprié : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.display.SimpleButton; import flash.text.TextField; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLRequestMethod; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.events.Event; import flash.events.MouseEvent; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; public var echanges:URLLoader; public function Document () { echanges = new URLLoader(); echanges.dataFormat = URLLoaderDataFormat.VARIABLES; echanges.addEventListener ( Event.COMPLETE, retourServeur ); echanges.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); echanges.addEventListener ( HTTPStatusEvent.HTTP_STATUS, statutHTTP ); boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } private function envoiMail ( pEvt:MouseEvent ):void { var variables:URLVariables = new URLVariables(); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 54 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org // affectation des variables à envoyer coté serveur variables.sujet = sujet.text; variables.destinataire = destinataire.text; variables.message = message.text; // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // nous passons les variables dans l'url (tableau POST ) requete.method = URLRequestMethod.POST; // associe les variables à l'objet URLRequest requete.data = variables; // envoi les données de manière transparente, sans ouvrir de nouvelle fenêtre navigateur echanges.load ( requete ); } private function retourServeur ( pEvt:Event ):void { sujet.text = destinataire.text = message.text = ""; var messageRetour:String; if ( Number ( pEvt.target.data.resultat ) == 1 ) messageRetour = "Mail bien envoyé !"; else if ( Number ( pEvt.target.data.resultat ) == 2 ) messageRetour = "Erreur de transmission des données !"; else messageRetour = "Erreur d'envoi du message !"; message.text = messageRetour; } private function statutHTTP ( pEvt:HTTPStatusEvent ):void { trace( "Code HTTP : " + pEvt.status ); } private function erreurChargement ( pEvt:IOErrorEvent ):void { trace("Erreur de chargement !"); } } } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 55 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous n?envoyons pas les variables au script serveur ou que la fonction mail échoue nous obtenons un message approprié dans notre application Flash. Dans le code suivant, nous n?affectons aucune variable à l?objet URLVariables : private function envoiMail ( pEvt:MouseEvent ):void { var variables:URLVariables = new URLVariables(); // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // nous passons les variables dans l'url (tableau POST ) requete.method = URLRequestMethod.POST; // associe les variables à l'objet URLRequest requete.data = variables; // envoi les données de manière transparente, sans ouvrir de nouvelle fenêtre navigateur echanges.load ( requete ); } L?email ne peut être envoyé, la figure 14-13 illustre le message affiché par l?application : Figure 14-13. Echec d?envoi de l?email. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 56 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Nous pourrions aller plus loin dans cet exemple et enregistrer l?utilisateur dans une base de données et renvoyer d?autres types d?informations. Nous reviendrons en détail sur l?envoi et la réception de données issues d?une base de données au cours du chapitre 19 intitulé Remoting. A retenir ? Afin de récupérer les données issues du serveur nous écoutons l?événement Event.COMPLETE de l?objet URLLoader. ? Afin de retourner des données à Flash, nous devons utiliser le mot clé PHP echo. Aller plus loin Souvenez-vous, au cours du chapitre 2, nous avons découvert la notion d?expressions régulières permettant d?effectuer des manipulations complexes sur des chaînes de caractères. Afin d?optimiser notre application nous allons intégrer une vérification de l?email par expression régulière. Nous allons donc créer une classe OutilsFormulaire globale à tous nos projets, intégrant une méthode statique verifieEmail. Celle-ci renverra true si l?email est correcte ou false le cas échéant. Rappelez-vous, au cours du chapitre 12 intitulé Programmation Bitmap, nous avions créé un répertoire global de classes nommé classes_as3. Au sein du répertoire outils nous créons la classe OutilsFormulaire suivante : package org.bytearray.outils { public class FormulaireOutils { private static var emailModele:RegExp = /^[a-z0-9][-._a-z0-9]*@([a-z0- 9][-_a-z0-9]*\.)+[a-z]{2,6}$/ public static function verifieEmail ( pEmail:String ):Boolean { var resultat:Array = pEmail.match( FormulaireOutils.emailModele ); return resultat != null; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 57 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Une fois définie, nous pouvons l?utiliser dans notre application en l?important : import org.bytearray.outils.FormulaireOutils; Puis nous modifions la méthode envoiMail afin d?intégrer une vérification de l?email : private function envoiMail ( pEvt:MouseEvent ):void { if ( FormulaireOutils.verifieEmail ( destinataire.text ) ) { var variables:URLVariables = new URLVariables(); // affectation des variables à envoyer coté serveur variables.sujet = sujet.text; variables.destinataire = destinataire.text; variables.message = message.text; // création de l'objet URLRequest var requete:URLRequest = new URLRequest ("http://localhost/mail/envoiMail.php"); // nous passons les variables dans l'url (tableau POST ) requete.method = URLRequestMethod.POST; // associe les variables à l'objet URLRequest requete.data = variables; // envoi les données de manière transparente, sans ouvrir de nouvelle fenêtre navigateur echanges.load ( requete ); } else destinataire.text = "Email non valide !"; } Ainsi, toutes nos applications pourront utiliser la classe FormulaireOutils afin de vérifier la validité d?un email. Bien entendu, nous pouvons ajouter d?autres méthodes pratiques à celle-ci. Nous retrouvons ici l?intérêt d?une méthode statique, pouvant être appelée directement sur le constructeur de la classe, sans avoir à instancier un objet FormulaireOutils. Envoyer un flux binaire Rendez vous au chapitre 20 intitulé ByteArray pour découvrir comment transmettre un flux binaire grâce à la classe URLLoader. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 58 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Télécharger un fichier Nous avons vu jusqu?à present comment charger ou envoyer des variables à partir du lecteur Flash. Certaines applications peuvent néanmoins nécessiter un téléchargement ou un envoi de fichiers entre le serveur et l?ordinateur de l?utilisateur. La classe flash.net.FileReference permet de télécharger n?importe quel fichier grace à la méthode download. Imaginons que nous devions télécharger un fichier zip depuis une URL spécifique telle : http://www.monserveur.org/archive.zip Au sein d?un nouveau document Flash CS3 nous associons la classe de document suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { } } } Puis nous créons un objet FileReference : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReference; public class Document extends ApplicationDefaut { private var telechargeur:FileReference; public function Document () { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 59 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org telechargeur = new FileReference(); } } } Afin de gérer les différentes étapes liées au téléchargement de données la classe FileReference diffuse différents événements dont voici le détail : ? Event.CANCEL : diffusé lorsqu?un envoi ou téléchargement est annulé par la fenêtre de parcours. ? Event.COMPLETE : diffusé lorsqu?un envoi ou un téléchargement a réussi. ? HTTPStatusEvent.HTTP_STATUS : diffusé lorsqu?un envoi ou chargement échoue. ? IOErrorEvent.IO_ERROR : diffusé lorsque l?envoi ou la réception des données échoue. ? Event.OPEN : diffusé lorsque l?envoi ou la réception des données commence. ? ProgressEvent.PROGRESS : diffusé lorsque l?envoi ou le chargement est en cours. ? SecurityErrorEvent.SECURITY_ERROR : diffusé lorsque le lecteur tente d?envoyer ou de charger des données depuis un domaine non autorisé. ? Event.SELECT : diffusé lorsque le bouton OK de la boite de dialogue de recherches de fichiers est relâche ? DataEvent.UPLOAD_COMPLETE_DATA : diffusé lorsqu?une opération d?envoi ou de chargement est terminé et que le serveur renvoie des données. Afin de télécharger un fichier nous utilisons la méthode download dont voici la signature : public function download(request:URLRequest, defaultFileName:String = null):void Celle-ci requiert deux paramètres dont voici le détail : ? request : un objet URLRequest contenant l?adresse de l?élément à télécharger. ? defaultFileName : ce paramètre permet de spécifier le nom de l?élément téléchargé au sein de la boite de dialogue. Dans le code suivant, nous téléchargeons un fichier distant : package org.bytearray.document Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 60 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; public class Document extends ApplicationDefaut { private var telechargeur:FileReference; private var lienFichier:String = "http://alivepdf.bytearray.org/wp- content/images/logo_small.jpg"; private var requete:URLRequest = new URLRequest( lienFichier ); public var boutonTelecharger:SimpleButton; public function Document () { // création d'un objet FileReference telechargeur = new FileReference(); boutonTelecharger.addEventListener ( MouseEvent.CLICK, telechargeFichier ); } private function telechargeFichier ( pEvt:MouseEvent ):void { // téléchargement du fichier distant telechargeur.download ( requete ); } } } Lorsque nous cliquons sur le bouton boutonTelecharger une fenêtre de téléchargement s?ouvre comme l?illustre la figure 14-14 : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 61 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-14. Boite de dialogue. Une fois l?emplacement sélectionné, nous cliquons sur le bouton Enregistrer. Le fichier est alors sauvé en local sur l?ordinateur exécutant l?application. Bien que nous n?ayons pas spécifié le nom du fichier, le lecteur Flash extrait automatiquement celui-ci à partir de son URL. Attention, lorsque cette boite de dialogue apparaît, la tentative d?accès au fichier distant n?a pas encore démarré. Si nous modifions l?adresse du fichier distant par l?adresse suivante : private var lienFichier:String = "http://www.monserveurInexistant.org/logo_small.jpg"; Le lecteur affiche tout de même la boite de dialogue ainsi que le nom du fichier. Afin de gérer l?état du transfert nous écoutons les principaux événements : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 62 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.HTTPStatusEvent; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var telechargeur:FileReference; private var lienFichier:String = "http://alivepdf.bytearray.org/wp- content/images/logo_small.jpg"; private var requete:URLRequest = new URLRequest( lienFichier ); public var boutonTelecharger:SimpleButton; public function Document () { // création d'un objet FileReference telechargeur = new FileReference(); telechargeur.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); telechargeur.addEventListener ( Event.COMPLETE, chargementTermine ); telechargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); boutonTelecharger.addEventListener ( MouseEvent.CLICK, telechargeFichier ); } private function chargementEnCours ( pEvt:ProgressEvent ):void { trace( "chargement en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function chargementTermine ( pEvt:Event ):void { trace( "chargement terminé" ); } private function erreurChargement ( pEvt:IOErrorEvent ):void { trace( "erreur de chargement du fichier !" ); } private function telechargeFichier ( pEvt:MouseEvent ):void { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 63 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org // téléchargement du fichier distant telechargeur.download ( requete ); } } } La méthode download n?accèpte qu?un seul fichier à télécharger. Il est donc impossible de télécharger plusieurs fichiers en même temps à l?aide de la classe FileReference. Il est important de noter que dans un contexte inter domaines, le téléchargement de fichiers est interdit si le domaine distant n?est pas autorisé. Ainsi, si nous publions l?application actuelle sur un serveur distant, l?événement SecurityErrorEvent.SECURITY_ERROR est diffusé affichant le message suivant : Error #2048: Violation de la sécurité Sandbox : http://monserveur.fr/as3/chap-14-filereference.swf ne peut pas charger de données à partir de http://alivepdf.bytearray.org/wp- content/images/logo_small.jpg. Afin de pouvoir charger le fichier distant nous devons utiliser un fichier de régulation ou un proxy. Nous allons utiliser dans le code suivant un proxy afin de feindre le lecteur Flash : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.URLRequestMethod; import flash.net.URLVariables; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; import flash.events.HTTPStatusEvent; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; public class Document extends ApplicationDefaut { private var telechargeur:FileReference; private var lienFichier:String = "proxy.php"; private var requete:URLRequest = new URLRequest( lienFichier ); public var boutonTelecharger:SimpleButton; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 64 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org public function Document () { // création d'un objet FileReference telechargeur = new FileReference(); // création d'un objet URLVariables pour passer le chemin du fichier à charger au proxy var variables:URLVariables = new URLVariables(); // spécification du chemin variables.chemin = "http://alivepdf.bytearray.org/wp- content/images/logo_small.jpg"; // affectation des variables et de la méthode utilisée requete.data = variables; requete.method = URLRequestMethod.POST; telechargeur.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); telechargeur.addEventListener ( Event.COMPLETE, chargementTermine ); telechargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); boutonTelecharger.addEventListener ( MouseEvent.CLICK, telechargeFichier ); } private function chargementEnCours ( pEvt:ProgressEvent ):void { trace( "chargement en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function chargementTermine ( pEvt:Event ):void { trace( "chargement terminé" ); } private function erreurChargement ( pEvt:IOErrorEvent ):void { trace( "erreur de chargement du fichier !" ); } private function telechargeFichier ( pEvt:MouseEvent ):void { // téléchargement du fichier distant telechargeur.download ( requete ); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 65 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Une fois l?application publiée, l?appel de la méthode download ne lève plus d?exeception. La boite de dialogue s?ouvre et propose comme nom de fichier le nom du fichier proxy utilisé comme l?illustre la figure 14-15 : Figure 14-15. Nom d?enregistrement du fichier. Comme nous l?avons vu précédemment, le lecteur devine automatiquement le nom du fichier téléchargé depuis l?URL du fichier. Dans notre cas, ce dernier considère que le fichier à charger est le proxy et propose donc comme nom de fichier proxy.php. Afin de proposer le nom réel du fichier chargé, nous allons utiliser le deuxième paramètre defaultFileName de la méthode download : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.URLRequestMethod; import flash.net.URLVariables; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 66 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.HTTPStatusEvent; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; public class Document extends ApplicationDefaut { private var telechargeur:FileReference; private var lienFichier:String = "proxy.php"; private var requete:URLRequest = new URLRequest( lienFichier ); private var urlFichier:String; public var boutonTelecharger:SimpleButton; public function Document () { // création d'un objet FileReference telechargeur = new FileReference(); // création d'un objet URLVariables pour passer le chemin du fichier à charger au proxy var variables:URLVariables = new URLVariables(); // url du fichier distant à charger par le proxy urlFichier = "http://alivepdf.bytearray.org/wp- content/images/logo_small.jpg"; // spécification du chemin variables.chemin = urlFichier; // affectation des variables et de la méthode utilisée requete.data = variables; requete.method = URLRequestMethod.POST; telechargeur.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); telechargeur.addEventListener ( Event.COMPLETE, chargementTermine ); telechargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); boutonTelecharger.addEventListener ( MouseEvent.CLICK, telechargeFichier ); } private function chargementEnCours ( pEvt:ProgressEvent ):void { trace( "chargement en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function chargementTermine ( pEvt:Event ):void { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 67 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org trace( "chargement terminé" ); } private function erreurChargement ( pEvt:IOErrorEvent ):void { trace( "erreur de chargement du fichier !" ); } private function telechargeFichier ( pEvt:MouseEvent ):void { // expression régulière var modele:RegExp = /[A-Za-z0-9_]*\.\D{3}$/ // extrait le nom du fichier de l'url var resultat:Array = urlFichier.match ( modele ); // téléchargement du fichier distant if ( resultat != null ) telechargeur.download ( requete, resultat[0] ); } } } Le nom du fichier est extrait manuellement puis passé en deuxième paramètre de la méthode download. Lors du clic sur le bouton boutonTelecharger la boite de dialogue s?ouvre en proposant le bon nom de fichier : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 68 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-16. Nom d?enregistrement du fichier. Il est donc possible de rendre totalement transparent l?intervention d?un proxy dans le téléchargement de notre fichier distant. Lorsque nous cliquons sur le bouton Enregistrer, le fichier est téléchargé et enregistré. Nous allons nous attarder à présent sur l?envoi de fichiers par la classe flash.net.FileReference. A retenir ? Afin de télécharger un fichier depuis le lecteur Flash nous utilisons la méthode download de la classe FileReference. ? L?appel de la méthode download entraîne l?ouverture d?une boite de dialogue permettant de sauver le fichier sur l?ordinateur de l?utilisateur. Publier un fichier A l?inverse si nous souhaitons mettre en ligne un fichier selectionné depuis l?ordinateur de l?utilisateur, nous pouvons utiliser la méthode upload de la classe FileReference. Dans un nouveau document Flash CS3, nous associons la classe de document suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var envoyeur:FileReference; private var lienScript:String = "http://localhost/envoi/envoiFichier.php"; private var requete:URLRequest = new URLRequest( lienScript ); public var boutonEnvoyer:SimpleButton; public function Document () { // création d'un objet FileReference Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 69 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org envoyeur = new FileReference(); envoyeur.addEventListener ( ProgressEvent.PROGRESS, envoiEnCours ); envoyeur.addEventListener ( Event.COMPLETE, envoiTermine ); envoyeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurEnvoi ); boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function envoiEnCours ( pEvt:ProgressEvent ):void { trace( "envoi en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function envoiTermine ( pEvt:Event ):void { trace( "envoi terminé" ); } private function erreurEnvoi ( pEvt:IOErrorEvent ):void { trace( "erreur d'envoi du fichier !" ); } private function parcoursFichiers ( pEvt:MouseEvent ):void { // ouvre la boite de dialogue permettant de parcourir les fichiers envoyeur.browse (); } } } Au sein de l?application, un bouton boutonEnvoyer déclenche l?ouverture de la boite de dialogue permettant de parcourir les fichiers à transférer à l?aide de la méthode browse de l?objet FileReference. La propriété lienScript pointe vers un script serveur permettant l?enregistrement du fichier transféré sur le serveur. Voici le code PHP permettant de sauver le fichier transféré : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 70 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org <?php $fichier = $_FILES["Filedata"]; if ( isset ( $fichier ) ) { $nomFichier = $fichier['name']; move_uploaded_file ( $fichier['tmp_name'], "monRepertoire/".$nomFichier); } ?> Le lecteur Flash place le fichier transféré ua sein du tableau $_FILES et de la propriété Filedata. Lors de la gestion d?envoi de fichiers par FileReference il convient d?écouter l?événement Event.SELECT diffusé lorsque l?utilisateur a selectionné un fichier. Ainsi nous pouvons récupérer différentes informations liées au fichier selectionné : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var envoyeur:FileReference; private var lienScript:String = "http://localhost/envoi/envoiFichier.php"; private var requete:URLRequest = new URLRequest( lienScript ); public var boutonEnvoyer:SimpleButton; public function Document () { // création d'un objet FileReference envoyeur = new FileReference(); envoyeur.addEventListener ( Event.SELECT, selectionFichier ); envoyeur.addEventListener ( ProgressEvent.PROGRESS, envoiEnCours ); envoyeur.addEventListener ( Event.COMPLETE, envoiTermine ); envoyeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurEnvoi ); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 71 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function selectionFichier ( pEvt:Event ):void { var fichier:FileReference = FileReference ( pEvt.target ); // affiche : Tue Oct 23 19:59:27 GMT+0200 2007 trace( fichier.creationDate ); // affiche : Interview.doc trace( fichier.name ); // affiche : 37888 trace( fichier.size ); // affiche : .doc trace( fichier.type ); } private function envoiEnCours ( pEvt:ProgressEvent ):void { trace( "envoi en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function envoiTermine ( pEvt:Event ):void { trace( "envoi terminé" ); } private function erreurEnvoi ( pEvt:IOErrorEvent ):void { trace( "erreur d'envoi du fichier !" ); } private function parcoursFichiers ( pEvt:MouseEvent ):void { // ouvre la boite de dialogue permettant de parcourir les fichiers envoyeur.browse (); } } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 72 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } Voici le détail de chacune des propriétés définies par la classe FileReference : ? creationDate : date de création du fichier. ? creator : type de créateur Macintosh du fichier. ? modificationDate : date de dernière modification du fichier. ? name : nom du fichier. ? size : taille du fichier en octets. ? type : extension du fichier. Une fois l?objet séléctionné, nous appelons la méthode upload sur l?objet FileReference : private function selectionFichier ( pEvt:Event ):void { var fichier:FileReference = FileReference ( pEvt.target ); // affiche : Tue Oct 23 19:59:27 GMT+0200 2007 trace( fichier.creationDate ); // affiche : Interview.doc trace( fichier.name ); // affiche : 37888 trace( fichier.size ); // affiche : .doc trace( fichier.type ); // envoi du fichier fichier.upload ( requete ); } Comme l?illustre la figure 14-17, vous remarquerez qu?il est impossible de sélectionner plusieurs fichiers au sein de la boite de dialogue ouverte par la méthode browse de l?objet FileReference : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 73 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-17. Selection multiple impossible. Afin de pouvoir sélectionner plusieurs fichiers nous devons obligatoirement utiliser la classe flash.net.FileReferenceList. A retenir ? Afin de selectionner un fichier à transférer sur un serveur depuis le lecteur Flash nous utilisons d?abord la méthode browse de l?objet FileReference. ? Une fois le fichier sélectionné, nous appelons la méthode upload en spécifiant l?URL du script serveur permettant l?enregistrement du fichier. Publier plusieurs fichiers Afin de pouvoir envoyer plusieurs fichiers en même temps, nous modifions la classe du document afin de créer un objet FileReferenceList : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReferenceList; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var envoyeurMultiple:FileReferenceList; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 74 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private var lienScript:String = "http://localhost/envoi/envoiFichier.php"; private var requete:URLRequest = new URLRequest( lienScript ); public var boutonEnvoyer:SimpleButton; public function Document () { // création d'un objet FileReference envoyeurMultiple = new FileReferenceList(); envoyeurMultiple.addEventListener ( Event.SELECT, selectionFichier ); envoyeurMultiple.addEventListener ( ProgressEvent.PROGRESS, envoiEnCours ); envoyeurMultiple.addEventListener ( Event.COMPLETE, envoiTermine ); envoyeurMultiple.addEventListener ( IOErrorEvent.IO_ERROR, erreurEnvoi ); boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function selectionFichier ( pEvt:Event ):void { // référence le tableau contenant chaque objet FileReference var listeFichiers:Array = pEvt.target.fileList; // affiche : n fichiers séléctionnés trace( listeFichiers.length + " fichier(s) selectionnés"); } private function envoiEnCours ( pEvt:ProgressEvent ):void { trace( "envoi en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function envoiTermine ( pEvt:Event ):void { trace( "envoi terminé" ); } private function erreurEnvoi ( pEvt:IOErrorEvent ):void { trace( "erreur d'envoi du fichier !" ); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 75 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private function parcoursFichiers ( pEvt:MouseEvent ):void { // ouvre la boite de dialogue permettant de parcourir les fichiers envoyeurMultiple.browse (); } } } La boite de dialogue ouverte par la méthode browse de l?objet FileReferenceList permet la sélection multiple, comme l?indique la figure 14-18 : Figure 14-18. Selection multiple possible. En réalité lorsque nous sélectionnons plusieurs fichiers à travers la boite de dialogue et que nous validons la sélection. La classe FileReferenceList crée un interne un objet FileReference associé à chaque fichier référence au sein de la propriété fileList de l?objet FileReferenceList. Nous devons donc manuellement parcourir ce tableau interne contenant les objets FileReference et appeler la méthode upload sur chacun d?entre eux. Il est important de signaler que l?objet FileReference ne diffuse que les événements Event.SELECT et Event.CANCEL liés à la sélection des fichiers. La gestion du chargement se fait par l?intermédiaire des objets FileReference créés en interne par l?objet FileReferenceList : package org.bytearray.document Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 76 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReferenceList; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var envoyeurMultiple:FileReferenceList; private var lienScript:String = "http://localhost/envoi/envoiFichier.php"; private var requete:URLRequest = new URLRequest( lienScript ); public var boutonEnvoyer:SimpleButton; public function Document () { // création d'un objet FileReference envoyeurMultiple = new FileReferenceList(); envoyeurMultiple.addEventListener ( Event.SELECT, selectionFichier ); boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function selectionFichier ( pEvt:Event ):void { // référence le tableau contenant chaque objet FileReference var listeFichiers:Array = pEvt.target.fileList; // longueur du tableau var lng:int = listeFichiers.length; for ( var i:int = 0; i< lng; i++ ) { var fichier:FileReference = listeFichiers[i]; fichier.addEventListener ( ProgressEvent.PROGRESS, envoiEnCours ); fichier.addEventListener ( Event.COMPLETE, envoiTermine ); fichier.addEventListener ( IOErrorEvent.IO_ERROR, erreurEnvoi ); fichier.upload ( requete ); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 77 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } } private function envoiEnCours ( pEvt:ProgressEvent ):void { trace( "envoi en cours : " + pEvt.bytesLoaded / pEvt.bytesTotal ); } private function envoiTermine ( pEvt:Event ):void { trace( "envoi terminé" ); } private function erreurEnvoi ( pEvt:IOErrorEvent ):void { trace( "erreur d'envoi du fichier !" ); } private function parcoursFichiers ( pEvt:MouseEvent ):void { // ouvre la boite de dialogue permettant de parcourir les fichiers envoyeurMultiple.browse (); } } } En testant notre application, les fichiers sont bien transférés sur le serveur. Nous allons ajouter à présent une jauge de chargement indiquant l?état du transfert. Pour cela nous créons un clip de forme rectangulaire en bibliothèque auquel nous associons une classe Prechargeur. package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import flash.net.FileReferenceList; import flash.net.FileReference; import flash.net.URLRequest; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.events.Event; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 78 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class Document extends ApplicationDefaut { private var envoyeurMultiple:FileReferenceList; private var lienScript:String = "http://localhost/envoi/envoiFichier.php"; private var requete:URLRequest = new URLRequest( lienScript ); public var boutonEnvoyer:SimpleButton; private var prechargeur:Prechargeur; public function Document () { // création d'un objet FileReference envoyeurMultiple = new FileReferenceList(); prechargeur = new Prechargeur(); envoyeurMultiple.addEventListener ( Event.SELECT, selectionFichier ); boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function selectionFichier ( pEvt:Event ):void { // référence le tableau contenant chaque objet FileReference var listeFichiers:Array = pEvt.target.fileList; // longueur du tableau var lng:int = listeFichiers.length; for ( var i:int = 0; i< lng; i++ ) { var fichier:FileReference = listeFichiers[i]; fichier.addEventListener ( ProgressEvent.PROGRESS, envoiEnCours ); fichier.addEventListener ( Event.COMPLETE, envoiTermine ); fichier.addEventListener ( IOErrorEvent.IO_ERROR, erreurEnvoi ); fichier.upload ( requete ); } // ajout du préchargeur à l'affichage if ( !contains ( prechargeur ) ) addChild ( prechargeur ); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 79 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private function envoiEnCours ( pEvt:ProgressEvent ):void { // ajuste la taille de la jauge par rapport à l'état du transfert prechargeur.scaleX = pEvt.bytesLoaded / pEvt.bytesTotal; } private function envoiTermine ( pEvt:Event ):void { trace( "envoi terminé" ); } private function erreurEnvoi ( pEvt:IOErrorEvent ):void { trace( "erreur d'envoi du fichier !" ); } private function parcoursFichiers ( pEvt:MouseEvent ):void { // ouvre la boite de dialogue permettant de parcourir les fichiers envoyeurMultiple.browse (); } } } Lors du transfert des fichiers, la jauge de transfert indique l?état du chargement. En revanche, lors du transfert de multiples fichiers la jauge reçoit les événements de chaque fichier en cours de transfert et indique le chargement de tous les fichiers en même temps. De la même manière, nous ne pouvons pas savoir simplement, si le transfert de tous les fichiers est terminé. Nous ne bénéficions pas d?événement approprié. Pour remédier à tout cela nous allons développer notre propre version de la classe FileReferenceList. A retenir Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 80 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ? Seule la classe FileReferenceList permet l?envoi de plusieurs fichiers en même temps. Création de la classe EnvoiMultiple Nous allons améliorer la gestion d?envoi des fichiers en créant une classe EnvoiMultiple. Celle si étend la classe FileReferenceList et améliore le transfert de chaque fichier en s?assurant que chaque fichier est transféré l?un après l?autre. De plus, nous allons ajouter un événement indiquant la fin du transfert de tous les fichiers. Nous commençons par définir notre classe EnvoiMultiple en étendant la classe FileReferenceList : package org.bytearray.envoi { import flash.net.FileReferenceList; import flash.events.Event; import flash.net.URLRequest; public class EnvoiMultiple extends FileReferenceList { private var requete:URLRequest; public function EnvoiMultiple ( pRequete:URLRequest ) { requete = pRequete; addEventListener ( Event.SELECT, selectionFichiers ); } private function selectionFichiers ( pEvt:Event ):void { trace("fichiers selectionnés"); } } } La classe EnvoiMultiple accèpte en paramètre un objet URLRequest pointant vers un script serveur permettant l?enregistrement du fichier. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 81 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Nous souhaitons que la classe EnvoiMultiple puisse gérer de manière synchronisée chaque envoi de fichiers. Pour cela nous allons lancer le premier transfert puis attendre que celui soit terminé pour déclencher les suivants : package org.bytearray.envoi { import flash.events.Event; import flash.events.ProgressEvent; import org.bytearray.events.ProgressQueueEvent; import org.bytearray.events.QueueEvent; import flash.net.FileReference; import flash.net.URLRequest; public class EnvoiMultiple extends FileReferenceList { private var historiqueFichiers:Array; private var fichierEnCours:FileReference; private var fichiers:Array; private var requete:URLRequest; public function EnvoiMultiple ( pRequete:URLRequest ) { requete = pRequete; addEventListener ( Event.SELECT, selectionFichiers ); } private function selectionFichiers ( pEvt:Event ):void { historiqueFichiers = new Array(); fichiers = pEvt.target.fileList; suivant(); } private function suivant ( ):void { var fichierEnCours:FileReference = fichiers.shift(); historiqueFichiers.push ( fichierEnCours ); fichierEnCours.addEventListener ( Event.COMPLETE, transfertTermine ); fichierEnCours.addEventListener ( ProgressEvent.PROGRESS, transfertEnCours ); fichierEnCours.upload ( requete ); Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 82 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } private function transfertTermine ( pEvt:Event ):void { } private function transfertEnCours ( pEvt:ProgressEvent ):void { } } } Lorsque le premier fichier a été transféré la méthode écouteur transfertTermine est déclenchée. Nous devons ajouter au sein de celle-ci le code nécessaire afin de lancer le transfert suivant, si cela est nécessaire. Nous en profitions pour supprimer l?écoute des événements Event.COMPLETE et ProgressEvent.PROGRESS auprès de l?objet FileReference en cours. Nous consommons le tableau fichierEnCours en déclenchant la méthode suivant, si d?autres fichiers sont en attendre de transfert au sein du tableau fichiers : private function transfertTermine ( pEvt:Event ):void { pEvt.target.removeEventListener ( Event.COMPLETE, transfertTermine ); pEvt.target.removeEventListener ( ProgressEvent.PROGRESS, transfertEnCours ); if ( fichiers.length ) suivant(); trace("transfert terminé !"); } private function transfertEnCours ( pEvt:ProgressEvent ):void { trace("transfert en cours..."); } Il ne nous reste plus qu?à diffuser deux événements pour chaque phase : ? EvenementEnvoiMultiple.TERMINE : cet événement est diffusé lorsque la totalité des fichiers séléctionnés sont transférés. Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 83 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org ? ProgressionEvenementEnvoiMultiple.PROGRESSION : cet événement est diffusé lorsqu?un fichier est en cours de transfert. Au sein du paquetage evenements nous créons la classe EvenementEnvoiMultiple : package org.bytearray.evenements { import flash.events.Event; public class EvenementEnvoiMultiple extends Event { public static const TERMINE:String = "termine"; public var historique:Array; public function EvenementEnvoiMultiple ( pType:String, pHistorique:Array=null ) { // initialisation du constructeur de la classe Event super( pType, false, false ); historique = pHistorique; } // la méthode clone doit être surchargée public override function clone ():Event { return new EvenementEnvoiMultiple ( type, historique ); } // la méthode toString doit être surchargée public override function toString ():String { return '[EvenementEnvoiMultiple type="'+ type +'" bubbles=' + bubbles + ' eventPhase='+ eventPhase + ' cancelable=' + cancelable +']'; } } } Puis la classe ProgressionEvenementEnvoiMultiple : package org.bytearray.evenements { import flash.events.ProgressEvent; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 84 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.Event; import flash.net.FileReference; public class ProgressionEvenementEnvoiMultiple extends ProgressEvent { public static const PROGRESSION:String = "progression"; public var fichier:FileReference; public function ProgressionEvenementEnvoiMultiple ( pType:String, pOctetsCharges:Number, pOctetsTotaux:Number, pFichier:FileReference ) { // initialisation du constructeur de la classe Event super( pType, false, false, pOctetsCharges, pOctetsTotaux ); fichier = pFichier; } // la méthode clone doit être surchargée public override function clone ():Event { return new ProgressionEvenementEnvoiMultiple ( type, bytesLoaded, bytesTotal, fichier ); } // la méthode toString doit être surchargée public override function toString ():String { return '[ProgressionEvenementEnvoiMultiple type="'+ type +'" bubbles=' + bubbles + ' eventPhase='+ eventPhase + ' cancelable=' + cancelable + ' bytesLoaded=' + bytesLoaded +' bytesTotal=' + bytesTotal +']'; } } } Enfin nous diffusons les événements appropriés au sein des méthodes transfertEnCours et transfertTermine : package org.bytearray.envoi { import flash.events.Event; import flash.events.ProgressEvent; import org.bytearray.evenements.EvenementEnvoiMultiple; import org.bytearray.evenements.ProgressionEvenementEnvoiMultiple; import flash.net.FileReference; import flash.net.FileReferenceList; import flash.net.URLRequest; public class EnvoiMultiple extends FileReferenceList Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 85 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { private var historiqueFichiers:Array; private var fichierEnCours:FileReference; private var fichiers:Array; private var requete:URLRequest; public function EnvoiMultiple ( pRequete:URLRequest ) { requete = pRequete; addEventListener ( Event.SELECT, selectionFichiers ); } private function selectionFichiers ( pEvt:Event ):void { historiqueFichiers = new Array(); fichiers = pEvt.target.fileList; suivant(); } private function suivant ( ):void { var fichierEnCours:FileReference = fichiers.shift(); historiqueFichiers.push ( fichierEnCours ); fichierEnCours.addEventListener ( Event.COMPLETE, transfertTermine ); fichierEnCours.addEventListener ( ProgressEvent.PROGRESS, transfertEnCours ); fichierEnCours.upload ( requete ); } private function transfertTermine ( pEvt:Event ):void { pEvt.target.removeEventListener ( Event.COMPLETE, transfertTermine ); pEvt.target.removeEventListener ( ProgressEvent.PROGRESS, transfertEnCours ); if ( fichiers.length ) suivant(); else dispatchEvent ( new EvenementEnvoiMultiple ( EvenementEnvoiMultiple.TERMINE, historiqueFichiers ) ); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 86 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private function transfertEnCours ( pEvt:ProgressEvent ):void { dispatchEvent ( new ProgressionEvenementEnvoiMultiple ( ProgressionEvenementEnvoiMultiple.PROGRESSION, pEvt.bytesLoaded, pEvt.bytesTotal, FileReference ( pEvt.target ) ) ); } } } Nous pouvons à présent utiliser la classe EnvoiMultiple. Dans un nouveau document Flash CS3, nous associons la classe de document suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.envoi.EnvoiMultiple; import org.bytearray.evenements.EvenementEnvoiMultiple; import org.bytearray.evenements.ProgressionEvenementEnvoiMultiple; import flash.display.MovieClip; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.text.TextField; public class Document extends ApplicationDefaut { private var envoyeur:EnvoiMultiple; public var boutonEnvoyer:SimpleButton; public var prechargeur:MovieClip; public var legende:TextField; public function Document () { // création de l'objet EnvoiMultiple envoyeur = new EnvoiMultiple( new URLRequest ("http://www.bytearray.org/as3/upload.php" )); // écoute des événements personnalisés liés au chargement envoyeur.addEventListener ( ProgressionEvenementEnvoiMultiple.PROGRESSION, transfertEnCours ); envoyeur.addEventListener ( EvenementEnvoiMultiple.TERMINE, transfertTermine ); boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function transfertEnCours ( pEvt:ProgressionEvenementEnvoiMultiple ):void Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 87 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { // mise à jour de la barre de progression prechargeur.scaleX = pEvt.bytesLoaded / pEvt.bytesTotal; // informations liées au fichier en cours de transfert legende.text = "Transfert en cours : " + pEvt.fichier.name; } private function transfertTermine ( pEvt:EvenementEnvoiMultiple ):void { // indique le nombre de fichiers transférés legende.text = pEvt.historique.length + " fichiers(s) transféré(s)"; } private function parcoursFichiers ( pEvt:MouseEvent ):void { envoyeur.browse(); } } } La document en cours contient une barre de préchargement prechargeur ainsi qu?un champ texte legende posés sur la scène afin d?afficher les informations relatives au transfert des fichiers. La figure 14-19 illustre l?application : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 88 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-19. Transfert de fichiers. Lorsque les images ont été transférées, l?événement EvenementEnvoiMultiple.TERMINE est diffusé, cela nous permet d?afficher un message approprié et de réagir en conséquence : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 89 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 14-20. Transfert de fichiers terminé. La propriété historique de l?objet événementiel EvenementEnvoiMultiple est un tableau contenant des objets FileReference associé à chaque fichier transféré. En récupérant sa longueur nous pouvons indiquer le nombre de fichiers transférés. A vous d?ajouter à présent, une instanciation dynamique de la jauge de transfert et de la légende puis de les supprimer une fois l?événement EvenementEnvoiMultiple.TERMINE est diffusé. Retourner des données une fois l?envoi terminé ActionScript 3 ajoute un événement très intéressant à la classe FileReference que nous allons découvrir à présent. Dans les précédentes versions d?ActionScript, il était impossible d?obtenir des informations du serveur lorsque le transfert était terminé. Très souvent, les développeurs souhaitaient retourner des informations indiquant si le transfert des données avait échoué ou non. L?événement FileFerence.COMPLETE n?offre pas la possibilité de renvoyer les informations provenant du serveur. En revanche, ActionScript 3 introduit un événement DataEvent.UPLOAD_COMPLETE_DATA offrant cette possibilité. Nous allons ajouter cette fonctionnalité à notre classe EnvoiMultiple et renvoyer des informations afin d?indiquer à l?application Flash que l?écriture sur le serveur a réussi ou non. Nous allons modifier le script serveur gérant l?enregistrement des fichiers de manière à indiquer si l? : <?php $fichier = $_FILES["Filedata"]; if ( isset ( $fichier ) ) { $nomFichier = $fichier['name']; if ( move_uploaded_file ( $fichier['tmp_name'], $nomFichier) ) echo "resultat=1"; else echo "resultat=0"; } else echo "resultat=0"; ?> Voici le code complet de la classe EnvoiMultiple : Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 90 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org package org.bytearray.envoi { import flash.events.DataEvent; import flash.events.Event; import flash.events.ProgressEvent; import org.bytearray.evenements.EvenementEnvoiMultiple; import org.bytearray.evenements.ProgressionEvenementEnvoiMultiple; import flash.net.FileReference; import flash.net.FileReferenceList; import flash.net.URLRequest; public class EnvoiMultiple extends FileReferenceList { private var historiqueFichiers:Array; private var fichierEnCours:FileReference; private var fichiers:Array; private var requete:URLRequest; public function EnvoiMultiple ( pRequete:URLRequest ) { requete = pRequete; addEventListener ( Event.SELECT, selectionFichiers ); } private function selectionFichiers ( pEvt:Event ):void { historiqueFichiers = new Array(); fichiers = pEvt.target.fileList; suivant(); } private function suivant ( ):void { var fichierEnCours:FileReference = fichiers.shift(); historiqueFichiers.push ( fichierEnCours ); fichierEnCours.addEventListener ( Event.COMPLETE, transfertTermine ); fichierEnCours.addEventListener ( ProgressEvent.PROGRESS, transfertEnCours ); fichierEnCours.addEventListener ( DataEvent.UPLOAD_COMPLETE_DATA, retourServeur ); fichierEnCours.upload ( requete ); } Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 91 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org private function retourServeur ( pEvt:DataEvent ):void { dispatchEvent ( pEvt ); pEvt.target.removeEventListener ( DataEvent.UPLOAD_COMPLETE_DATA, retourServeur ); } private function transfertTermine ( pEvt:Event ):void { pEvt.target.removeEventListener ( Event.COMPLETE, transfertTermine ); pEvt.target.removeEventListener ( ProgressEvent.PROGRESS, transfertEnCours ); if ( fichiers.length ) suivant(); else dispatchEvent ( new EvenementEnvoiMultiple ( EvenementEnvoiMultiple.TERMINE, historiqueFichiers ) ); } private function transfertEnCours ( pEvt:ProgressEvent ):void { dispatchEvent ( new ProgressionEvenementEnvoiMultiple ( ProgressionEvenementEnvoiMultiple.PROGRESSION, pEvt.bytesLoaded, pEvt.bytesTotal, FileReference ( pEvt.target ) ) ); } } } Il ne reste plus qu?à écouter cet événement auprès de l?objet EnvoiMultiple créé : package org.bytearray.document { import flash.events.DataEvent; import flash.net.URLRequest; import flash.net.URLVariables; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.envoi.EnvoiMultiple; import org.bytearray.evenements.EvenementEnvoiMultiple; import org.bytearray.evenements.ProgressionEvenementEnvoiMultiple; import flash.display.MovieClip; import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.text.TextField; public class Document extends ApplicationDefaut Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 92 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { private var envoyeur:EnvoiMultiple; public var boutonEnvoyer:SimpleButton; public var prechargeur:MovieClip; public var legende:TextField; public function Document () { // création de l'objet EnvoiMultiple envoyeur = new EnvoiMultiple( new URLRequest ("http://www.bytearray.org/as3/upload.php" )); // écoute des événements personnalisés liés au chargement envoyeur.addEventListener ( ProgressionEvenementEnvoiMultiple.PROGRESSION, transfertEnCours ); envoyeur.addEventListener ( EvenementEnvoiMultiple.TERMINE, transfertTermine ); envoyeur.addEventListener ( DataEvent.UPLOAD_COMPLETE_DATA, retourServeur ); boutonEnvoyer.addEventListener ( MouseEvent.CLICK, parcoursFichiers ); } private function retourServeur ( pEvt:DataEvent ):void { var variables:URLVariables = new URLVariables ( pEvt.data ); var retour:Number = Number ( variables.resultat ); legende.text = retour ? "Transfert réussi !" : "Echec de transfert"; } private function transfertEnCours ( pEvt:ProgressionEvenementEnvoiMultiple ):void { // mise à jour de la barre de progression prechargeur.scaleX = pEvt.bytesLoaded / pEvt.bytesTotal; // informations liées au fichier en cours de transfert legende.text = "Transfert en cours : " + pEvt.fichier.name; } private function transfertTermine ( pEvt:EvenementEnvoiMultiple ):void { // indique le nombre de fichiers transférés legende.text = pEvt.historique.length + " fichiers(s) transféré(s)"; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 93 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org } private function parcoursFichiers ( pEvt:MouseEvent ):void { envoyeur.browse(); } } } Vous remarquerez que nous utilisons la classe URLVariables afin de décoder la chaîne encodée URL retournée par le serveur. Nous avons achevé notre classe EnvoiMultiple qui pourra être réutilisée dans tous les projets nécessitant un transfert de fichiers synchronisés. A retenir ? La classe FileReferenceList envoie tous les fichiers en même temps et ne diffuse pas d?événement approprié lorsque tous les fichiers ont été transférés. ? Afin de corriger cela, nous avons créé une classe EnvoiMultiple. Aller plus loin Souvenez-vous, lors du chapitre 10 intitulé Diffusion d?événements personnalisés nous avions entamé la conception d?une classe ChargeurXML pouvant charger un flux XML et diffuser un événement Event.COMPLETE : package { import flash.events.Event; import flash.events.EventDispatcher; import flash.net.URLRequest; public class ChargeurXML extends EventDispatcher { public function ChargeurXML () { } public function charge ( pRequete:URLRequest ):void Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 94 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { } public function chargementTermine ( pEvt:Event ):void { } } } Nous allons la compléter en ajoutant créant un objet URLoader interne : package org.bytearray.xml { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.HTTPStatusEvent; import flash.events.SecurityErrorEvent; import flash.events.EventDispatcher; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; public class ChargeurXML extends EventDispatcher { private var chargeur:URLLoader; private var fluxXML:XML; public function ChargeurXML () { chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.TEXT; chargeur.addEventListener ( Event.OPEN, redirigeEvenement ); chargeur.addEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, redirigeEvenement ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); chargeur.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, redirigeEvenement ); } private function redirigeEvenement ( pEvt:Event ):void { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 95 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org dispatchEvent ( pEvt ); } public function charge ( pRequete:URLRequest ):void { chargeur.load ( pRequete ); } private function chargementTermine ( pEvt:Event ):void { try { fluxXML = new XML ( pEvt.target.data ); } catch ( pErreur:Error ) { trace (pErreur); } dispatchEvent ( pEvt ); } public function get donnees ( ):XML { return fluxXML; } } } Une fois le fichier XML chargé nous diffusons un événement Event.COMPLETE. Ainsi, le code permettant de charger un fichier XML est extrêmement réduit : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.xml.ChargeurXML; import flash.events.Event; import flash.net.URLRequest; public class Document extends ApplicationDefaut { Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 96 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org public function Document () { var chargeur:ChargeurXML = new ChargeurXML (); chargeur.charge ( new URLRequest ("donnees.xml") ); chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); } private function chargementTermine ( pEvt:Event ):void { /* affiche : <MENU> <BOUTON legende="Accueil" couleur="0x887400" vitesse="1" url="accueil.swf"/> <BOUTON legende="Photos" couleur="0x005587" vitesse="1" url="photos.swf"/> <BOUTON legende="Blog" couleur="0x125874" vitesse="1" url="blog.swf"/> <BOUTON legende="Liens" couleur="0x59CCAA" vitesse="1" url="liens.swf"/> <BOUTON legende="Forum" couleur="0xEE44AA" vitesse="1" url="forum.swf"/> </MENU> */ trace( pEvt.target.donnees ); } } } La classe ChargeurXML peut ainsi être utilisée dans de nombreux projets ayant recourt au XML. Si un matin, un membre de votre équipe se plaint de la mise en cache des XML par le navigateur. Assurez-lui, qu?un système anti-cache peut être ajouté à la classe ChargeurXML : package org.bytearray.xml { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.HTTPStatusEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.events.EventDispatcher; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.net.URLRequestHeader; Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 97 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org public class ChargeurXML extends EventDispatcher { private var chargeur:URLLoader; private var fluxXML:XML; private var antiCache:Boolean; public function ChargeurXML ( pAntiCache:Boolean=false ) { antiCache = pAntiCache; chargeur = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.TEXT; chargeur.addEventListener ( Event.OPEN, redirigeEvenement ); chargeur.addEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( HTTPStatusEvent.HTTP_STATUS, redirigeEvenement ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); chargeur.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, redirigeEvenement ); } public function charge ( pRequete:URLRequest ):void { if ( antiCache ) pRequete.requestHeaders.push ( new URLRequestHeader ("pragma", "no-cache") ); chargeur.load ( pRequete ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function chargementTermine ( pEvt:Event ):void { try { fluxXML = new XML ( pEvt.target.data ); } catch ( pErreur:Error ) Chapitre 14 ? Chargement et envoi de données ? version 0.1.1 98 / 98 Thibault Imbert pratiqueactionscript3.bytearray.org { trace (pErreur); } dispatchEvent ( pEvt ); } public function get donnees ( ):XML { return fluxXML; } } } Grace à la classe URLRequestHeader, nous avons modifié l?entête HTTP du lecteur Flash lors du chargement du fichier XML et empêché sa mise en cache par le navigateur. Ainsi chaque développeur souhaitant tirer profit de cette fonctionnalité devra simplement le préciser lors de l?instanciation de l?objet ChargeurXML : // crée un objet ChargeurXML en précisant de jamais mettre en cache les fichiers XML chargés var chargeur:ChargeurXML = new ChargeurXML ( true ); D?autres entêtes HTTP peuvent être utilisées, nous reviendrons sur la classe URLRequestHeader au cours du chapitre 20 intitulé ByteArray. A retenir ? La classe URLRequestHeader permet de modifier les entêtes HTTP du lecteur Flash. Dans le prochain chapitre, nous explorerons les nouveautés apportées par ActionScript 3 en matière d?échanges entre le lecteur Flash et les différents navigateurs. Chapitre 15 ? Communication externe ? version 0.1.1 1 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org 15 Communication Externe CONTENEUR ET CONTENU............................................................................... 1 PASSER DES VARIABLES ................................................................................... 2 INTÉGRATION PAR JAVASCRIPT .............................................................................. 3 LA PROPRIETE PARAMETERS ................................................................................... 5 LES FLASHVARS ................................................................................................... 10 PASSER DES VARIABLES DYNAMIQUES.................................................................. 11 ACCÉDER FACILEMENT AUX FLASHVARS ............................................................. 14 APPELER UNE FONCTION ............................................................................... 17 L?API EXTERNALINTERFACE ........................................................................ 18 APPELER UNE FONCTION EXTERNE DEPUIS ACTIONSCRIPT ................................... 22 APPELER UNE FONCTION ACTIONSCRIPT DEPUIS LE CONTENEUR.......................... 27 COMMUNICATION ET SECURITE............................................................................. 30 Conteneur et contenu Lors du déploiement d?une application Flash sur Internet, nous intégrons généralement celle-ci au sein d?une page conteneur HTML interprétée par différents navigateurs tels Internet Explorer, Firefox, Opera ou autres. Dans d?autres situations, celle-ci peut être intégrée au sein d?une application bureau développée en C#, C++ ou Java. Bien qu?autonome, l?animation lue par le lecteur Flash peut nécessiter des informations provenant de l?application conteneur dans le cas de développement d?applications dynamiques. Avant de s?intéresser à la notion d?échanges, il convient de définir dans un premier temps les deux acteurs concernés par une éventuelle communication : Chapitre 15 ? Communication externe ? version 0.1.1 2 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le conteneur : Une application contenant le SWF. Il peut s?agir d?une page HTML ou d?une application C#, C++ ou autres. ? L?application : Il s?agit du SWF lu par le lecteur Flash. Au cours de ce chapitre, nous allons nous intéresser aux différentes techniques de communication possible entre ces deux acteurs tout en se préoccupant des éventuelles restrictions de sécurité pouvant intervenir. Passer des variables Afin d?introduire la notion d?échanges entre ces deux acteurs, nous allons nous intéresser dans un premier temps au passage de simples variables encodées au format URL. Afin de bien comprendre l?intérêt de tels échanges, mettons nous en situation avec le cas suivant : Vous devez développer un lecteur vidéo qui puisse charger différentes vidéos dynamiques pour un portail destiné aux cinéphiles. Afin de rendre le lecteur le plus dynamique possible, il serait judicieux de pouvoir spécifier en dehors de l?animation quelle bande-annonce jouer. Vous pensez dans un premier temps à la création d?un fichier XML contenant le nom de la vidéo à jouer. Certes, cette technique fonctionnerait mais n?est pas optimisée. Le fichier XML devrait être dupliqué pour chaque lecteur vidéo, rendant le développement rigide et redondant. La solution la plus efficace consiste à passer dynamiquement le nom de la vidéo à jouer depuis la page navigateur contenant le lecteur vidéo. Pour cela, deux techniques existent : La première consiste à passer des variables encodées URL après le nom du fichier au sein du paramètre movie de la balise object et src de la balise embed : <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab #version=9,0,0,0" width="550" height="400" id="chap-15-variables" align="middle"> <param name="allowScriptAccess" value="sameDomain" /> <param name="allowFullScreen" value="false" /> <param name="movie" value="chap-15-variables.swf?maVar1=15&maVar2=50" /><param name="quality" value="high" /><param name="bgcolor" value="#ffffff" /> <embed src="chap-15-variables.swf?maVar1=15&maVar2=50" quality="high" bgcolor="#ffffff" width="550" height="400" name="chap-15-variables" align="middle" allowScriptAccess="sameDomain" allowFullScreen="false" Chapitre 15 ? Communication externe ? version 0.1.1 3 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /> </object> Automatiquement, les variables maVar1 et maVar2 sont accessibles au sein du SWF intitulé chap-15-variables.swf. Cette technique est utilisée lorsque l?animation est intégrée manuellement au sein d?une page conteneur. Dans le cas de Flash CS3 un script JavaScript est généré automatiquement lors de la publication afin d?intégrer dynamiquement l?animation. Flash CS3 intègre un mécanisme d?intégration de l?animation Flash par script JavaScript permettant de ne pas avoir à demander l?autorisation de l?utilisateur afin de lire du contenu Flash. Cette astuce évite d?avoir à activer l?animation en cliquant dessus au sein d?Internet Explorer. En avril 2007, la société Eolas avait obtenu de Microsoft la modification d?Internet Explorer visant à demander obligatoirement l?activation de l?utilisateur lorsqu?un contenu ActiveX était intégré dans une page. Si la page contenant l?animation est directement générée depuis Flash CS3, la technique visant à intégrer les variables directement au sein des balises object et embed ne fonctionnera pas. Il nous faut passer les variables depuis la fonction JavaScript AC_FL_RunContent. A retenir ? L?utilisation de variables encodées au format URL est le moyen le plus simple de passer des données au SWF. ? Lorsque la page conteneur est générée depuis Flash CS3, l?intégration du SWF dans la page est gérée par la fonction JavaScript AC_FL_RunContent. Intégration par JavaScript Lorsque la page conteneur est directement générée depuis Flash CS3, le script suivant est intégré à la page HTML afin d?intégrer le SWF : <script language="javascript"> if (AC_FL_RunContent == 0) { alert("Cette page nécessite le fichier AC_RunActiveContent.js."); } else { AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', Chapitre 15 ? Communication externe ? version 0.1.1 4 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org 'width', '550', 'height', '400', 'src', 'chap-15-variables', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'chap-15-variables', 'bgcolor', '#ffffff', 'name', 'chap-15-variables', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-15-variables', 'salign', '' ); } </script> La fonction AC_FL_RunContent intègre l?animation en ajoutant chaque attribut passé en paramètre. Ainsi, pour passer les variables équivalentes à l?exemple précédent, nous passons les variables au sein du paramètre movie de la fonction AC_FL_RunContent : AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', 'width', '550', 'height', '400', 'src', 'chap-15-external-interface', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'monApplication', 'bgcolor', '#ffffff', 'name', 'monApplication', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-15-variables?maVar1=15&maVar2=50', 'salign', '' ); Dans les précédentes versions d?ActionScript, les variables étaient directement placées sur le scénario principal _root. Chapitre 15 ? Communication externe ? version 0.1.1 5 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org En ActionScript 3, afin d?éviter les conflits de variables, celles-ci ne sont plus disponibles à partir du scénario principal mais depuis la propriété parameters de l?objet LoaderInfo du scénario du SWF. A retenir ? Dans le cas de l?utilisation de la fonction AC_FL_RunContent, les variables doivent être passées au sein du paramètre movie. La propriété parameters Comme nous l?avons vu au cours du chapitre 13 intitulé Chargement de contenu. Chaque SWF contient un objet LoaderInfo associé, contenant différentes informations. Dans le cas de variables passées par le navigateur, les variables passées dynamiquement deviennent des propriétés de l?objet référencé par la propriété parameters de l?objet LoaderInfo. Dans le code suivant nous définissons la classe de document suivante, afin d?accéder aux deux variables passées : package org.bytearray.document { import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { var infos:Object = root.loaderInfo.parameters; infosVariables.appendText ( infos.maVar1 + "\n" ); infosVariables.appendText ( infos.maVar2 + "\n" ); } } } Un champ texte infosVariables est posé sur le scénario principal afin d?afficher les variables réceptionnées. Lorsque des variables sont passées au lecteur Flash, les variables sont copiées au sein de la propriété parameters de l?objet LoaderInfo. Vous remarquez que nous accédons aux variables comme des propriétés de l?objet parameters. Chapitre 15 ? Communication externe ? version 0.1.1 6 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Il est important de noter que les variables sont copiées au sein de l?objet LoaderInfo avant l?exécution du code. Celles-ci sont donc accessibles instantanément. Dans le code précédent nous ciblons de manière explicite la propriété loaderInfo du scénario principal, à l?aide de la propriété root. Dans un contexte de classe du document, l?instance en cours fait référence au scénario principal, nous pouvons donc directement accéder à l?objet LoaderInfo de la manière suivante : package org.bytearray.document { import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { var infos:Object = loaderInfo.parameters; infosVariables.appendText ( infos.maVar1 + "\n" ); infosVariables.appendText ( infos.maVar2 + "\n" ); } } } Au cas où nous ne connaissons pas le nom des variables passées, nous pouvons les énumérer à l?aide d?une boucle for in : package org.bytearray.document { import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { var infos:Object = loaderInfo.parameters; for ( var p:String in infos ) Chapitre 15 ? Communication externe ? version 0.1.1 7 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org { infosVariables.appendText ( p + "\n" ); } } } } Si aucune variable n?est passée, la boucle n?est pas exécutée, aucun contenu n?est affiché dans le champ texte infosVariables. Lors du test de l?animation au sein de Flash CS3, l?animation est lue au sein du lecteur autonome. Aucune variable ne peut donc être donc passée. De la même manière, si les variables sont passées d?une manière non appropriée, nous devons le gérer. Il est donc important de s?assurer que les variables sont bien définies, pour cela nous écrivons le code suivant : package org.bytearray.document { import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { var infos:Object = loaderInfo.parameters; if ( infos.maVar1 != undefined && infos.maVar2 != undefined ) { infosVariables.appendText ( infos.maVar1 + "\n" ); infosVariables.appendText ( infos.maVar2 + "\n" ); } else infosVariables.appendText ( "Les variables ne sont pas disponibles" ); } } } La figure 15-1 illustre le résultat : Chapitre 15 ? Communication externe ? version 0.1.1 8 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 15-1. Variables non accessibles. Si nous testons l?animation au sein d?une page passant correctement les variables, nous obtenons le résultat illustré en figure 15-2 : Figure 15-2. Variables correctement passées. Il est important de noter que les variables sont copiées en tant que chaîne de caractères. Ainsi, si nous souhaitons manipuler les variables afin de faire des opérations mathématiques nous devons au préalable le convertir en nombres. Dans le code suivant, nous tentons d?additionner les deux variables, nous obtenons alors une simple concaténation : infosVariables.appendText ( infos.maVar1 + infos.maVar2 ); Chapitre 15 ? Communication externe ? version 0.1.1 9 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 15-3 illustre le résultat : Figure 15-3. Variables concaténées. Afin d?additionner correctement les variables nous devons au préalable les convertir en tant que nombre : infosVariables.appendText ( String ( Number ( infos.maVar1 ) + Number ( infos.maVar2 ) ) ); La figure 15-4 illustre le résultat : Figure 15-4. Variables additionnées. Cette technique offre en revanche une limitation de volume de données passées propre à chaque navigateur. Depuis le lecteur 6, nous avons la possibilité de passer ces mêmes variables à l?aide d?un nouvel attribut des balises object et embed appelé flashvars. De plus, le passage de variables sans l?utilisation de l?attribut flashvars force le rechargement du SWF empêchant donc sa mise en cache. Notons que si les variables passées sont identiques à plusieurs reprises, il n?y a pas de rechargement forcé. A retenir Chapitre 15 ? Communication externe ? version 0.1.1 10 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les variables passées sont copiées au sein de l?objet retourné par la propriété parameters de l?objet LoaderInfo du scénario principal. ? Les variables sont copiées en tant que chaînes de caractères. ? Il convient de convertir les variables en nombre avant d?effectuer des opérations mathématiques sur chacune d?elles. Les FlashVars L?utilisation des FlashVars est recommandée depuis Flash MX (Flash 6). Pour passer des variables grâce à l?attribut flashvars, il nous suffit d?ajouter l?attribut flashvars en paramètre de la fonction AC_FL_RunContent : AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', 'width', '550', 'height', '400', 'flashvars', 'maVar1=15&maVar2=50', 'src', 'chap-15-external-interface', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'monApplication', 'bgcolor', '#ffffff', 'name', 'monApplication', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-15-external-interface', 'salign', '' ); Si nous testons à nouveau l?animation les variables sont accessibles de la même manière. A retenir Chapitre 15 ? Communication externe ? version 0.1.1 11 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?utilisation des FlashVars est recommandée depuis le lecteur Flash 6. ? Les balises object et embed possèdent un attribut flashvars permettant un passage de variables optimisé. Passer des variables dynamiques Afin d?aller plus loin, nous allons récupérer dynamiquement les variables passées au sein de l?url de l?animation et les récupérer dans notre application. Dans le cas d?un site Flash traditionnel, nous souhaitons pouvoir récupérer les variables passées en GET depuis l?url suivante : http://www.monsite.org/index.php?rubrique=4&langue=fr Le lecteur Flash n?a pas la capacité de les récupérer de manière autonome, nous pouvons utiliser pour cela un script JavaScript ou autre qui se chargera de les passer au SWF grâce à l?attribut flashvars. Pour cela, nous ajoutons au sein de la page conteneur une fonction JavaScript nommée recupVariables : <script language="javascript"> var position = window.location.href.indexOf ("?")+1; var chaine = window.location.href.substr ( position ); if (AC_FL_RunContent == 0) { alert("Cette page nécessite le fichier AC_RunActiveContent.js."); } else { AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', 'width', '550', 'height', '400', 'flashvars', chaine, 'src', 'chap-15-flashvars-dynamique', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'chap-15-flashvars-dynamique', 'bgcolor', '#ffffff', 'name', 'chap-15-flashvars-dynamique', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-15-flashvars-dynamique', 'salign', '' ); //end AC code Chapitre 15 ? Communication externe ? version 0.1.1 12 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org } </script> Nous passons dynamiquement les flashvars en reconstituant une chaîne encodée URL à l?aide des variables récupérées depuis l?URL. Afin de tester si les variables sont bien passées, nous accédons à notre animation en ajoutant les variables encodées URL en fin d?adresse : http://www.monsite.org/index.php?rubrique=4&langue=fr Afin d?éviter les erreurs nous testons si les variables sont définies : package org.bytearray.document { import flash.text.TextField; import flash.display.MovieClip; public class Document extends MovieClip { public function Document () { var infos:Object = loaderInfo.parameters; if ( (infos.rubrique != undefined && infos.langue != undefined) ) { infosVariables.appendText ( "langue = " + infos.langue + "\n" ); infosVariables.appendText ( "rubrique = " + infos.rubrique + "\n" ); } else infosVariables.appendText ( "Les variables ne sont pas disponibles" ); } } } En testant notre animation, nous voyons que les variables sont correctement passées. La figure 15-4 illustre le résultat : Chapitre 15 ? Communication externe ? version 0.1.1 13 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 15-5. FlashVars dynamiques. Souvenez-vous, les variables sont passées en tant que chaîne de caractères. Ainsi, si aucune variable n?est passée dans l?url, la valeur des variables rubrique et langue seront bien différentes de undefined, car elles auront pour valeur "undefined" en tant que chaîne. Pour gérer cela, nous ajoutons la condition suivante : package org.bytearray.document { import flash.text.TextField; import oflash.display.MovieClip; public class Document extends MovieClip { public function Document () { var infos:Object = loaderInfo.parameters; if ( (infos.rubrique != undefined && infos.langue != undefined) && (infos.rubrique != "undefined" && infos.langue != "undefined") ) { infosVariables.appendText ( "langue = " + infos.langue + "\n" ); infosVariables.appendText ( "rubrique = " + infos.rubrique + "\n" ); } else infosVariables.appendText ( "Les variables ne sont pas disponibles" ); } } Chapitre 15 ? Communication externe ? version 0.1.1 14 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org } Ainsi, si nous accédons à l?animation en omettant les variables au sein de l?URL, nous obtenons le message illustré en figure 15-6 : Figure 15-6. Message d?erreur. Dans un contexte réel, il est important de s?assurer que chaque SWF composant le site puisse accéder sans problèmes aux variables passées au SWF principal. Dans la partie suivante, nous allons découvrir comment faciliter le passage de celles-ci au reste de l?application. A retenir ? A l?aide d?un script intégré à la page conteneur, nous pouvons passer des variables dynamiques à l?animation. Accéder facilement aux FlashVars Nous savons que les variables passées à l?animation sont accessibles depuis l?objet LoaderInfo du scénario principal. Afin de garantir un accès simplifié de ces variables aux différents SWF composant notre application, nous allons diffuser un événement personnalisé par l?intermédiaire de la propriété sharedEvents de l?objet LoaderInfo. Souvenez, vous au cours du chapitre 13 intitulé Chargement de contenu, nous avons vu que la propriété sharedEvents était une excellent passerelle de communication entre le SWF chargeant et chargé. Nous définissons dans un premier temps une classe InfosEvenement chargée de transporter les variables : package org.bytearray.evenements { import flash.display.LoaderInfo; import flash.events.Event; Chapitre 15 ? Communication externe ? version 0.1.1 15 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org public class InfosEvenement extends Event { public static const INFOS:String = "infos"; public var infos:LoaderInfo; public function InfosEvenement ( pType:String, pLoaderInfo:LoaderInfo ) { super ( pType, false, false ); infos = pLoaderInfo; } public override function clone ( ):Event { return new InfosEvenement ( type, infos ); } } } Puis nous modifions la classe de document du SWF principal afin de charger dynamiquement le SWF : package org.bytearray.document { import flash.events.Event; import flash.net.URLRequest; import flash.display.Loader; import flash.display.MovieClip; import org.bytearray.evenements.InfosEvenement; public class Document extends MovieClip { private var loader:Loader; public function Document () { loader = new Loader(); loader.contentLoaderInfo.addEventListener ( Event.COMPLETE, onComplete ); loader.load ( new URLRequest ("chap-15-animation.swf") ); Chapitre 15 ? Communication externe ? version 0.1.1 16 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org addChild ( loader ); } private function onComplete ( pEvt:Event ):void { pEvt.target.sharedEvents.dispatchEvent ( new InfosEvenement ( InfosEvenement.INFOS, loaderInfo ) ); } } } Afin de récupérer les variables envoyées, nous associons la classe de document suivante au SWF chargé : package org.bytearray.rubriques { import flash.events.Event; import flash.text.TextField; import flash.display.MovieClip; import org.bytearray.abstrait.ApplicationDefaut; public class Rubrique extends ApplicationDefaut { public var infosVariables:TextField; public function Rubrique () { loaderInfo.sharedEvents.addEventListener ( InfosEvenement.INFOS, infos ); } function infos ( pEvt:InfosEvenement ):void { var infos:Object = pEvt.infos.parameters; if ( (infos.rubrique != undefined && infos.langue != undefined) && (infos.rubrique != "undefined" && infos.langue != "undefined") ) { infosVariables.appendText ( infos.rubrique + "\n" ); infosVariables.appendText ( infos.langue + "\n" ); } else infosVariables.appendText ( "Les variables ne sont pas disponibles" ); Chapitre 15 ? Communication externe ? version 0.1.1 17 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org } } } A la réception des variables la méthode écouteur infos est déclenchée est procède à un affichage des variables si celles-ci sont correctement définies. A retenir ? Afin d?assurer un accès simplifié aux variables, il est intéressant de diffuser un événement depuis le SWF principal aux SWF chargés. Appeler une fonction La communication entre l?application conteneur et le lecteur ne se limite pas à un simple passage de variables encodées URL. Dans le cas d?une page HTML conteneur, il est possible d?appeler une fonction JavaScript depuis ActionScript à l?aide de la fonction navigateToURL. Dans le code suivant, nous ouvrons une fenêtre alert au sein de la page conteneur lorsque nous cliquons sur la scène : package org.bytearray.document { import flash.external.ExternalInterface; import flash.text.TextField; import flash.events.MouseEvent; import flash.net.navigateToURL; import flash.net.URLRequest; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { stage.addEventListener ( MouseEvent.CLICK, click ); } private function click ( pEvt:MouseEvent ):void { navigateToURL ( new URLRequest ("javascript: alert('Un message du lecteur Flash !')"), "_self"); Chapitre 15 ? Communication externe ? version 0.1.1 18 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org } } } En préfixant le nom de la fonction de l?instruction javascript: il est aussi possible d?appeler n?importe quelle fonction JavaScript depuis ActionScript. Dans le code suivant, nous appelons une fonction fonctionJavaScript : navigateToURL ( new URLRequest ("javascript: fonctionJavaScript()" ), "_self"); La fonction navigateToURL est généralement utilisée au sein d?une page conteneur afin de lancer le gestionnaire de mail configuré sur la machine : private function click ( pEvt:MouseEvent ):void { navigateToURL ( new URLRequest ("mailto:bob@groove.com") ); } Bien qu?efficace, cette technique ne permet pas de réceptionner le retour d?une fonction JavaScript ou de passer des types précis en paramètres tels Number ou Boolean. L?API ExternalInterface La communication entre l?application conteneur et le lecteur ne se limite pas à un simple passage de variables encodées URL. Il est aussi possible d?appeler différentes méthodes définies au sein du conteneur depuis ActionScript et inversement. Cette fonctionnalité était assurée auparavant par la méthode fscommand qui se trouve désormais dans le paquetage flash.system. L?utilisation de la fonction fscommand est aujourd?hui dépréciée au profit de l?API ExternalInterface. Afin de pouvoir utiliser celle-ci dans un contexte de navigateur, celui- ci doit prendre en charge les contrôles ActiveX ou l?API NPRuntime. Voici un tableau récapitulatif des différents navigateurs compatible fonctionnant avec l?API ExternalInterface : Navigateur Système d'exploitation Système d'exploitation Chapitre 15 ? Communication externe ? version 0.1.1 19 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Internet Explorer 5.0 et versions ultérieures Windows Netscape 8.0 et versions ultérieures Windows Macintosh Mozilla 1.7.5 et versions ultérieures Windows Macintosh Firefox 1.0 et versions ultérieures Windows Macintosh Safari 1.3 et versions ultérieures Macintosh Tableau 1. Navigateurs compatibles. Voici en détail les trois propriétés de la classe ExternalInterface : ? available : Renvoie l?id de la balise object sous Internet Explorer, ou l?attribut name de la balise embed sous Nestcape, Firefox, Opera ou autres. ? marshallExceptions : Cette propriété définit, si les deux acteurs peuvent recevoir les exceptions de chacun. ? objectID : Permet de savoir si le conteneur est compatible avec l?API ExternalInterface. Dans le code suivant, nous testons si l?application évolue dans un contexte compatible avec l?API ExternalInterface : package org.bytearray.document { import flash.external.ExternalInterface; import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var compatible:TextField; public function Document () { compatible.text = String ( ExternalInterface.available ); } } } Chapitre 15 ? Communication externe ? version 0.1.1 20 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Il convient de toujours tester si le contexte actuel permet l?utilisation de l?API ExternalInterface. Pour cela, nous testons la propriété available avant toute tentative de communication. La propriété objectID peut être aussi utilisée afin de déterminer si l?application évolue au sein du navigateur ou non. Ainsi nous pourrions ajouter une propriété navigateur à la classe ApplicationDefaut permettant de savoir si l?animation évolue au sein du navigateur ou non : package org.bytearray.abstrait { import flash.display.MovieClip; import flash.events.Event; import flash.display.Stage; import flash.external.ExternalInterface; public class ApplicationDefaut extends MovieClip { public static var globalStage:Stage; public static var enLigne:Boolean; public static var navigateur:Boolean; public static var stage:Stage; public static var root:ApplicationDefaut; public function ApplicationDefaut () { ApplicationDefaut.root = this; addEventListener ( Event.ADDED_TO_STAGE, activation ); loaderInfo.addEventListener ( Event.INIT, init ); ApplicationDefaut.navigateur = ExternalInterface.objectID != null; } private function activation ( pEvt:Event ):void { ApplicationDefaut.globalStage = stage; } private function init ( pEvt:Event ):void { ApplicationDefaut.enLigne = pEvt.target.url.match ( new RegExp ("^http://") ) != null; Chapitre 15 ? Communication externe ? version 0.1.1 21 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Puis, dans n?importe quelle classe de document héritant de la classe ApplicationDefaut, nous pouvons savoir facilement si l?animation évolue ou non au sein du navigateur : package org.bytearray.document { import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var compatible:TextField; public function Document () { if ( ApplicationDefaut.navigateur ) compatible.text = "L'animation est lue au sein d'une page web"; else compatible.text = "L'animation est lue hors du navigateur"; } } } Si nous testons l?application hors du navigateur : Chapitre 15 ? Communication externe ? version 0.1.1 22 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 15-7. Détection du contexte. A l?inverse, si l?animation est lue au sein du navigateur, nous le détectons comme l?illustre la figure 15-8 : Figure 15-8. Détection du contexte. Nous allons à présent nous attarder sur l?appel de fonctions externe depuis ActionScript. A retenir ? L?API ExternalInterface est recommandée pour la communication entre ActionScript et JavaScript. ? ExternalInterface remplace les précédentes fonctions fscommand, callFrame et callLabel. Appeler une fonction externe depuis ActionScript Deux méthodes sont disponibles sur la classe ExternalInterface, voici le détail de chacune d?entre elles : Chapitre 15 ? Communication externe ? version 0.1.1 23 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org ? addCallBack : Enregistre un alias pour une fonction ActionScript. L?alias est ensuite utilisé depuis la fonction externe pour exécuter la fonction ActionScript. ? call : Exécute la fonction passée en paramètre au sein du conteneur. Dans la partie suivante, nous allons nous intéresser à l?appel d?une méthode JavaScript depuis ActionScript. La figure 15-9 illustre le concept : Figure 15-09. Méthode statique call. Afin d?exécuter une méthode au sein de l?application conteneur, nous utilisons la méthode statique call de la classe ExternalInterface dont voici la signature : public static function call(functionName:String, ... arguments):* Le premier paramètre concerne le nom de la fonction à exécuter. Les paramètres suivants sont passés en paramètre à la fonction exécutée. Nous allons commencer par un exemple simple, en appelant la méthode direBonjour lorsque nous cliquons sur le bouton executeFonction : package org.bytearray.document { import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.external.ExternalInterface; Chapitre 15 ? Communication externe ? version 0.1.1 24 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var executeFonction:SimpleButton; public function Document () { executeFonction.addEventListener ( MouseEvent.CLICK, declencheAppel ); } private function declencheAppel ( pEvt:MouseEvent ):void { if ( ExternalInterface.available ) { ExternalInterface.call ("direBonjour"); } } } } La fonction JavaScript direBonjour est définie au sein de notre page conteneur : function direBonjour ( ) { alert ("Bonjour !"); } Lorsque nous cliquons sur le bouton, la fonction est déclenchée et ouvre une fenêtre d?alerte comme l?illustre la figure 15-9 : Figure 15-10. Détection du contexte. Chapitre 15 ? Communication externe ? version 0.1.1 25 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Nous pouvons passer dynamiquement des paramètres depuis Flash en spécifiant les paramètres à la méthode call : ExternalInterface.call ("direBonjour", "Message de Flash !"); Nous modifions la fonction direBonjour afin d?accepter un message en paramètre : function direBonjour ( pMessage ) { alert (pMessage); } La figure 15-11 illustre le résultat : Figure 15-11. Détection du contexte. La fonction JavaScript peut retourner une valeur, la valeur sera récupérée à l?appel de la méthode call. Si nous modifions la fonction JavaScript afin que celle-ci renvoie le total des deux paramètres passés : function calculTotal ( p1, p2 ) { return p1 + p2; } Nous affichons dans le champ texte total, le retour de l?appel de la méthode call : package org.bytearray.document { import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.external.ExternalInterface; import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { Chapitre 15 ? Communication externe ? version 0.1.1 26 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org public var executeFonction:SimpleButton; public var total:TextField; public function Document () { executeFonction.addEventListener ( MouseEvent.CLICK, declencheAppel ); } private function declencheAppel ( pEvt:MouseEvent ):void { if ( ExternalInterface.available ) { total.text = ExternalInterface.call ("calculTotal", 10, 15); } } } } La figure 15-12 illustre le résultat : Figure 15-12. Détection du contexte. Le code précédent illustre un des avantages de l?API ExternalInterface concernant le passage de données typées. Contrairement à la fonction fscommand ou navigateToURL, nous ne sommes pas à limités au passage de chaînes de caractères avec l?API ExternalInterface. Nous pouvons passer entre JavaScript et ActionScript des données de type Number, String, et Boolean. Nous allons maintenant communiquer dans l?autre sens en appelant depuis l?application conteneur une fonction ActionScript. A retenir Chapitre 15 ? Communication externe ? version 0.1.1 27 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode call permet d?exécuter une fonction définie au sein du conteneur depuis ActionScript. ? Dans le cas de l?utilisation d?une fonction JavaScript, des types comme Number, Boolean et String peuvent être échangés. Appeler une fonction ActionScript depuis le conteneur De la même manière, nous pouvons déclencher une fonction ActionScript depuis une fonction définie au sein du conteneur. Afin d?enregistrer une fonction ActionScript auprès d?une fonction du conteneur, nous utilisons la méthode addCallback, dont voici la signature : public static function addCallback(functionName:String, closure:Function):void La figure 15-13 illustre l?idée : Figure 15-13. Méthode statique addCallback. Voici le détail des deux paramètres de la méthode addCallback : ? functionName : Il s?agit de l?identifiant de la fonction depuis la page conteneur. Nous devrons utiliser cette chaîne pour exécuter la fonction passée au sein du paramètre closure. ? closure : La fonction à déclencher depuis la page conteneur. Dans le code suivant, nous enregistrons une fonction maFunction à l?aide de l?alias aliasFonction : Chapitre 15 ? Communication externe ? version 0.1.1 28 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org package org.bytearray.document { import flash.external.ExternalInterface; import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var messageJavascript:TextField; public function Document () { // enregistre l'alias "aliasFonction" vers la méthode maFunction ExternalInterface.addCallback ( "aliasFonction", maFunction ); } private function maFunction ( pMessage:String ):void { // nous affichons le message reçu depuis JavaScript messageJavascript.text = pMessage; } } } Afin de pouvoir appeler une fonction ActionScript nous devons ensuite donner un nom à notre application grâce aux attributs id et name : AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', 'width', '550', 'height', '400', 'src', 'chap-15-external-interface', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'monApplication', 'bgcolor', '#ffffff', 'name', 'monApplication', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-15-external-interface', Chapitre 15 ? Communication externe ? version 0.1.1 29 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org 'salign', '' ); Puis au sein du conteneur, nous appelons la fonction maFunction grâce à l?alias aliasFonction : function recupAnimation ( pAnim ) { if (navigator.appName.indexOf("Microsoft") != -1) return window[pAnim]; else return document[pAnim]; } function declencheFonctionActionScript ( ) { recupAnimation("monApplication").aliasFonction("Message de JavaScript !"); } La fonction recupAnimation permet de cibler l?animation selon le type de navigateur utilisé. Nous passons le nom de l?animation à celle- ci, qui nous renvoie l?animation intégrée dans la page, sur laquelle nous appelons la fonction grâce à l?alias passé à la méthode addCallback. Il ne nous reste plus qu?à déclencher la fonction declencheFonctionActionScript lors du clic bouton : <input type="button" name="monBouton" value="Exécute fonction ActionScript" onClick=" declencheFonctionActionScript()"> La figure 15-14 illustre le résultat : Figure 15-14. Méthode ActionScript exécutée. Comme vous pouvez l?imaginer, la notion de communication entre le lecteur Flash et l?application conteneur est soumise à des restrictions de sécurité. Chapitre 15 ? Communication externe ? version 0.1.1 30 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Nous allons nous intéresser dans la partie suivante, aux différentes restrictions possibles. A retenir ? La méthode addCallback permet d?exécuter une fonction ActionScript depuis l?application conteneur. ? Dans le cas de l?utilisation d?une fonction JavaScript, des types comme Number, Boolean et String peuvent être échangés. Communication et sécurité Afin que les deux acteurs puissent communiquer, nous devons nous intéresser à la valeur passée à l?attribut allowScriptAccess. La fonction AC_FL_RunContent intègre un paramètre allowScriptAccess régissant la communication entre l?application conteneur et le lecteur Flash : AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', 'width', '550', 'height', '400', 'src', 'chap-15-external-interface', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'monApplication', 'bgcolor', '#ffffff', 'name', 'monApplication', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-15-external-interface', 'salign', '' ); Voici les trois valeurs possibles de l?attribut allowScriptAccess : ? always : La communication entre le l?application conteneur et ActionScript est toujours possible. ? sameDomain : La communication entre l?application conteneur et ActionScript est possible, uniquement si la page conteneur et le SWF évoluent dans le même domaine. ? never : La communication entre le l?application conteneur et ActionScript est impossible. Chapitre 15 ? Communication externe ? version 0.1.1 31 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Nous pourrions nous demander dans quels cas l?utilisation de l?attribut allowScriptAccess serait nécessaire. Imaginons le scénario suivant : Au sein d?un forum, les utilisateurs ont la possibilité d?intégrer leurs avatars ou signature à partir d?animations Flash. Certaines d?entre elles pourraient scripter la page du forum provoquant alors un dysfonctionnement. Afin de réguler cela, nous pourrions passer la valeur never à l?attribut allowScriptAccess empêchant toute communication entre les pages du forum et les avatars ou signatures. Dans le code suivant, nous passons l?attribut allowScriptAccess à never : AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version= 9,0,0,0', 'width', '550', 'height', '400', 'src', 'chap-15-external-interface', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'monApplication', 'bgcolor', '#ffffff', 'name', 'monApplication', 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','never', 'movie', 'chap-15-external-interface', 'salign', '' ); Si nous tentons d?appeler à l?aide de la méthode call une fonction définie au sein de la page conteneur, une exception de type SecurityError est levée. Chapitre 15 ? Communication externe ? version 0.1.1 32 / 32 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 15-15. Restriction de sécurité. A l?inverse, pour que la page conteneur puisse scripter un SWF provenant d?un domaine différent, nous pouvons utiliser la méthode allowDomain de la classe Security au sein du SWF à scripter. Pour plus d?informations concernant le modèle de sécurité du lecteur Flash, reportez vous à la partie Modèle de sécurité du lecteur Flash du chapitre 13 intitulé Chargement de contenu. A retenir ? La communication entre le lecteur Flash et l?application conteneur est soumise au modèle de sécurité du lecteur Flash. ? L?attribut allowScriptAccess permet d?autoriser ou non le lecteur Flash à scripter la page conteneur. Chapitre 16 ? Le texte ? version 0.1.1 1 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org 16 Le texte LE TEXTE DANS FLASH...................................................................................... 1 AFFICHER DU TEXTE DANS FLASH........................................................................... 2 AFFICHER DU TEXTE EN PROGRAMMANT................................................................. 5 LES TYPES DE CHAMP TEXTE ................................................................................. 13 FORMATAGE DU TEXTE .................................................................................. 15 RENDU HTML...................................................................................................... 15 LA CLASSE TEXTFORMAT ..................................................................................... 17 ETENDRE LA CLASSE TEXTFIELD .......................................................................... 24 LA CLASSE STYLESHEET....................................................................................... 30 MODIFIER LE CONTENU D?UN CHAMP TEXTE ........................................ 34 REMPLACER DU TEXTE.......................................................................................... 37 L?EVENEMENT TEXTEVENT.LINK ............................................................... 39 CHARGER DU CONTENU EXTERNE ............................................................. 44 EXPORTER UNE POLICE DANS L?ANIMATION ......................................... 49 CHARGER DYNAMIQUEMENT UNE POLICE ............................................................. 53 DETECTER LES COORDONNEES DE TEXTE .............................................. 57 CREER UN EDITEUR DE TEXTE..................................................................... 68 Le texte dans Flash La gestion du texte dans le lecteur Flash a souvent été source de discussions au sein de la communauté. Quelque soit le type d?application produite, l?utilisation du texte s?avère indispensable. Le lecteur Flash 8 a permit l?introduction d?un nouveau moteur de rendu du texte appelé Saffron. Ce dernier améliorait nettement la lisibilité du texte dans une taille réduite et offrait un meilleur contrôle sur le niveau de lissage des polices. Chapitre 16 ? Le texte ? version 0.1.1 2 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org ActionScript 3 n?intègre pas de nouveau moteur de rendu, mais enrichit remarquablement les fonctionnalités offertes par la classe TextField et ouvre de nouvelles possibilités. Trois classes permettent de représenter du texte en ActionScript 3 : ? flash.text.StaticText : la classe StaticText représente les champs texte statiques créés depuis l?environnement auteur ; ? flash.text.TextField : la classe TextField représente les champs texte dynamiques créés depuis l?environnement auteur ainsi que la création de champs texte par programmation ; ? flash.text.TextSnapShot : la classe TextSnapshot permet de travailler avec le contenu d?un champ texte statique créé depuis l?environnement auteur. Bien que ces classes ne résident pas dans le paquetage flash.display, celles-ci héritent de la classe DisplayObject. Nous allons nous intéresser au cours de ce chapitre à quelques cas pratiques d?utilisation du texte qui auraient été difficilement réalisables sans l?utilisation d?ActionScript 3. Afficher du texte dans Flash Afin de nous familiariser avec le texte, nous allons créer un simple champ texte statique au sein d?un nouveau document comme l?illustre la figure 16-1 : Figure 16-1. Champ texte statique. Il est impossible d?attribuer un nom d?occurrence à un champ texte statique, ActionScript 3 nous permet cependant d?accéder au champ texte en utilisant la méthode getChildAt : // affiche : [object StaticText] trace( getChildAt ( 0 ) ); Lorsqu?un champ texte statique est créé depuis l?environnement auteur, celui-ci est de type StaticText. Cette classe définit une seule et unique propriété text en lecture seule permettant de récupérer le contenu du champ : var champTexte:StaticText = StaticText ( getChildAt ( 0 ) ); Chapitre 16 ? Le texte ? version 0.1.1 3 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : Contenu statique trace( champTexte.text ); Si nous tentons d?instancier la classe StaticText : var monChamp:StaticText = new StaticText(); Une erreur à l?exécution de type ArgumentError est levée : ArgumentError: Error #2012: Impossible d'instancier la classe StaticText Il est important de noter que seul l?environnement auteur de Flash CS3 permet la création d?instance de la classe StaticText. Bien que celle-ci ne définisse qu?une seule propriété, la classe StaticText hérite de la classe DisplayObject. Il est donc possible de modifier la présentation du champ. Dans le code suivant nous modifions la rotation du texte, ainsi que l?opacité du champ : var champTexte:StaticText = StaticText ( getChildAt ( 0 ) ); champTexte.rotation = 20; champTexte.alpha = .5; La figure 16-2 illustre le résultat : Figure 16-2. Présentation du champ texte statique. Dans le cas de champs texte dynamiques, assurez-vous d?avoir bien intégré les contours de police afin de pouvoir faire subir une rotation au texte sans que celui-ci ne disparaisse. Nous reviendrons très bientôt sur cette notion. Dans les précédentes versions d?ActionScript il était impossible d?accéder ou de modifier directement la présentation d?un champ texte statique créé depuis l?environnement auteur. En modifiant le type du champ, en texte dynamique ou de saisie, nous pouvons lui donner un nom d?occurrence comme l?illustre la figure 16-3 : Chapitre 16 ? Le texte ? version 0.1.1 4 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-3. Champ texte dynamique. A la compilation, une variable du même nom est créée afin de référencer notre champ texte. Nous ciblons ce dernier à travers le code suivant : // affiche : [object TextField] trace( legende ); Nous pouvons remarquer que le type du champ n?est plus StaticText mais TextField. Contrairement à la classe StaticText, les différentes propriétés et méthodes définies par la classe TextField permettent une manipulation avancée du texte : legende.text = "Nouveau contenu"; // affiche : 56 trace( legende.textWidth ); // affiche : 0 trace( legende.textColor ); // affiche : dynamic trace( legende.type ); Afin de continuer notre exploration du texte au sein d?ActionScript 3, nous allons nous intéresser à présent à la création de texte dynamique. A retenir Chapitre 16 ? Le texte ? version 0.1.1 5 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org ? Trois classes peuvent être utilisées afin de manipuler du texte : StaticText, TextField, TextSnapShot. ? ActionScript 3 n?intègre pas de nouveau moteur de rendu mais enrichit les capacités de la classe TextField. ? Les champs texte statique sont de type StaticText. ? Seul l?environnement auteur de Flash CS3 permet la création de champ texte de type StaticText. ? Il est impossible d?instancier manuellement la classe StaticText. ? Les champs texte dynamique et de saisie sont de type TextField. Afficher du texte en programmant Lorsque nous devons afficher du texte par programmation, nous utilisons la classe flash.text.TextField. Nous allons pour démarrer, créer un simple champ texte et y ajouter du contenu. La classe TextField s?instancie comme tout autre DisplayObject à l?aide du mot clé new : var monTexte:TextField = new TextField(); monTexte.text = "Bonjour !"; Il est important de noter que grâce au nouveau mode d?instanciation des DisplayObject, il n?est plus nécessaire de disposer d?une instance de MovieClip afin de créer un champ texte. Rappelez-vous, la méthode createTextField présente en ActionScript et 2 était définie sur la classe MovieClip. Il était donc impossible de créer un champ texte sans clip d?animation. Une fois le champ texte créé nous pouvons l?afficher en l?ajoutant à la liste d?affichage : var monTexte:TextField = new TextField(); monTexte.text = "Bonjour !"; addChild ( monTexte ); Si nous testons le code précédent nous obtenons le résultat illustré en figure 16-4 : Chapitre 16 ? Le texte ? version 0.1.1 6 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-4. Contenu texte. La propriété text de l?objet TextField nous permet d?affecter du contenu, nous allons découvrir très vite de nouvelles propriétés et méthodes liées à l?affectation de contenu. Par défaut, la taille d?un champ texte créé par programmation est de 100 pixels en largeur et en hauteur : var monTexte:TextField = new TextField(); monTexte.text = "Bonjour !"; addChild ( monTexte ); // affiche : 100 100 trace( monTexte.width, monTexte.height ); Afin de vérifier cela visuellement, nous pouvons activer la propriété border de l?objet TextField qui ajoute un contour en bordure du champ texte : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Bonjour !"; addChild ( monTexte ); La figure 16-5 illustre les dimensions d?un champ texte par défaut : Chapitre 16 ? Le texte ? version 0.1.1 7 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-5. Dimensions par défaut du champ texte. Si le contenu dépasse cette surface par défaut, le texte est tronqué. Dans le code suivant nous modifions le contenu du champ afin de le faire déborder : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Bonjour, voici du contenu qui déborde !"; addChild ( monTexte ); Le texte est alors tronqué comme l?illustre la figure 16-6 : Figure 16-6. Texte tronqué. Afin que le texte revienne à la ligne automatiquement nous activons la propriété wordWrap qui par défaut est définie à false : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Bonjour, voici du texte avec un retour à la ligne automatique !"; monTexte.wordWrap = true; addChild ( monTexte ); La figure 16-7 illustre le résultat : Chapitre 16 ? Le texte ? version 0.1.1 8 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-7. Texte contraint avec retour à la ligne automatique. A l?inverse si nous souhaitons que le champ s?adapte automatiquement au contenu, nous utilisons la propriété autoSize. En plus d?assurer un redimensionnement automatique, celle-ci permet de justifier le texte dans différentes positions. La propriété autoSize prend comme valeur, une des quatre propriétés constantes de la classe TextFieldAutoSize : ? TextFieldAutoSize.CENTER : le texte est justifié au centre ; ? TextFieldAutoSize.LEFT : le texte est justifié à gauche ; ? TextFieldAutoSize.NONE : aucune justification du texte ; ? TextFieldAutoSize.RIGHT : le texte est justifié à droite ; Dans le code suivant nous justifions le texte à gauche du champ : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Bonjour, voici du contenu qui ne déborde plus !"; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); La figure 16-8 illustre le résultat : Figure 16-8. Redimensionnement automatique du champ. Cette fonctionnalité est couramment utilisée pour la mise en forme du texte au sein de paragraphes. Nous l?utiliserons lorsque nous développerons un éditeur de texte à la fin de ce chapitre. Si nous souhaitons ajouter une contrainte de dimensions afin de contenir le texte au sein d?un bloc spécifique, nous pouvons affecter au champ une largeur et une hauteur spécifique. Un retour à la ligne Chapitre 16 ? Le texte ? version 0.1.1 9 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org automatique devra être ajouté, tout en s?assurant de ne pas spécifier de redimensionnement par la propriété autoSize. Pour cela nous utilisons les propriétés width et height et wordWrap de l?objet TextField : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Le texte évolue dans un paragraphe d?une largeur et hauteur de 250 pixels maximum. Si le texte dépasse, un retour à la ligne est ajouté automatiquement."; monTexte.width = 250; monTexte.height = 250; monTexte.wordWrap = true; addChild ( monTexte ); Dans le code précédent, le texte est contraint à un paragraphe d?une largeur et hauteur de 250 pixels maximum. La figure 16-9 illustre le bloc de texte : Figure 16-9. Retour à la ligne automatique. Si nous réduisons la largeur du champ texte, le texte est alors contraint d?évoluer dans cette surface : var monTexte:TextField = new TextField(); monTexte.border = true; Chapitre 16 ? Le texte ? version 0.1.1 10 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org monTexte.text = "Le texte évolue dans un paragraphe d?une largeur et hauteur de 120 pixels maximum. Si le texte dépasse, un saut de ligne est ajouté automatiquement."; monTexte.width = 120; monTexte.height = 120; monTexte.wordWrap = true; addChild ( monTexte ); La figure 16-10 illustre le résultat : Figure 16-10. Champ texte restreint. Quelles que soient les dimensions affectées au champ texte, si nous activons le redimensionnement automatique, le lecteur Flash affichera totalement le texte, quitte à étendre la hauteur du champ. Dans le code suivant, nous réduisons les dimensions du champ texte : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Le texte évolue dans un paragraphe d?une largeur et hauteur de 120 pixels maximum. Si le texte dépasse, un saut de ligne est ajouté automatiquement."; monTexte.width = 120; monTexte.height = 30; monTexte.wordWrap = true; addChild ( monTexte ); La figure 16-11 illustre le rendu : Chapitre 16 ? Le texte ? version 0.1.1 11 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-11. Texte tronqué. Si nous ajoutons un ajustement automatique par la propriété autoSize, le lecteur Flash conserve une largeur maximale de 120 pixels mais augmente la hauteur du champ afin d?afficher le contenu total : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Le texte évolue dans un paragraphe d?une largeur et hauteur de 120 pixels maximum. Si le texte dépasse, un saut de ligne est ajouté automatiquement."; monTexte.width = 120; monTexte.height = 30; monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.wordWrap = true; addChild ( monTexte ); La figure 16-12 illustre le comportement : Figure 16-12. Redimensionnement automatique. Nous pouvons dynamiquement modifier les dimensions du champ afin de comprendre comment le lecteur ajuste le contenu du champ : var monTexte:TextField = new TextField(); Chapitre 16 ? Le texte ? version 0.1.1 12 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org monTexte.border = true; monTexte.text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Pellentesque at metus a magna bibendum semper. Suspendisse id mauris. Duis consequat dolor et odio. Integer euismod enim ut nulla. Sed quam. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer lobortis. In non erat. Sed ac dui a arcu ultrices aliquam. Aenean neque neque, vulputate ac, dictum eu, vestibulum ut, enim. Sed quis eros. Curabitur eu odio ac nisi suscipit venenatis. Duis ultrices viverra sapien. Fusce interdum, felis eget mollis varius, enim sem imperdiet leo, sed sagittis turpis odio sed quam. Phasellus ac orci. Morbi vestibulum, sem at cursus auctor, metus odio suscipit ipsum, ac sodales erat mi ac velit. Nullam tempus iaculis sem."; monTexte.wordWrap = true; addChild ( monTexte ); stage.addEventListener ( MouseEvent.MOUSE_MOVE, ajustement ); function ajustement ( pEvt:MouseEvent ):void { monTexte.width = pEvt.stageX; monTexte.height = pEvt.stageY; pEvt.updateAfterEvent(); } La figure 16-13 illustre le résultat : Figure 16-13. Taille du paragraphe dynamique. Nous utilisons la méthode updateAfterEvent afin de forcer le rafraîchissement du lecteur. Chapitre 16 ? Le texte ? version 0.1.1 13 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsqu?un champ texte est créé, celui-ci est par défaut éditable permettant une sélection du texte. Si nous souhaitons rendre le champ non sélectionnable nous passons la propriété selectable à false : monTexte.selectable = false; Nous allons nous intéresser au cours de la prochaine partie aux différents types de champs texte dynamiques existants. A retenir ? Un champ texte créé par programmation possède une taille par défaut de 100 pixels en largeur et hauteur. ? Les propriétés width et height permettent de définir la taille d?un champ texte. ? La propriété wordWrap permet d?activer le retour à la ligne automatique. ? Afin d?adapter le champ texte dynamique au contenu, nous utilisons la propriété autoSize et l?une des quatres constantes de la classe TextFieldAutoSize. ? La propriété autoSize permet aussi de définir l?ajustement du texte au sein du champ. ? Si un dimensionnement automatique a été spécifié et que le champ texte est trop petit, le lecteur Flash étend le champ dans le sens de la hauteur pour afficher la totalité du texte. Les types de champ texte Deux comportements existent pour les champs texte dynamiques. Il est possible par la propriété type de récupérer ou de modifier le comportement du champ. Deux types de champs texte dynamiques sont définis par la classe TextFieldType : ? TextFieldType.DYNAMIC : le champ texte et de type dynamique. ? TextFieldType.INPUT : le champ texte est de type saisie. Par défaut, un champ texte créé à partir de la classe TextField est considéré comme dynamic : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.text = "Voici du contenu qui ne déborde plus !"; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); Chapitre 16 ? Le texte ? version 0.1.1 14 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : dynamic trace( monTexte.type ); // affiche : true trace( monTexte.type == TextFieldType.DYNAMIC ); Dans ce cas, celui ci peut être manipulé par programmation aussi bien au niveau de sa présentation qu?au niveau de son contenu. Afin de créer un champ texte de saisie, nous modifions la propriété type et passons la chaîne TextFieldType.INPUT : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.type = TextFieldType.INPUT; monTexte.text = "Voici du contenu qui ne débordre plus ! Dans un champ texte de saisie !"; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); Le champ texte permet désormais la saisie du texte à la volée : Figure 16-14. Champ texte auto-adapté. Notons que dans le code précédent, nous ne pouvons sauter des lignes manuellement. Afin d?activer cette fonctionnalité, nous utilisons le mode multi-lignes à l?aide de la propriété multiline : var monTexte:TextField = new TextField(); monTexte.border = true; monTexte.multiline = true; monTexte.type = TextFieldType.INPUT; monTexte.text = "Bonjour, voici du contenu qui ne débordre plus !"; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); Dans la prochaine partie, nous allons nous intéresser aux différentes techniques permettant la modification du texte. Chapitre 16 ? Le texte ? version 0.1.1 15 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Formatage du texte ActionScript 3 dispose de trois techniques assurant le formatage du texte, voici un récapitulatif de chacune d?entre elles : ? Le texte HTML : de simples balises HTML peuvent être intégrées au contenu texte afin d?assurer son formatage. ? La classe flash.text.TextFormat : à l?aide d?un objet TextFormat, le texte peut être mis en forme aisément et rapidement. ? La classe flash.text.StyleSheet : la classe StyleSheet permet la création d?une feuille de style CSS, idéale pour la mise en forme de contenu conséquent. Dans la mesure où nous affectons du contenu, le formatage du texte ne concerne que les champs texte dynamique ou de saisie. Rappelez vous qu?il est impossible de modifier le contenu d?un champ StaticText ou TextSnapshot. Rendu HTML Nous allons nous intéresser dans un premier temps à une première technique de formatage, par balises HTML, à l?aide de la propriété htmlText. Consultez l?aide de Flash CS3 pour la liste des balises HTML gérées par le lecteur Flash. Dans le code suivant, nous créons un simple champ texte : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte !"; addChild ( monTexte ); Si nous accédons à la propriété text, le lecteur Flash renvoie la chaîne de caractères sans aucune information de formatage : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte au format HTML"; addChild ( monTexte ); // affiche : Voici du texte au format HTML trace( monTexte.text ); Chapitre 16 ? Le texte ? version 0.1.1 16 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org A l?inverse, si nous accédons au contenu, par l?intermédiaire de la propriété htmlText, le lecteur renvoie le texte accompagné des différentes balises HTML utilisées en interne : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte au format HTML"; addChild ( monTexte ); /* affiche : <P ALIGN="LEFT"><FONT FACE="Times New Roman" SIZE="12" COLOR="#000000" LETTERSPACING="0" KERNING="0">Voici du texte au format HTML</FONT></P> */ trace( monTexte.htmlText ); En modifiant le couleur du texte à l?aide de la propriété textColor, nous remarquons que l?attribut color de la balise <font> est automatiquement mis à jour : var monTexte:TextField = new TextField(); monTexte.textColor = 0x990000; monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte au format HTML"; addChild ( monTexte ); /* affiche : <P ALIGN="LEFT"><FONT FACE="Times New Roman" SIZE="12" COLOR="#990000" LETTERSPACING="0" KERNING="0">Voici du texte au format HTML</FONT></P> */ trace( monTexte.htmlText ); Nous affectons ainsi du contenu HTML de manière indirecte, par l?intermédiaire des différentes propriétés de la classe TextField. En utilisant la propriété htmlText, les balises sont automatiquement interprétées : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "Voici du texte au format <b>HTML</b>"; addChild ( monTexte ); La figure 16-15 illustre le rendu : Chapitre 16 ? Le texte ? version 0.1.1 17 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-15. Champ texte HTML formaté. Notons que la propriété html de la classe TextField n?existe plus en ActionScript 3. L?affectation du texte par la propriété htmlText active automatiquement l?interprétation des balises HTML. Nous pouvons changer la couleur du texte en utilisant l?attribut color de la balise <font> : monTexte.htmlText = "Voici du texte <font color='#FF0000'>rouge</font> au format <b>HTML</b>"; L?affectation de contenu HTML s?avère extrêmement pratique dans la mesure où le contenu texte possède au préalable toutes les balises liées à sa présentation. En revanche, une fois le contenu affecté, il s?avère difficile de modifier la mise en forme du texte à l?aide des balises HTML. Pour cela, nous préférerons généralement l?utilisation d?un objet TextFormat. La classe TextFormat L?objet TextFormat a été conçu pour représenter la mise en forme du texte. Celui-ci doit être considéré comme un outil de formatage. En réalité, l?objet TextFormat génère automatiquement les balises HTML appropriées afin de styliser le texte. La classe TextFormat bénéficie ainsi d?un grand nombre de propriétés permettant une mise en forme du texte avancée. Consultez l?aide de Flash CS3 pour la liste des propriétés liées à la mise en forme définie par la classe TextFormat. La première étape consiste à créer l?objet TextFormat, puis de définir les propriétés de mise en forme : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte mis en forme"; addChild ( monTexte ); // création de l'objet TextFormat ( mise en forme ) Chapitre 16 ? Le texte ? version 0.1.1 18 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org var miseEnForme:TextFormat = new TextFormat(); // activation du style gras miseEnForme.bold = true; // modification de la taille du texte miseEnForme.size = 14; // modification de la police miseEnForme.font = "Verdana"; Afin d?appliquer cette mise en forme, nous devons à present appeler la la méthode setTextFormat de la classe TextField dont voici la signature : public function setTextFormat(format:TextFormat, beginIndex:int = -1, endIndex:int = -1):void Analysons chacun des paramètres : ? format : l?objet TextFormat déterminant la mise en forme du texte. ? beginIndex : la position du caractère de départ auquel appliquer la mise en forme, la valeur par défaut est -1. ? endIndex : la position du caractère de fin auquel appliquer la mise en forme, la valeur par défaut est -1. Dans le code suivant, nous appliquons la mise en forme à la totalité du champ texte : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte mis en forme"; addChild ( monTexte ); // création de l'objet TextFormat ( mise en forme ) var miseEnForme:TextFormat = new TextFormat(); // activation du style gras miseEnForme.bold = true; // modification de la taille du texte miseEnForme.size = 14; // modification de la police miseEnForme.font = "Verdana"; // application de la mise en forme monTexte.setTextFormat( miseEnForme ); La figure 16-16 illustre le résultat : Chapitre 16 ? Le texte ? version 0.1.1 19 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-16. Mise en forme du texte par l?objet TextFormat. En spécifiant les positions des caractères de début et fin, nous pouvons affecter le style à une partie du texte seulement : // application de la mise en forme à une partie du texte monTexte.setTextFormat( miseEnForme, 22, 27); Comme l?illustre la figure 16-17, seul le mot forme est affecté : Figure 16-17. Mise en forme partielle du texte. En interne, l?affectation de la mise en forme genère les balises HTML appropriées : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Voici du texte mis en forme"; addChild ( monTexte ); // création de l'objet TextFormat ( mise en forme ) var miseEnForme:TextFormat = new TextFormat(); // activation du style gras miseEnForme.bold = true; // modification de la taille du texte miseEnForme.size = 14; // modification de la police miseEnForme.font = "Verdana"; /* affiche : <P ALIGN="LEFT"><FONT FACE="Times New Roman" SIZE="12" COLOR="#000000" LETTERSPACING="0" KERNING="0">Voici du texte au format HTML</FONT></P> */ trace( monTexte.htmlText ); // application de la mise en forme monTexte.setTextFormat( miseEnForme, 22, 27 ); Chapitre 16 ? Le texte ? version 0.1.1 20 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org /* affiche : <P ALIGN="LEFT"><FONT FACE="Times New Roman" SIZE="12" COLOR="#000000" LETTERSPACING="0" KERNING="0">Voici du texte au format <FONT FACE="Verdana" SIZE="14"><B>HTML</B></FONT></FONT></P> */ trace( monTexte.htmlText ); Il est important de noter que lors de l?affectation de contenu par la propriété text ou htmlText, le lecteur crée automatiquement en interne un objet TextFormat associé. Il convient donc d?appeler la méthode setTextFormat après l?affectation du contenu, au risque de voir la mise en forme préalablement appliquée écrasée. En revanche, une fois la méthode setTextFormat appelée, tout ajout de texte par la méthode appendText conserve la mise en forme en cours. Dans le code suivant, le texte est affecté après l?application de la mise en forme, celle-ci est donc écrasée : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); // création de l'objet TextFormat ( mise en forme ) var miseEnForme:TextFormat = new TextFormat(); // activation du style gras miseEnForme.bold = true; // modification de la taille du texte miseEnForme.size = 14; // modification de la police miseEnForme.font = "Verdana"; // application de la mise en forme monTexte.setTextFormat( miseEnForme ); // le contenu est affecté après l'application de la mise en forme monTexte.text = "Voici du texte mis en forme"; La figure 16-18 illustre le résultat : Chapitre 16 ? Le texte ? version 0.1.1 21 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-18. Mise en forme du texte inchangée. Notons que si nous utilisons la méthode appendText après l?application de la mise en forme, celle-ci est conservée. En revanche, si le contenu est remplaçé, la mise en forme est perdue. ActionScript 3 intègre une nouvelle propriété defaultTextFormat permettant de remédier à cela en définissant un style par défaut : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); // création de l'objet TextFormat ( mise en forme ) var miseEnForme:TextFormat = new TextFormat(); // activation du style gras miseEnForme.bold = true; // modification de la taille du texte miseEnForme.size = 14; // modification de la police miseEnForme.font = "Verdana"; // application d'une mise en forme par défaut monTexte.defaultTextFormat = miseEnForme; // tout contenu affecté prend la mise en forme par défaut monTexte.text = "Voici du texte mis en forme"; Tout texte ajouté par la suite prendra automatiquement la mise en forme définie par l?objet miseEnForme. Si nous souhaitons modifier le style par défaut, nous pouvons affecter à l?aide de la méthode setTextFormat une mise en forme spécifique à l?aide d?un autre objet TextFormat : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); // création de l'objet TextFormat ( mise en forme ) var miseEnForme:TextFormat = new TextFormat(); // activation du style gras miseEnForme.bold = true; // modification de la taille du texte miseEnForme.size = 14; // modification de la police miseEnForme.font = "Verdana"; // application d'une mise en forme par défaut monTexte.defaultTextFormat = miseEnForme; Chapitre 16 ? Le texte ? version 0.1.1 22 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org // tout contenu affecté prend la mise en forme par défaut monTexte.text = "Voici du texte mis en forme"; // création de l'objet TextFormat ( mise en forme ) var autreMiseEnForme:TextFormat = new TextFormat(); // modification de la police autreMiseEnForme.font = "Arial"; // modification de la taille autreMiseEnForme.size = 18; // affectation partielle de la nouvelle mise en forme monTexte.setTextFormat( autreMiseEnForme, 22, 27 ); La figure 16-19 illustre le résultat : Figure 16-19. Mise en forme du texte partielle. La classe TextField définit une autre méthode très pratique en matière de mise en forme du texte nommée getTextFormat dont voici la signature : public function getTextFormat(beginIndex:int = -1, endIndex:int = - 1):TextFormat Analysons chacun des paramètres : ? beginIndex : position du caractère de début à partir duquel la mise en forme doit être extraite, la valeur par est défaut -1. ? endIndex : position du caractère de fin à partir duquel la mise en forme doit être extraite, la valeur par défaut est -1. Cette dernière renvoie un objet TextFormat associé à la partie du texte spécifiée. Si aucun paramètre n?est spécifié, l?objet TextFormat renvoyé concerne la totalité du champ texte. Comme nous l?avons vu précédemment, lorsque du contenu est affecté à un champ texte, le lecteur Flash crée un objet TextFormat interne contenant les options de mise en forme du texte. Dans le code suivant, nous récupérons les informations de mise en forme du champ monTexte : var monTexte:TextField = new TextField(); monTexte.text = "Voici du texte !"; Chapitre 16 ? Le texte ? version 0.1.1 23 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); // extraction de l'objet TextFormat var miseEnForme:TextFormat = monTexte.getTextFormat(); // affiche : Times New Roman trace( miseEnForme.font ); // affiche : 12 trace( miseEnForme.size ); // affiche : left trace( miseEnForme.align ); // affiche : 0 trace( miseEnForme.color ); // affiche : false trace( miseEnForme.bold ); // affiche : false trace( miseEnForme.italic ); // affiche : false trace( miseEnForme.underline ); En définissant différentes balises HTML, nous remarquons que l?objet TextFormat renvoyé reflète correctement la mise en forme actuelle du champ : var monTexte:TextField = new TextField(); monTexte.htmlText = "<font size='18' color='#990000'><i><b>Voici du texte !</b></i></font>"; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); // extraction de l'objet TextFormat var miseEnForme:TextFormat = monTexte.getTextFormat(); // affiche : Times New Roman trace( miseEnForme.font ); // affiche : 18 trace( miseEnForme.size ); // affiche : left trace( miseEnForme.align ); // affiche : 990000 trace( miseEnForme.color.toString(16) ); // affiche : true trace( miseEnForme.bold ); // affiche : true trace( miseEnForme.italic ); Chapitre 16 ? Le texte ? version 0.1.1 24 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : false trace( miseEnForme.underline ); La classe TextFormat s?avère extrêmement pratique, nous allons l?utiliser dans la section suivante afin d?augmenter les capacités de la classe TextField. Etendre la classe TextField Nous avons vu au cours du chapitre 9 intitulé Etendre les classes natives qu?il était possible d?augmenter les capacités d?une classe lorsque celle-ci nous paraissait limitée. La classe TextField figure parmi les classes souvent étendues afin d?optimiser l?affichage ou la gestion du texte. Dans le cas d?applications localisées, la taille du texte peut varier selon les langues utilisées. La taille du champ doit rester la même afin de ne pas perturber la présentation graphique de l?application. En étendant la classe TextField nous allons ajouter un mécanisme d?adaptation automatique du texte au sein du champ. En activant le mécanisme d?adaptation le texte est réduit afin d?être affiché totalement. La figure 16-20 illustre le résultat : Figure 16-20. Champ texte auto-adaptable. Dans un nouveau paquetage org.bytearray.texte nous définissons la classe ChampTexteAutoAdaptable contenant le code suivant : Chapitre 16 ? Le texte ? version 0.1.1 25 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org package org.bytearray.texte { import flash.text.TextField; public class ChampTexteAutoAdaptable extends TextField { public function ChampTexteAutoAdaptable () { } } } La classe ChampTexteAutoAdaptable étend la classe TextField afin d?hériter de toutes ses fonctionnalités. Nous définissons une propriété autoAdaptation en lecture écriture afin de spécifier si le champ doit redimensionner ou non le contenu texte : package org.bytearray.texte { import flash.text.TextField; public class ChampTexteAutoAdaptable extends TextField { private var adaptation:Boolean; public function ChampTexteAutoAdaptable () { } public function set autoAdaptation ( pAdaptation:Boolean ):void { adaptation = pAdaptation; } public function get autoAdaptation ():Boolean { return adaptation; } } Chapitre 16 ? Le texte ? version 0.1.1 26 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } Lorsque le contenu texte est ajouté, nous devons déclencher le processus d?adaptation du texte au sein du champ, pour cela nous devons modifier le fonctionnement de la propriété text. Nous surchargeons celle-ci en déclenchant la méthode adapte lorsque du contenu est affecté : package org.bytearray.texte { import flash.text.TextField; import flash.text.TextFormat; public class ChampTexteAutoAdaptable extends TextField { private var adaptation:Boolean; private var formatage:TextFormat; private var tailleInitiale:int; public function ChampTexteAutoAdaptable () { } public function set autoAdaptation ( pAdaptation:Boolean ):void { adaptation = pAdaptation; } public function get autoAdaptation ():Boolean { return adaptation; } public override function set text ( pText:String ):void { super.text = pText; if ( autoAdaptation ) adapte(); } private function adapte ():void { formatage = getTextFormat(); Chapitre 16 ? Le texte ? version 0.1.1 27 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org tailleInitiale = int(formatage.size); while ( textWidth > width || textHeight > height ) { if ( formatage.size <= 0 ) return; formatage.size = --tailleInitiale; setTextFormat ( formatage ); } } } } Nous retrouvons ici le concept de surcharge en étendant le fonctionnement de la propriété text. Bien que celle-ci soit surchargée, nous déclenchons la définition héritée grâce au mot-clé super. La méthode adapte trouve une taille de police adéquate afin d?adapter le contenu texte au sein du champ. Grâce à la propriété autoAdaptation nous pouvons activer ou non l?adaptation du texte aux dimensions du champ : import org.bytearray.texte.ChampTexteAutoAdaptable; var monPremierChamp:ChampTexteAutoAdaptable = new ChampTexteAutoAdaptable(); monPremierChamp.border = true; monPremierChamp.wordWrap = true; // activation du mode d'auto adaptation monPremierChamp.autoAdaptation = true; monPremierChamp.text = "Mauris consectetuer sagittis est. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum gravida, arcu ac ullamcorper semper..."; addChild ( monPremierChamp ); var monSecondChamp:ChampTexteAutoAdaptable = new ChampTexteAutoAdaptable(); monSecondChamp.border = true; monSecondChamp.wordWrap = true; monSecondChamp.text = "Mauris consectetuer sagittis est. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum gravida, arcu ac ullamcorper semper..."; monSecondChamp.y = 120; addChild ( monSecondChamp ); Chapitre 16 ? Le texte ? version 0.1.1 28 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous créons un simple champ texte contenant la chaîne de caractères suivante "Bienvenue au camping des Bois Fleuris" : import org.bytearray.texte.ChampTexteAutoAdaptable; var monPremierChamp:ChampTexteAutoAdaptable = new ChampTexteAutoAdaptable(); monPremierChamp.border = true; monPremierChamp.width = 220; monPremierChamp.autoAdaptation = true; monPremierChamp.text = "Bienvenue au camping des Bois Fleuris"; addChild ( monPremierChamp ); La figure 16-21 illustre le rendu : Figure 16-21. Contenu texte. Le mode d?adaptation du contenu est activé, si nous modifions le contenu du champ par un contenu trop long, le champ réduit automatiquement la taille de la police afin d?afficher totalement le contenu. Dans le code suivant, le message de bienvenue est localisé en Allemand : import org.bytearray.texte.ChampTexteAutoAdaptable; var monPremierChamp:ChampTexteAutoAdaptable = new ChampTexteAutoAdaptable(); monPremierChamp.border = true; monPremierChamp.width = 220; monPremierChamp.autoAdaptation = true; monPremierChamp.text = "Willkommen auf dem Bois Fleuris Campingplatz"; addChild ( monPremierChamp ); La figure 16-22 illustre le résultat : Chapitre 16 ? Le texte ? version 0.1.1 29 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-22. Reduction du texte automatique. Dans certains cas, les champs texte peuvent déjà être créés depuis l?environnement auteur afin d?être positionnés convenablement au sein d?une interface. Au lieu de remplacer chaque champ par une instance de la classe ChampTexteAutoAdaptable, nous pouvons directement ajouter le mécanisme d?auto adaptation au sein du prototype de la classe TextField : TextField.prototype.texteAutoAdapte = function ( pTexte:String ) { this.text = pTexte; this.adapte(); } TextField.prototype.adapte = function () { var formatage = this.getTextFormat(); var tailleInitiale = int(formatage.size); while ( this.textWidth > this.width || this.textHeight > this.height ) { if ( formatage.size <= 0 ) return; formatage.size = --tailleInitiale; this.setTextFormat ( formatage ); } } Il nous suffit par la suite d?affecter le contenu texte aux différents champs par la méthode texteAutoAdapte : Chapitre 16 ? Le texte ? version 0.1.1 30 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org champTexte.texteAutoAdapte ( "Willkommen auf dem Bois Fleuris Campingplatz" ); Si nous tentons de compiler, le compilateur nous affiche le message suivant : 1061: Appel à la méthode texteAutoAdapte peut-être non définie, via la référence de type static flash.text:TextField. Bien entendu, la classe TextField ne dispose pas par défaut d?une méthode texteAutoAdapte, le compilateur nous empêche donc de compiler. Nous passons outre cette vérification en transtypant notre champ texte vers la classe dynamique Object : Object(champTexte).texteAutoAdapte ( "Willkommen auf dem Bois Fleuris Campingplatz" ); A l?exécution, la méthode texteAutoAdapte est trouvée et exécutée, le contenu du champ est adapté. Nous verrons au cours de la section intitulée Créer un éditeur de texte un autre exemple d?application des méthodes setTextFormat et getTextFormat. A retenir ? La classe TextField peut être étendue afin d?augmenter ses capacités. ? Selon les dimensions du champ, la classe ChampTexteAutoAdaptable permet d?adapter la taille de la police afin d?afficher totalement le contenu du texte. La classe StyleSheet Afin de mettre en forme du texte, il peut être intéressant d?externaliser la mise en forme du texte au sein d?une feuille de style CSS. L?avantage de cette technique permet entre autre de mettre à jour la mise en forme du texte sans avoir à recompiler l?application. La feuille de style est modifiée puis chargée dynamiquement afin de mettre en forme le contenu. Consultez l?aide de Flash CS3 pour la liste des propriétés CSS gérées par le lecteur Flash. Au sein d?un fichier nommé style.css nous définissons la feuille de style suivante : p Chapitre 16 ? Le texte ? version 0.1.1 31 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org { font-family:Verdana, Arial, Helvetica, sans-serif; color:#CC9933; } .main { font-style:italic; font-size:12; color:#CC00CC; } .title { font-weight:bold; font-style:italic; font-size:16px; color:#FF6633; } Puis nous chargeons celle-ci à l?aide de l?objet URLLoader : var monTexte:TextField = new TextField(); monTexte.wordWrap = true; monTexte.width = 300; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); var chargeurCSS:URLLoader = new URLLoader(); // la feuille de style est chargée comme du contenu texte chargeurCSS.dataFormat = URLLoaderDataFormat.TEXT; var requete:URLRequest = new URLRequest ("style.css"); chargeurCSS.load ( requete ); chargeurCSS.addEventListener (Event.COMPLETE, chargementTermine); chargeurCSS.addEventListener (IOErrorEvent.IO_ERROR, erreurChargement); function chargementTermine ( pEvt:Event ):void { // affiche : ( contenu texte de style.css ) trace( pEvt.target.data ); } function erreurChargement ( pEvt:IOErrorEvent ):void Chapitre 16 ? Le texte ? version 0.1.1 32 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org { trace( "erreur de chargement" ); } Nous l?affectons au champ texte par la propriété styleSheet définie par la classe TextField : function chargementTermine ( pEvt:Event ):void { // une feuille de style vide est créée var feuilleDeStyle:StyleSheet = new StyleSheet(); // la feuille de style est définie en interprétant le contenu de style.css feuilleDeStyle.parseCSS ( pEvt.target.data ); // la feuille de style est affectée au champ monTexte.styleSheet = feuilleDeStyle; // le contenu HTML contenant les balises nécessaires est affecté monTexte.htmlText = "<p><span class='main'>Lorem ipsum</span> dolor sit amet, consectetuer adipiscing elit. <span class='title'>Nulla sit amet tellus</span>. Quisque lobortis. Duis tincidunt mollis massa. Maecenas neque orci, vulputate eget, consectetuer vitae, consectetuer ut, urna. Curabitur non nibh eu massa tincidunt consequat. Nullam nulla magna, auctor sed, semper ut, nonummy a, ipsum. Aliquam pulvinar est sit amet tortor. Sed facilisis ligula at est volutpat tristique. Etiam sem felis, facilisis in, fermentum at, consectetuer quis, ipsum. Morbi tincidunt velit. Integer cursus pretium nisl. Fusce et ante.</p>"; } La méthode statique parseCSS permet d?interpréter la chaine de caractère contenue dans le fichier style.css afin de définir l?objet StyleSheet. La figure 16-23 illustre la mise en forme : Chapitre 16 ? Le texte ? version 0.1.1 33 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-23. Mise en forme par feuille de style. Grace aux feuilles de style, nous pouvons définir des options de mise en forme liées à l?intéractivité. En ajoutant la propriété a:hover au sein de la feuille de style nous rendons le texte gras et modifions sa couleur lors du survol : a:hover { font-weight:bold; color:#0033CC; } En ajoutant un lien hypertexte, le texte change de style automatiquement : monTexte.htmlText = "<p><span class='main'>Lorem ipsum</span> dolor sit amet, consectetuer adipiscing elit. <span class='title'>Nulla sit amet tellus</span>.<a href='http://www.oreilly.fr'>Quisque lobortis</a>. Duis tincidunt mollis massa. Maecenas neque orci, vulputate eget, consectetuer vitae, consectetuer ut, urna. Curabitur non nibh eu massa tincidunt consequat. Nullam nulla magna, auctor sed, semper ut, nonummy a, ipsum. Aliquam pulvinar est sit amet tortor. Sed facilisis ligula at est volutpat tristique. Etiam sem felis, facilisis in, fermentum at, consectetuer quis, ipsum. Morbi tincidunt velit. Integer cursus pretium nisl. Fusce et ante.</p>"; La figure 16-24 illustre le résultat : Figure 16-24. Mise en forme par feuille de style. Malheureusement, l?utilisation des CSS s?avère limitée dans certains cas. Un champ texte de saisie n?autorise pas la saisie de texte lorsque celui-ci est lié à une feuille de style. Chapitre 16 ? Le texte ? version 0.1.1 34 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org De la même manière, un champ texte lié à une feuille de style n?est pas modifiable. L?utilisation des méthodes replaceText, replaceSelectedText, appendText, setTextFormat n?est pas autorisée. A retenir ? Trois techniques permettent de mettre du texte en forme : les balises HTML, l?objet TextFormat, la définition d?une feuille style à l?aide de la classe StyleSheet. ? Quelle que soit la technique employée, le lecteur Flash fonctionne en interne par l?intérmédiaire de balises HTML. Modifier le contenu d?un champ texte Durant l?exécution d?une application, nous avons souvent besoin de modifier le contenu de champs texte. Pour cela, nous pouvons utiliser les propriétés suivantes : ? text : permet l?affectation de contenu non HTML ; ? htmlText : permet l?affectation de contenu HTML ; Ainsi que les méthodes suivantes : ? appendText : remplace l?opérateur += lors d?ajout de texte. Ne prend pas en charge le texte au format HTML ; ? replaceText : permet de remplacer une partie du texte au sein d?un champ. Ne prend pas en charge le texte au format HTML. Dans le cas d?ajout de contenu nous pouvons utiliser l?opérateur += sur la propriété text : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "ActionScript"; monTexte.text += " 3" // affiche : ActionScript 3 trace( monTexte.text ); Cependant, en ActionScript 3 l?utilisation de l?opérateur += sur un champ texte est dépréciée au profit de la méthode appendText. Si le mode Avertissements du compilateur est activé, celui-ci nous avertit de la dépréciation de l?opérateur += : Chapitre 16 ? Le texte ? version 0.1.1 35 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Warning: 3551: L'ajout de texte à la fin d'un champ texte TextField avec += est beaucoup plus lent que l'utilisation de la méthode TextField.appendText(). Voici la signature de la méthode appendText : public function appendText(newText:String):void Nous passons en paramètre le texte à ajouter au champ de la manière suivante : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "ActionScript"; monTexte.appendText ( " 3" ); // affiche : ActionScript 3 trace( monTexte.text ); En faisant un simple test de performances, nous voyons que l?opérateur += est en effet plus lent que la méthode appendText : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; var debut:Number = getTimer(); for (var i:int = 0; i< 1500; i++ ) { monTexte.text += "ActionScript 3"; } // affiche : 1120 trace( getTimer() - debut ); Une simple concaténation sur 1500 itérations nécessite 1120 millisecondes. Si nous utilisons cette fois la méthode appendText : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; var debut:Number = getTimer(); for (var i:int = 0; i< 1500; i++ ) Chapitre 16 ? Le texte ? version 0.1.1 36 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org { monTexte.appendText ( "ActionScript 3" ); } // affiche : 847 trace( getTimer() - debut ); Le temps d?exécution est de 847 ms. L?utilisation de la méthode appendText s?avère être environ 30% plus rapide que l?opérateur +=. Nous pourrions penser que la méthode appendText s?avère donc être la méthode favorite. En réalité, afin d?affecter du texte de manière optimisée, il convient de limiter au maximum la manipulation du champ texte. Ainsi, nous pourrions accélérer le code précèdent en créant une variable contenant le texte, puis en l?affectant au champ une fois la concaténation achevée : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; var debut:Number = getTimer(); var contenu:String = monTexte.text; for (var i:int = 0; i< 1500; i++ ) { contenu += "ActionScript 3"; } monTexte.text = contenu; // affiche : 2 trace( getTimer() - debut ); En procédant ainsi, nous passons à une vitesse d?exécution de 2 millisecondes. Soit un temps d?exécution environ 420 fois plus rapide que la méthode appendText et 560 fois plus rapide que l?opérateur += auprès de la propriété text. La même approche peut être utilisée dans le cas de manipulation de texte au format HTML grâce à la propriété htmlText : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; Chapitre 16 ? Le texte ? version 0.1.1 37 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org var debut:Number = getTimer(); var contenu:String = monTexte.htmlText; for (var i:int = 0; i< 1500; i++ ) { contenu += "<b>ActionScript<b> 3"; } monTexte.htmlText = contenu; // affiche : 29 trace( getTimer() - debut ); Afin d?obtenir le même résultat, si nous concaténons directement le contenu au sein du champ texte, le temps d?exécution excède 15 secondes et lève une exception de type ScriptTimeoutError : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; var debut:Number = getTimer(); for (var i:int = 0; i< 1500; i++ ) { monTexte.htmlText += "ActionScript <b>2</b>"; } trace( getTimer() - debut ); Le processus de rendu est trop complexe et lève l?exception suivante : Error: Error #1502: La durée d'exécution d'un script excède le délai par défaut (15 secondes). Il convient donc d?utiliser la méthode appendText ou les propriétés text et htmlText dans un contexte non intensif. Dans tous les autres cas, nous préférerons créer une variable contenant le texte, puis travailler sur celle-ci avant de l?affecter au champ texte. Remplacer du texte Si nous souhaitons modifier une partie du texte, nous pouvons utiliser la méthode replaceText de la classe TextField ayant la signature suivante : public function replaceText(beginIndex:int, endIndex:int, newText:String):void Chapitre 16 ? Le texte ? version 0.1.1 38 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Cette méthode accepte trois paramètres, dont voici le détail : ? beginIndex : la position du caractère à partir duquel le remplacement démarre, la valeur par défaut est -1 ; ? endIndex : la position du caractère à partir duquel le remplacement se termine, la valeur par défaut est -1 ; ? newText : le texte de remplacement ; Afin de bien comprendre l?utilisation de cette méthode, prenons le cas du texte suivant : ActionScript 2 Ce texte est ajouté à un champ texte : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "ActionScript 2"; Afin de remplacer le chiffre 2 par 3 nous utilisons la méthode replaceText : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "ActionScript 2"; monTexte.replaceText( 13, 14, "3" ); addChild ( monTexte ); Le champ texte contient alors le texte suivant : ActionScript 3 Nous pouvons rendre le remplacement dynamique et rechercher la position du caractère à l?aide de la méthode indexOf de la classe String : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "ActionScript 2"; var chaine:String = "2"; var position:int = monTexte.text.indexOf ( chaine ); if ( position != -1 ) monTexte.replaceText ( position, position+chaine.length, "3" ); addChild ( monTexte ); Chapitre 16 ? Le texte ? version 0.1.1 39 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Comme nous l?avons vu lors du chapitre 2 intitulé Langage et API du lecteur Flash, l?intégration des expressions regulières en ActionScript 3 rend la manipulation de chaînes de caractères extrêmement puissante. Il est préférable d?utiliser les méthodes de la classe String telles search, replace ou match pour une recherche plus complexe. A retenir ? Afin de modifier le contenu d?un champ texte, nous pouvons utilisons la propriété text, htmlText ainsi que les méthodes appendText ou replaceText. ? La méthode appendText ne permet pas l?affectation de contenu HTML. ? Dans tous les cas, l?utilisation d?une variable temporaire afin de concaténer et traiter le contenu s?avère plus rapide. ? La méthode replaceText permet de remplacer du texte au sein d?un champ de manière simplifiée. L?événement TextEvent.LINK Les précédentes versions d?ActionScript intégraient une fonctionnalité appelée asfunction permettant de déclencher des fonctions ActionScript à partir de texte HTML. Ce protocole fut intégré au sein du lecteur Flash 5. Afin de déclencher une fonction ActionScript, le nom de la fonction précédé du mot-clé asfunction était passé à l?attribut href de la balise d?ancrage <a> : var monTexte:TextField = this.createTextField ("legende", 0, 0, 0, 0, 0); monTexte.autoSize = true; function maFonction ( pArgument ) { trace ( "Le paramètre passé est " + pArgument ); } monTexte.html = true; monTexte.htmlText ="<a href='asfunction:maFonction,Coucou'>Cliquez ici !</a>"; Afin de rester conforme au nouveau modèle événementiel, ActionScript 3 remplace ce protocole en proposant la diffusion d?événements TextEvent.LINK par les champs texte. Chapitre 16 ? Le texte ? version 0.1.1 40 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous créons un champ texte comportant une balise <b> et <i> : var monTexte:TextField = new TextField(); monTexte.width = 250; monTexte.wordWrap = true; addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "En ActionScript 3, il est possible de diffuser un événement <i>TextEvent.LINK</i> depuis un champ texte en cliquant sur un <b>lien</b>."; La figure 16-25 illustre le résultat : Figure 16-25. Champ texte formaté. Nous allons permettre la diffusion d?un événement TextEvent.LINK, à l?aide de la syntaxe suivante : <a href='event:paramètre'>Texte Cliquable</a> L?utilisation du mot clé event en tant que valeur de l?attribut href permet la diffusion d?un événement TextEvent.LINK lors du clic sur le lien. La valeur précisée après les deux points est affectée à la propriété text de l?objet événementiel diffusé. Dans le code suivant, nous écoutons l?événement TextEvent.LINK auprès du champ texte : var monTexte:TextField = new TextField(); monTexte.width = 250; monTexte.wordWrap = true; addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "En ActionScript 3, il est possible de diffuser un événement <i>TextEvent.LINK</i> depuis un champ texte en cliquant sur un <a href='event:Texte caché !'><b>lien</b></a>."; monTexte.addEventListener (TextEvent.LINK, clicLien); function clicLien ( pEvt:TextEvent ):void Chapitre 16 ? Le texte ? version 0.1.1 41 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org { // affiche : [TextEvent type="link" bubbles=true cancelable=false eventPhase=2 text="Texte caché !"] trace( pEvt ); // affiche : [object TextField] trace( pEvt.target ); } La propriété text de l?objet événementiel contient le texte spécifié après l?attribut event. Dans le code suivant, nous changeons dynamiquement la cadence de l?animation en cliquant sur différents liens associés au champ texte : var monTexte:TextField = new TextField(); monTexte.width = 250; monTexte.wordWrap = true; addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "La cadence de l'animation peut être changée depuis le champ texte, la cadence peut être passée à <a href='event:10'><b>10 img/s</b></a> ou bien <a href='event:25'><b>25 img/s</b></a> ou encore <a href='event:50'><b>50 img/s</b></a>."; monTexte.addEventListener (TextEvent.LINK, onLinkClick); function onLinkClick ( pEvt:TextEvent ):void { // affiche : 25 trace( pEvt.text ); stage.frameRate = int ( pEvt.text ); } La figure 16-26 illustre le résultat : Chapitre 16 ? Le texte ? version 0.1.1 42 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-26. Modification dynamique de la cadence de l?animation depuis le champ texte. Il n?est pas possible de diffuser d?autres événements à l?aide de l?attribut event de la balise <a>. De la même manière, il n?est pas prévu de pouvoir passer plusieurs paramètres à la fonction écouteur, mais à l?aide d?un séparateur comme une virgule nous pouvons y parvenir : <a href='event:paramètre1,paramètre2,paramètre3'>Texte Cliquable</a> Ainsi, nous pouvons passer plusieurs cadences avec la syntaxe suivante : <a href='event:10,20,30,40'><b>10 img/s</b></a> Au sein de la fonction écouteur nous transformons la chaîne en un tableau à l?aide de la méthode split de la classe String : function clicLien ( pEvt:TextEvent ):void { var parametres:Array = pEvt.text.split (","); /* affiche : 10 20 30 40 */ for each ( var p:* in parametres ) trace( p ); Chapitre 16 ? Le texte ? version 0.1.1 43 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } L?utilisation des événements TextEvent.LINK entraîne malheureusement un comportement pénalisant. Lorsqu?un clic droit est détecté sur un lien associé au champ texte, un menu contextuel s?ouvre habituellement comme l?illustre la figure 16- 27 : Figure 16-27. Menu contextuel de liens hypertexte. L?utilisation d?événements TextEvent.LINK empêche le bon fonctionnement du menu contextuel. Si nous décidons d?ouvrir le lien directement ou dans une nouvelle fenêtre, le message d?erreur illustré en figure 16-28 s?affiche : Figure 16-28. Message d?erreur associé aux liens hypertexte. Le navigateur par défaut ne reconnaît pas le lien associé (protocole event) qui est représenté sous la forme suivante : event:paramètre De plus en sélectionnant une option du menu contextuel l?événement TextEvent.LINK n?est pas diffusé. Chapitre 16 ? Le texte ? version 0.1.1 44 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Il convient donc de prendre ces comportements en considération. A retenir ? Le protocole asfunction est remplacé par la diffusion d?événements TextEvent.LINK. ? Afin de diffuser un événement TextEvent.LINK, nous utilisons le mot-clé event en tant que valeur de l?attribut href de la balise d?ancrage <a>. ? L?utilisation d?événements TextEvent.LINK empêche le bon fonctionnement du menu contextuel associé au lien. Charger du contenu externe Nous avons vu qu?il était possible d?intégrer des balises HTML au sein d?un champ texte. Dans le cas de l?utilisation de la balise <img>, nous pouvons intégrer facilement un élément multimédia telle une image ou un SWF. Dans le code suivant, nous intégrons une image au champ texte : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "<p>Voici une image intégrée au sein d'un champ texte HTML :</p><p><img src='http://www.google.com/intl/en_ALL/images/logo.gif'>"; La figure 16-29 illustre le résultat : Figure 16-29. Image intégrée dans un champ texte. Afin de correctement gérer le chargement des médias intégrés aux champs texte, ActionScript 3 introduit une nouvelle méthode de la Chapitre 16 ? Le texte ? version 0.1.1 45 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org classe TextField nommée getImageReference dont voici la signature : public function getImageReference(id:String):DisplayObject Lorsqu?une balise <img> est utilisée au sein d?un champ texte, le lecteur Flash crée automatiquement un objet Loader enfant au champ texte associé au média chargé. Bien que la classe TextField ne soit pas un objet de type DisplayObjectContainer, l?objet Loader est pourtant contenu par le champ texte. Dans le code suivant, nous ajoutons un identifiant monLogo à l?image intégrée grâce à l?attribut id : monTexte.htmlText = "<p>Voici une image intégrée au sein d'un champ texte HTML :</p><p><img src='http://www.google.com/intl/en_ALL/images/logo.gif' id='monLogo'>"; Afin d?extraire l?objet Loader associé au média chargé, nous passons ce même identifiant à la méthode getImageReference : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "<p>Voici une image intégrée au sein d'un champ texte HTML :</p><p><img src='http://www.google.com/intl/en_ALL/images/logo.gif' id='monLogo'>"; var chargeurLogo:Loader = Loader ( monTexte.getImageReference("monLogo") ); // affiche : [object Loader] trace( chargeurLogo ); // affiche : [object TextField] trace( chargeurLogo.parent ); Nous remarquons que la propriété parent de l?objet Loader fait bien référence au champ texte. Nous pouvons ainsi gérer le préchargement de l?image et accéder à celle-ci grâce la propriété content de l?objet Loader : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "<p>Voici une image intégrée au sein d'un champ texte HTML :</p><p><img src='http://www.google.com/intl/en_ALL/images/logo.gif' id='monLogo'>"; var chargeurLogo:Loader = Loader ( monTexte.getImageReference("monLogo") ); // écoute de l'événement Event.COMPLETE Chapitre 16 ? Le texte ? version 0.1.1 46 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org chargeurLogo.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); function chargementTermine ( pEvt:Event ):void { // affiche : [object Bitmap] trace( pEvt.target.content ); } Dans le code suivant, nous affectons un filtre de flou à l?image chargée : var point:Point = new Point ( 0, 0 ); var filtreFlou:BlurFilter = new BlurFilter ( 8, 8, 2 ); function chargementTermine ( pEvt:Event ):void { if ( pEvt.target.content is Bitmap ) { var imageChargee:Bitmap = Bitmap ( pEvt.target.content ); // affecte un filtre de flou à l'image intégrée au champ texte imageChargee.bitmapData.applyFilter ( imageChargee.bitmapData, imageChargee.bitmapData.rect, point, filtreFlou ); } } La figure 16-30 illustre l?image floutée : Figure 16-30. Image chargée dynamiquement puis floutée. Une fois l?image chargée, celle-ci est entièrement manipulable. Chapitre 16 ? Le texte ? version 0.1.1 47 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Si aucune image n?est associée à l?identifiant passé à la méthode getImageReference, celle-ci renvoie null. Ainsi, le chargement d?une image au sein d?un champ texte repose sur les mêmes mécanismes de chargement qu?une image traditionnelle chargée directement au sein d?un objet Loader : var monTexte:TextField = new TextField(); addChild ( monTexte ); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.htmlText = "<p>Voici une image intégrée au sein d'un champ texte HTML :</p><p><img src='http://www.google.com/intl/en_ALL/images/logo.gif' id='monLogo'>"; var chargeurLogo:Loader = Loader ( monTexte.getImageReference("monLogo") ); // écoute des différents événements chargeurLogo.contentLoaderInfo.addEventListener ( Event.OPEN, chargementDemarre ); chargeurLogo.contentLoaderInfo.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); chargeurLogo.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); chargeurLogo.contentLoaderInfo.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementDemarre ( pEvt:Event ):void { trace("chargement demarré"); } function chargementEnCours ( pEvt:ProgressEvent ):void { trace("chargement en cours : " + pEvt.bytesLoaded + " / " + pEvt.bytesTotal ); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } var point:Point = new Point ( 0, 0 ); var filtreFlou:BlurFilter = new BlurFilter ( 8, 8, 2 ); function chargementTermine ( pEvt:Event ):void { Chapitre 16 ? Le texte ? version 0.1.1 48 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org if ( pEvt.target.content is Bitmap ) { var imageChargee:Bitmap = Bitmap ( pEvt.target.content ); // affecte un filtre de flou à l'image intégrée au champ texte imageChargee.bitmapData.applyFilter ( imageChargee.bitmapData, imageChargee.bitmapData.rect, point, filtreFlou ); } } Grâce aux différents événements diffusés par l?objet LoaderInfo, nous pouvons gérer les erreurs de chargement des images associées au champ texte en remplaçant l?image non trouvée par une image de substitution. De la même manière, nous pouvons intégrer dans le champ texte un SWF. monTexte.htmlText = "<p>Voici une animation intégrée au sein d'un champ texte HTML :</p><p><img src='animation.swf' id='monAnim'>"; Grâce à la méthode getImageReference nous extrayons l?objet Loader associé : var chargeurLogo:Loader = Loader ( monTexte.getImageReference("monAnim") ); Une fois le chargement terminé, nous accédons aux informations liées au SWF chargé et modifions son opacité : function chargementTermine ( pEvt:Event ):void { pEvt.target.content.alpha = .5; if ( pEvt.target.content is DisplayObjectContainer ) { // affiche : 12 trace( pEvt.target.frameRate ); // affiche : application/x-shockwave-flash trace( pEvt.target.contentType ); // affiche : 9 trace( pEvt.target.swfVersion ); // affiche : 3 trace( pEvt.target.actionScriptVersion ); } } Chapitre 16 ? Le texte ? version 0.1.1 49 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 16-31 illustre le résultat : Figure 16-31. Animation avec opacité réduite. Il est important de signaler que seule la méthode getImageReference permet d?extraire l?objet Loader associé au media chargé. Bien que l?objet TextField contienne les objets Loader, la classe TextField n?hérite pas de la classe DisplayObjectContainer et n?offre malheureusement pas de propriétés telles numChildren ou de méthodes getChildAt ou removeChild. A retenir ? Un objet Loader est créé pour chaque média intégré à un champ texte. ? Bien que la classe TextField n?hérite pas de la classe DisplayObjectContainer, les objets Loader sont enfants du champ texte. ? Seule la méthode getImageReference permet d?extraire l?objet Loader associé au média chargé. ? Afin de pouvoir utiliser la méthode getImageReference un identifiant doit être affecté au média à l?aide de l?attribut id. Exporter une police dans l?animation Lorsque nous créons un champ texte par programmation, le lecteur utilise par défaut les polices du système d?exploitation. Même si ce mécanisme possède un intérêt évident lié au poids de l?animation, si l?ordinateur visualisant l?application ne possède pas la police nécessaire, le texte risque de ne pas être affiché. Chapitre 16 ? Le texte ? version 0.1.1 50 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Pour nous rendre compte d?autres limitations liées aux polices systèmes, nous pouvons tester le code suivant : var monTexte:TextField = new TextField() monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Police système"; monTexte.rotation = 45; addChild ( monTexte ); Nous remarquons que le texte n?est pas affiché, car nous avons fait subir une rotation de 45 degrés au champ texte. Sans les contours de polices intégrés, le lecteur est incapable de travailler sur la rotation, les effets de masque ou l?opacité du texte. Si nous tentons de modifier l?opacité, le texte demeure totalement opaque : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.text = "Police système"; monTexte.alpha = .4; addChild ( monTexte ); Afin de remédier à ces limitations nous devons intégrer la police à l?animation. Pour cela, nous cliquons sur la partie droite de la bibliothèque du document en cours comme l?illustre la figure 16-32 : Figure 16-32. Animation avec opacité réduite. Puis nous sélectionnons l?option Nouvelle police. Chapitre 16 ? Le texte ? version 0.1.1 51 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Au sein du nouveau panneau, nous sélectionnons la police à intégrer puis nous validons. Dans notre cas, nous utilisons la police Lovitz. Notez que le nom de la police spécifié dans le champ associé n?a pas d?incidence sur la suite de l?opération. En utilisant le panneau Propriétés de liaison du symbole de police, nous associons une classe auto générée comme l?illustre la figure 16- 33 : Figure 16-33. Classe de police auto générée. Bien entendu, le nom de la classe associée ne doit pas nécessairement posséder le nom de la police, nous aurions pu utiliser MaPolice, PolicePerso ou autres. Afin d?utiliser une police embarquée dans un champ texte, nous utilisons dans un premier temps la propriété embedFonts de la classe TextField : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; // le champ texte doit utiliser une police embarquée monTexte.embedFonts = true; Puis, nous indiquons la police à utiliser en instanciant la classe Lovitz, et en passant son nom à la propriété font d?un objet de mise forme TextFormat : var monTexte:TextField = new TextField(); monTexte.autoSize = TextFieldAutoSize.LEFT; // le champ doit utiliser une police embarquée Chapitre 16 ? Le texte ? version 0.1.1 52 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org monTexte.embedFonts = true; // nous instancions la police var policeBibliotheque:Lovitz = new Lovitz(); // un objet de mise en forme est créé var miseEnForme:TextFormat = new TextFormat(); // nous passons le nom de la police embarquée à l?objet TextFormat miseEnForme.font = policeBibliotheque.fontName; miseEnForme.size = 64; // affectation de la mise en forme monTexte.defaultTextFormat = miseEnForme; monTexte.text = "Police intégrée"; monTexte.rotation = 45; monTexte.alpha = .4; monTexte.x = ( stage.stageWidth - monTexte.width ) / 2; monTexte.y = ( stage.stageHeight - monTexte.height ) / 2; addChild ( monTexte ); La figure 16-34 illustre le champ texte utilisant la police embarquée : Figure 16-34. Champ texte utilisant une police embarquée. Malheureusement, en exportant les contours de polices dans l?animation, nous augmentons sensiblement le poids du SWF généré. Chapitre 16 ? Le texte ? version 0.1.1 53 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Même si cela peut paraître négligeable dans un premier temps, cela peut poser un réel problème dans le cas d?applications localisées, où plusieurs polices peuvent être nécessaires. Au lieu d?intégrer plusieurs polices au sein de l?animation, nous préférerons charger dynamiquement la police nécessaire lorsque l?application en fait la demande. Charger dynamiquement une police Une des limitations des précedentes versions d?ActionScript concernait le manque de souplesse lié au chargement de polices dynamique. ActionScript 3 simplifie grandement ce processus grâce à l?introduction de la classe flash.text.Font. Nous avons découvert au cours du chapitre 13 intitulé Charger du contenu la méthode getDefinition de la classe ApplicationDomain. Au cours de ce chapitre, nous avions extrait différentes définitions de classes issues d?un SWF chargé dynamiquement. Le même concept peut être appliqué dans le cas de polices associées à des classes. En intégrant une police au sein d?un SWF, nous allons pouvoir charger ce dernier puis en extraire la police. Une fois la police en bibliothèque et associée à une classe, nous exportons l?animation sous la forme d?un SWF appelé bibliotheque.swf. Dans un nouveau document Flash CS3 nous chargeons dynamiquement cette bibliothèque partagée afin d?extraire la définition de classe de police : var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.load ( new URLRequest("bibliotheque.swf") ); function chargementTermine ( pEvt:Event ):void { var DefinitionClasse:Class = Class ( pEvt.target.applicationDomain.getDefinition("Lovitz") ); // affiche : [class Lovitz] trace( DefinitionClasse ); } Grâce à la méthode statique registerFont de la classe flash.text.Font nous enregistrons la définition de classe comme nouvelle police disponible au sein de tous les SWF de l?application : Chapitre 16 ? Le texte ? version 0.1.1 54 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org function chargementTermine ( pEvt:Event ):void { var DefinitionClasse:Class = Class ( pEvt.target.applicationDomain.getDefinition("Lovitz") ); Font.registerFont ( DefinitionClasse ); } La méthode statique enumerateFonts de la classe Font nous indique que la police a été correctement intégrée : function chargementTermine ( pEvt:Event ):void { var DefinitionClasse:Class = Class ( pEvt.target.applicationDomain.getDefinition("Lovitz") ); // énumération des polices intégrées var policeIntegrees:Array = Font.enumerateFonts(); // affiche : 0 trace( policeIntegrees.length ); Font.registerFont ( DefinitionClasse ); policeIntegrees = Font.enumerateFonts(); // affiche : 1 trace( policeIntegrees.length ); // affiche : [object Lovitz] trace( policeIntegrees[0] ); } Enfin, nous créons un champ texte et affectons la police chargée dynamiquement à l?aide d?un objet TextFormat : var monTexte:TextField = new TextField(); monTexte.embedFonts = true; monTexte.autoSize = TextFieldAutoSize.LEFT; addChild ( monTexte ); var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.load ( new URLRequest("bibliotheque.swf") ); function chargementTermine ( pEvt:Event ):void { Chapitre 16 ? Le texte ? version 0.1.1 55 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org var DefinitionClasse:Class = Class ( pEvt.target.applicationDomain.getDefinition("Lovitz") ); Font.registerFont ( DefinitionClasse ); var policeBibliotheque:Font = new DefinitionClasse(); monTexte.defaultTextFormat = new TextFormat(policeBibliotheque.fontName, 64, 0); monTexte.htmlText = "Police chargée dynamiquement !"; } La figure 16-35 illustre le résultat : Figure 16-35. Formatage du texte à l?aide d?une police dynamique. Une fois la police chargée et enregistrée parmi la liste des polices disponibles, nous pouvons l?utiliser en conjonction avec une feuille de style. Nous ajoutons le nom de la police officiel grâce à l?attribut font- family de la feuille de style : .main { font-family:Lovitz; font-style:italic; font-size:42; color:#CC00CC; } Attention, le nom de police utilisée ici doit être le nom officiel de la police et non le nom de la classe de police associée. Puis nous associons une feuille de style ayant recours à la police dynamique : var monTexte:TextField = new TextField(); monTexte.embedFonts = true; monTexte.rotation = 45; monTexte.autoSize = TextFieldAutoSize.LEFT; monTexte.wordWrap = true; Chapitre 16 ? Le texte ? version 0.1.1 56 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org monTexte.width = 250; addChild ( monTexte ); var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementPoliceTermine ); chargeur.load ( new URLRequest("bibliotheque.swf") ); var chargeurCSS:URLLoader = new URLLoader(); chargeurCSS.dataFormat = URLLoaderDataFormat.TEXT; chargeurCSS.addEventListener ( Event.COMPLETE, chargementCSSTermine ); function chargementPoliceTermine ( pEvt:Event ):void { var definitionClasse:Class = Class ( pEvt.target.applicationDomain.getDefinition("Lovitz") ); Font.registerFont ( definitionClasse ); var requete:URLRequest = new URLRequest ("style.css"); // une fois la police chargée et enregistrée nous chargeons la feuille de style chargeurCSS.load ( requete ); } function chargementCSSTermine ( pEvt:Event ):void { var feuilleDeStyle:StyleSheet = new StyleSheet(); feuilleDeStyle.parseCSS ( pEvt.target.data ); monTexte.styleSheet = feuilleDeStyle; monTexte.htmlText = "<span class='main'>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ligula. Proin tristique. Sed semper enim at augue. In dictum. Pellentesque pellentesque dui pulvinar nisi. Fusce eu tortor non lorem semper iaculis. Pellentesque nisl dui, lacinia vitae, vehicula a, pellentesque eget, urna. Aliquam erat volutpat. Praesent et massa vel augue aliquam iaculis. In et quam. Nulla ut ligula. Ut porttitor. Vestibulum elit purus, auctor non, commodo sit amet, vestibulum ut, lorem.</span>"; } monTexte.x = ( stage.stageWidth - monTexte.width ) / 2; monTexte.y = ( stage.stageHeight - monTexte.height ) / 2; La figure 16-36 illustre le rendu du texte : Chapitre 16 ? Le texte ? version 0.1.1 57 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-36. Police dynamique et feuille de style. La rotation du texte nous confirme que la police Lovitz est correctement intégrée à l?application, le rendu du texte est donc assuré quelque soit le poste visionnant l?animation. A retenir ? ActionScript 3 simplifie le chargement de police dynamique. ? La police est intégrée à un SWF, puis chargée dynamiquement. ? La méthode getDefinition de l?objet ApplicationDomain permet d?extraire la définition de classe. ? La méthode statique registerFont de la classe Font permet d?enregistrer la définition de classe dans la liste globale des polices disponibles. La police est alors accessible auprès de tous les SWF de l?application. Détecter les coordonnées de texte Nous avons vu que de nombreuses méthodes ont été ajoutées à la classe TextField en ActionScript 3. Nous allons tirer profit d?une nouvelle méthode appelée getCharBoundaries dont voici la signature : public function getCharBoundaries(charIndex:int):Rectangle Chapitre 16 ? Le texte ? version 0.1.1 58 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Celle-ci attend en paramètre l?index du caractère à rechercher et renvoie ses coordonnées sous la forme d?une instance de la classe flash.geom.Rectangle. Grâce à cela, nous allons créer un système de gestion d?émoticônes. Au sein d?un nouveau document Flash CS3, nous créons un nouveau symbole clip liée à une classe auto-générée Emoticone. Ce symbole contient différentes émoticônes sur chaque image le composant. La figure 16-37 illustre les différentes images du clip : Figure 16-37. Symbole content les différentes émoticônes. Nous créons à présent un symbole clip contenant un champ texte nommé contenuTexte puis nous associons la classe MoteurEmoticone suivante par le panneau Propriétés de liaison : package org.bytearray.emoticones { import flash.display.Sprite; import flash.text.TextField; public class MoteurEmoticone extends Sprite { public var contenuTexte:TextField; public function MoteurEmoticone () { } Chapitre 16 ? Le texte ? version 0.1.1 59 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } } Au sein du même document, nous instancions notre champ texte à gestion d?émoticônes à travers la classe de document suivante : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.emoticones.MoteurEmoticone; public class Document extends ApplicationDefaut { public var champTexteEmoticone:MoteurEmoticone; public function Document () { champTexteEmoticone = new MoteurEmoticone(); champTexteEmoticone.x = (stage.stageWidth - champTexteEmoticone.width) / 2; champTexteEmoticone.y = (stage.stageHeight - champTexteEmoticone.height) / 2; addChild ( champTexteEmoticone ); } } } La figure 16-38 illustre le résultat : Chapitre 16 ? Le texte ? version 0.1.1 60 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-38. Affichage du moteur d?émoticônes. Afin d?écouter la saisie utilisateur et le défilement du contenu, nous écoutons les événements Event.CHANGE et Event.SCROLL auprès du champ texte : package org.bytearray.emoticones { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; public class MoteurEmoticone extends Sprite { public var contenuTexte:TextField; public function MoteurEmoticone () { contenuTexte.addEventListener ( Event.CHANGE, saisieUtilisateur ); contenuTexte.addEventListener ( Event.SCROLL, saisieUtilisateur ); } private function saisieUtilisateur ( pEvt:Event ):void { Chapitre 16 ? Le texte ? version 0.1.1 61 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : texte saisie trace( pEvt.target.text ); } } } Lorsque du contenu est saisi dans le champ la méthode saisieUtilisateur est exécutée. Afin de savoir si l?utilisateur a saisie une combinaison de touches correspondant à une émoticône, nous ajoutons un tableau nommé codesTouches contenant le code des combinaisons à saisir afin d?afficher une émoticône : package org.bytearray.emoticones { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; public class MoteurEmoticone extends Sprite { public var contenuTexte:TextField; public var codesTouches:Array; public var lngTouches:int; public function MoteurEmoticone () { codesTouches = new Array (":)",":D",":(",";)",":p"); lngTouches = codesTouches.length contenuTexte.addEventListener ( Event.CHANGE, saisieUtilisateur ); contenuTexte.addEventListener ( Event.SCROLL, saisieUtilisateur ); } private function saisieUtilisateur ( pEvt:Event ):void { // affiche : texte saisie trace( pEvt.target.text ); } } Chapitre 16 ? Le texte ? version 0.1.1 62 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } Au sein de la méthode saisieUtilisateur nous ajoutons une simple recherche de chaque combinaison à l?aide de la méthode indexOf de la classe String : private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); if ( j != -1 ) trace( "Combinaison trouvée à l?index : " + j ); } } Si nous saisissons une combinaison contenue au sein du tableau codeTouches la variable j renvoie alors la position de la combinaison au sein du champ texte. En cas de saisie multiple, seule la première combinaison est détectée car la méthode indexOf ne renvoie que la première chaîne trouvée. Afin de corriger cela, nous ajoutons une boucle imbriquée afin de vérifier les multiples combinaisons existantes au sein du champ : private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); while ( j!= -1 ) { trace( "Combinaison trouvée à l?index : " + j ); j = pEvt.target.text.indexOf ( codesTouches[i], j+1 ); } } } Chapitre 16 ? Le texte ? version 0.1.1 63 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous testons le code précédent nous remarquons que les multiples combinaisons sont détectées. Grâce à la méthode getCharBoundaries nous pouvons récupérer les coordonnées x et y de chaque combinaison au sein du champ contenuTexte. Nous ajoutons une propriété coordonnees au sein de la classe, puis nous modifions la méthode saisieUtilisateur : package org.bytearray.emoticones { import flash.display.Sprite; import flash.events.Event; import flash.geom.Rectangle; import flash.text.TextField; public class MoteurEmoticone extends Sprite { public var contenuTexte:TextField; public var codesTouches:Array; public var lngTouches:int; public var coordonnees:Rectangle; public function MoteurEmoticone () { codesTouches = new Array (":)",":D",":(",";)",":p"); lngTouches = codesTouches.length contenuTexte.addEventListener ( Event.CHANGE, saisieUtilisateur ); contenuTexte.addEventListener ( Event.SCROLL, saisieUtilisateur ); } private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); while ( j!= -1 ) { coordonnees = pEvt.target.getCharBoundaries ( j ); Chapitre 16 ? Le texte ? version 0.1.1 64 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : Combinaison trouvée à la position : (x=62, y=2, w=7, h=18) trace( "Combinaison trouvée à la position : " + coordonnees ); j = pEvt.target.text.indexOf ( codesTouches[i], j+1 ); } } } } } La propriété coordonnees détermine la position de chaque combinaison de touches. Nous devons à présent interpréter ces coordonnées afin d?afficher l?émoticône correspondante. Nous modifions à nouveau la méthode saisieUtilisateur en appelant la méthode ajouteEmoticone : private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); while ( j!= -1 ) { coordonnees = pEvt.target.getCharBoundaries ( j ); if ( coordonnees != null ) ajouteEmoticone ( coordonnees, i ); j = pEvt.target.text.indexOf ( codesTouches[i], j+1 ); } } } Puis nous définissons une propriété icone de type Emoticone ainsi que la méthode ajouteEmoticone : private function ajouteEmoticone ( pRectangle:Rectangle, pIndex:int ):Emoticone { Chapitre 16 ? Le texte ? version 0.1.1 65 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org icone = new Emoticone(); icone.gotoAndStop ( pIndex + 1 ); icone.x = pRectangle.x + 1; icone.y = pRectangle.y - ((contenuTexte.scrollV - 1)*(contenuTexte.textHeight/contenuTexte.numLines))+1; addChild ( icone ); return icone; } Si nous testons le code actuel, nous remarquons que les émoticônes sont affichées correctement. Si nous revenons en arrière lors de la saisie nous devons supprimer les émoticônes déjà présentes afin de ne pas les conserver à l?affichage. Nous devons définir une propriété tableauEmoticones contenant la référence de chaque émoticône ajouté, puis créer un nouveau tableau au sein du constructeur : public function MoteurEmoticone () { tableauEmoticones = new Array(); codesTouches = new Array (":)",":D",":(",";)",":p"); lngTouches = codesTouches.length; contenuTexte.addEventListener ( Event.CHANGE, saisieUtilisateur ); contenuTexte.addEventListener ( Event.SCROLL, saisieUtilisateur ); } A chaque saisie utilisateur nous supprimons toutes les émoticônes créées auparavant afin de nettoyer l?affichage : private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; var nombreEmoticones:int = tableauEmoticones.length; for ( i = 0; i< nombreEmoticones; i++ ) removeChild (tableauEmoticones[i] ); tableauEmoticones = new Array(); for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); Chapitre 16 ? Le texte ? version 0.1.1 66 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org while ( j!= -1 ) { coordonnees = pEvt.target.getCharBoundaries ( j ); if ( coordonnees != null ) tableauEmoticones.push ( ajouteEmoticone ( coordonnees, i ) ); j = pEvt.target.text.indexOf ( codesTouches[i], j+1 ); } } } Voici le code complet de la classe MoteurEmoticone : package org.bytearray.emoticones { import flash.display.Sprite; import flash.events.Event; import flash.geom.Rectangle; import flash.text.TextField; public class MoteurEmoticone extends Sprite { public var contenuTexte:TextField; public var codesTouches:Array; public var lngTouches:int; public var coordonnees:Rectangle; public var icone:Emoticone; public var tableauEmoticones:Array; public function MoteurEmoticone () { tableauEmoticones = new Array(); codesTouches = new Array (":)",":D",":(",";)",":p"); lngTouches = codesTouches.length; contenuTexte.addEventListener ( Event.CHANGE, saisieUtilisateur ); contenuTexte.addEventListener ( Event.SCROLL, saisieUtilisateur ); } private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; Chapitre 16 ? Le texte ? version 0.1.1 67 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org var nombreEmoticones:int = tableauEmoticones.length; for ( i = 0; i< nombreEmoticones; i++ ) removeChild (tableauEmoticones[i] ); tableauEmoticones = new Array(); for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); while ( j!= -1 ) { coordonnees = pEvt.target.getCharBoundaries ( j ); if ( coordonnees != null ) tableauEmoticones.push ( ajouteEmoticone ( coordonnees, i ) ); j = pEvt.target.text.indexOf ( codesTouches[i], j+1 ); } } } private function ajouteEmoticone ( pRectangle:Rectangle, pIndex:int ):Emoticone { icone = new Emoticone(); icone.gotoAndStop ( pIndex + 1 ); icone.x = pRectangle.x + 1; icone.y = pRectangle.y - ((contenuTexte.scrollV - 1)*(contenuTexte.textHeight/contenuTexte.numLines))+1; addChild ( icone ); return icone; } } } La classe MoteurEmoticone peut ainsi être intégrée à un chat connecté par XMLSocket ou Flash Media Server, nous la réutiliserons au cours du chapitre 18 intitulé Sockets. D?autres émoticônes pourraient être intégrées, ainsi que d?autres fonctionnalités. Afin d?aller plus loin, nous allons créer un éditeur de texte enrichi. Celui-ci nous permettra de mettre en forme du texte de manière simplifiée. Chapitre 16 ? Le texte ? version 0.1.1 68 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? La méthode getCharBoundaries permet de connaître la position exate d?un caractère au sein d?un champ texte en retournant un objet Rectangle. Créer un éditeur de texte Nous avons très souvent besoin d?éditer du texte de manière avançée au sein d?applications Flash. La classe TextField a été grandement enrichie en ActionScript 3. De nombreuses méthodes et propriétés facilitent le développement d?applications ayant recours au texte. Afin de mettre en application les notions précédemment étudiées, nous allons développer à présent un éditeur de texte. La figure 16-39 illustre l?éditeur en question : Figure 16-39. Editeur de texte. Nous allons utiliser pour sa conception les différentes propriétés et méthodes listées ci-dessous : ? setTextFormat : la méthode setTextFormat nous permet d?affecter un style particulier à une sélection ; ? getTextFormat : la méthode getTextFormat nous permet de récupérer un style existant lié à une sélection afin de le modifier ; ? selectionBeginIndex : la propriété selectionBeginIndex permet de connaître le debut de selection du texte ; ? selectionEndIndex : la propriété selectionEndIndex permet de connaître la fin de sélection du texte ; ? alwaysShowSelection : la propriété alwaysShowSelection permet de conserver le texte sélectionné bien que la souris clique sur un autre élément interactif ; Nous commençons par créer la classe EditeurTexte suivante au sein du paquetage org.bytearray.richtext : package org.bytearray.texte.editeur { import flash.display.Sprite; import flash.events.Event; Chapitre 16 ? Le texte ? version 0.1.1 69 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org public class EditeurTexte extends Sprite { public function EditeurTexte () { addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { trace("activation"); } private function desactivation ( pEvt:Event ):void { trace("desactivation"); } } } Nous écoutons les événements Event.ADDED_TO_STAGE et Event.REMOVED_FROM_STAGE afin de gérer l?activation et la désactivation de l?éditeur. Sur la scène nous créons un clip contenant les différents boutons graphiques comme l?illustre la figure 16-40 : Figure 16-40. Editeur de texte enrichi. Grace au panneau Propriétés de liaison, nous lions ce clip à notre classe EditeurTexte précédemment définie. Nous ajoutons les écouteurs auprès des différents boutons de l?interface au sein de la classe EditeurTexte : package org.bytearray.texte.editeur { Chapitre 16 ? Le texte ? version 0.1.1 70 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.display.Sprite; import flash.display.SimpleButton; import flash.text.TextField; import flash.events.Event; import flash.events.MouseEvent; import fl.controls.ComboBox; import fl.controls.ColorPicker; import fl.events.ColorPickerEvent; public class EditeurTexte extends Sprite { public var boutonAlignerGauche:SimpleButton; public var boutonAlignerCentre:SimpleButton; public var boutonAlignerDroite:SimpleButton; public var boutonAlignerJustifier:SimpleButton; public var boutonCrenage:SimpleButton; public var boutonGras:SimpleButton; public var boutonItalique:SimpleButton; public var boutonSousLigne:SimpleButton; public var boutonLien:SimpleButton; public var champLien:TextField; public var listeDeroulantesPolices:ComboBox; public var nuancier:ColorPicker; public function EditeurTexte () { addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { boutonGras.addEventListener( MouseEvent.CLICK, affecteGras ); boutonItalique.addEventListener( MouseEvent.CLICK, affecteItalique ); boutonSousLigne.addEventListener( MouseEvent.CLICK, affecteSousLigne ); boutonAlignerGauche.addEventListener( MouseEvent.CLICK, alignerGauche ); boutonAlignerCentre.addEventListener( MouseEvent.CLICK, alignerCentre ); boutonAlignerDroite.addEventListener( MouseEvent.CLICK, alignerDroite ); boutonAlignerJustifier.addEventListener( MouseEvent.CLICK, alignerJustifier ); nuancier.addEventListener ( ColorPickerEvent.CHANGE, couleurSelection ); boutonCrenage.addEventListener ( MouseEvent.CLICK, affecteCrenage ); listeDeroulantesPolices.addEventListener ( Event.CHANGE, affectePolice ); boutonLien.addEventListener ( MouseEvent.CLICK, affecteLien ); } Chapitre 16 ? Le texte ? version 0.1.1 71 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org private function desactivation ( pEvt:Event ):void { boutonGras.removeEventListener( MouseEvent.CLICK, affecteGras ); boutonItalique.removeEventListener( MouseEvent.CLICK, affecteItalique ); boutonSousLigne.removeEventListener( MouseEvent.CLICK, affecteSousLigne ); boutonAlignerGauche.removeEventListener( MouseEvent.CLICK, alignerGauche ); boutonAlignerCentre.removeEventListener( MouseEvent.CLICK, alignerCentre ); boutonAlignerDroite.removeEventListener( MouseEvent.CLICK, alignerDroite ); boutonAlignerJustifier.removeEventListener( MouseEvent.CLICK, alignerJustifier ); nuancier.removeEventListener ( ColorPickerEvent.CHANGE, couleurSelection ); boutonCrenage.removeEventListener ( MouseEvent.CLICK, affecteCrenage ); listeDeroulantesPolices.removeEventListener ( Event.CHANGE, affectePolice ); boutonLien.removeEventListener ( MouseEvent.CLICK, affecteLien ); } function affectePolice ( pEvt:Event ):void { } function affecteCrenage ( pEvt:MouseEvent ):void { } function alignerJustifier ( pEvt:MouseEvent ):void { } function couleurSelection ( pEvt:ColorPickerEvent ):void { } function alignerGauche ( pEvt:MouseEvent ):void { } function alignerCentre ( pEvt:MouseEvent ):void { Chapitre 16 ? Le texte ? version 0.1.1 72 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } function alignerDroite ( pEvt:MouseEvent ):void { } function affecteGras ( pEvt:MouseEvent ):void { } function affecteItalique ( pEvt:MouseEvent ):void { } function affecteSousLigne ( pEvt:MouseEvent ):void { } private function affecteLien ( pEvt:MouseEvent ):void { } } } Nous remplissons la liste déroulante en énumérant les polices installées. Nous définissons une méthode affichePolices : private function affichePolices ():void { var polices:Array = Font.enumerateFonts(true); var donnees:Array = new Array(); for ( var p:String in polices ) donnees.push ( { label : polices[p].fontName, data : polices[p].fontName } ); listeDeroulantesPolices.dataProvider = new DataProvider ( donnees ); } Puis nous importons les classes nécessaires : import flash.text.Font; import fl.data.DataProvider; Nous modifions la méthode activation en appelant la méthode affichePolices : Chapitre 16 ? Le texte ? version 0.1.1 73 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org private function activation ( pEvt:Event ):void { boutonGras.addEventListener( MouseEvent.CLICK, affecteGras ); boutonItalique.addEventListener( MouseEvent.CLICK, affecteItalique ); boutonSousLigne.addEventListener( MouseEvent.CLICK, affecteSousLigne ); boutonAlignerGauche.addEventListener( MouseEvent.CLICK, alignerGauche ); boutonAlignerCentre.addEventListener( MouseEvent.CLICK, alignerCentre ); boutonAlignerDroite.addEventListener( MouseEvent.CLICK, alignerDroite ); boutonAlignerJustifier.addEventListener( MouseEvent.CLICK, alignerJustifier ); nuancier.addEventListener ( ColorPickerEvent.CHANGE, couleurSelection ); boutonCrenage.addEventListener ( MouseEvent.CLICK, affecteCrenage ); listeDeroulantesPolices.addEventListener ( Event.CHANGE, affectePolice ); boutonLien.addEventListener ( MouseEvent.CLICK, affecteLien ); affichePolices(); } Si nous testons notre application, la liste déroulante de l?éditeur enrichi doit afficher la totalité des polices présentes sur la machine client comme l?illustre la figure 16-41 : Figure 16-41. Editeur de texte enrichi. Afin de stocker le style en cours nous définissons une propriété formatEnCours : private var formatEnCours:TextFormat; Pour cela, nous ajoutons une propriété champCible : private var champCible:TextField; Puis une méthode cible permettant de spécifier le champ texte à enrichir : public function cible ( pChamp:TextField ):void { if ( pChamp != champCible ) { champCible = pChamp; champCible.alwaysShowSelection = true; } Chapitre 16 ? Le texte ? version 0.1.1 74 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } La logique de l?éditeur est quasi complète, il ne nous reste plus qu?à définir la logique nécessaire au sein de chaque fonction d?affectation de style. Voici le code complet de la classe EditeurTexte : package org.bytearray.texte.editeur { import flash.display.Sprite; import flash.display.SimpleButton; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFormatAlign; import flash.events.Event; import flash.events.MouseEvent; import fl.controls.ComboBox; import fl.controls.ColorPicker; import fl.events.ColorPickerEvent; import flash.text.Font; import fl.data.DataProvider; public class EditeurTexte extends Sprite { public var boutonAlignerGauche:SimpleButton; public var boutonAlignerCentre:SimpleButton; public var boutonAlignerDroite:SimpleButton; public var boutonAlignerJustifier:SimpleButton; public var boutonCrenage:SimpleButton; public var boutonGras:SimpleButton; public var boutonItalique:SimpleButton; public var boutonSousLigne:SimpleButton; public var boutonLien:SimpleButton; public var champLien:TextField; public var listeDeroulantesPolices:ComboBox; public var nuancier:ColorPicker; private var formatEnCours:TextFormat; private var champCible:TextField; public function EditeurTexte () { addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { boutonGras.addEventListener( MouseEvent.CLICK, affecteGras ); Chapitre 16 ? Le texte ? version 0.1.1 75 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org boutonItalique.addEventListener( MouseEvent.CLICK, affecteItalique ); boutonSousLigne.addEventListener( MouseEvent.CLICK, affecteSousLigne ); boutonAlignerGauche.addEventListener( MouseEvent.CLICK, alignerGauche ); boutonAlignerCentre.addEventListener( MouseEvent.CLICK, alignerCentre ); boutonAlignerDroite.addEventListener( MouseEvent.CLICK, alignerDroite ); boutonAlignerJustifier.addEventListener( MouseEvent.CLICK, alignerJustifier ); nuancier.addEventListener ( ColorPickerEvent.CHANGE, couleurSelection ); boutonCrenage.addEventListener ( MouseEvent.CLICK, affecteCrenage ); listeDeroulantesPolices.addEventListener ( Event.CHANGE, affectePolice ); boutonLien.addEventListener ( MouseEvent.CLICK, affecteLien ); affichePolices(); } public function cible ( pChamp:TextField ):void { if ( pChamp != champCible ) { champCible = pChamp; champCible.alwaysShowSelection = true; } } private function desactivation ( pEvt:Event ):void { boutonGras.removeEventListener( MouseEvent.CLICK, affecteGras ); boutonItalique.removeEventListener( MouseEvent.CLICK, affecteItalique ); boutonSousLigne.removeEventListener( MouseEvent.CLICK, affecteSousLigne ); boutonAlignerGauche.removeEventListener( MouseEvent.CLICK, alignerGauche ); boutonAlignerCentre.removeEventListener( MouseEvent.CLICK, alignerCentre ); boutonAlignerDroite.removeEventListener( MouseEvent.CLICK, alignerDroite ); boutonAlignerJustifier.removeEventListener( MouseEvent.CLICK, alignerJustifier ); nuancier.removeEventListener ( ColorPickerEvent.CHANGE, couleurSelection ); boutonCrenage.removeEventListener ( MouseEvent.CLICK, affecteCrenage ); listeDeroulantesPolices.removeEventListener ( Event.CHANGE, affectePolice ); boutonLien.removeEventListener ( MouseEvent.CLICK, affecteLien ); Chapitre 16 ? Le texte ? version 0.1.1 76 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org } private function affichePolices ():void { var polices:Array = Font.enumerateFonts(true); var donnees:Array = new Array(); for (var p in polices ) donnees.push ( { label : polices[p].fontName, data : polices[p].fontName } ); listeDeroulantesPolices.dataProvider = new DataProvider (donnees); } function affecteGras ( pEvt:MouseEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.bold = !formatEnCours.bold; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function affecteItalique ( pEvt:MouseEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.italic = !formatEnCours.italic; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function affecteSousLigne ( pEvt:MouseEvent ):void { Chapitre 16 ? Le texte ? version 0.1.1 77 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.underline = !formatEnCours.underline; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function alignerGauche ( pEvt:MouseEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.align = ( formatEnCours.align != TextFormatAlign.LEFT ) ? TextFormatAlign.LEFT : TextFormatAlign.LEFT; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function alignerCentre ( pEvt:MouseEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.align = ( formatEnCours.align != TextFormatAlign.CENTER ) ? TextFormatAlign.CENTER : TextFormatAlign.LEFT; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function alignerDroite ( pEvt:MouseEvent ):void { Chapitre 16 ? Le texte ? version 0.1.1 78 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.align = ( formatEnCours.align != TextFormatAlign.RIGHT ) ? TextFormatAlign.RIGHT : TextFormatAlign.LEFT; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function alignerJustifier ( pEvt:MouseEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.align = ( formatEnCours.align != TextFormatAlign.JUSTIFY ) ? TextFormatAlign.JUSTIFY : TextFormatAlign.LEFT; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function couleurSelection ( pEvt:ColorPickerEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.color = pEvt.color; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function affecteCrenage ( pEvt:MouseEvent ):void { Chapitre 16 ? Le texte ? version 0.1.1 79 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.kerning = !formatEnCours.kerning; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } function affectePolice ( pEvt:Event ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.font = ( formatEnCours.font != pEvt.target.selectedItem.data ) ? pEvt.target.selectedItem.data : "Verdana"; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } private function affecteLien ( pEvt:MouseEvent ):void { if ( champCible != null ) { formatEnCours = champCible.getTextFormat( champCible.selectionBeginIndex, champCible.selectionEndIndex ); formatEnCours.url = formatEnCours.url == "" ? champLien.text : ""; champCible.setTextFormat( formatEnCours, champCible.selectionBeginIndex, champCible.selectionEndIndex ); } else throw new Error ("Le champ cible n'a pas été défini."); } } } Chapitre 16 ? Le texte ? version 0.1.1 80 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de tester notre éditeur, nous associons la classe de document suivante : package org.bytearray.document { import flash.display.Sprite; import flash.text.TextField; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.texte.editeur.EditeurTexte; public class Document extends ApplicationDefaut { private var editeur:EditeurTexte; public var texteCible:TextField; public function Document () { // instanciation de l'éditeur enrichi editeur = new EditeurTexte(); // positionnement editeur.x = editeur.y = 15; // ajout à l'affichage addChild ( editeur ); // affectation du champ texte cible editeur.cible ( texteCible ); } } } La figure 16-42 illustre l?éditeur de texte en action : Chapitre 16 ? Le texte ? version 0.1.1 81 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 16-42. Editeur de texte finalisé. Il convient de s?attarder quelques instants sur la propriété alwaysShowSelection de la classe TextField. ActionScript 3 introduit cette propriété facilitant grandement le développement d?un éditeur comme celui que nous venons de terminer. La puissance de cette propriété réside dans la conservation de la sélection du texte bien que l?utilisateur entre en interaction avec d?autres éléments graphiques. Cela nous permet de récupérer facilement la sélection en cours à l?aide des propriétés selectionBeginIndex et selectionEndIndex et d?affecter le style voulu. D?autres fonctionnalités pourraient être ajoutées à l?éditeur, nous pourrions imaginer un export de la mise en forme du contenu texte sous la forme de feuille de style CSS, ou encore d?autres fonctionnalités liées à la mise en forme. A retenir Chapitre 16 ? Le texte ? version 0.1.1 82 / 82 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les propriétés selectionBeginIndex et selectionEndIndex nous permettent de connaître la selection en cours. ? Grace aux méthodes getTextFormat et setTextFormat, nous pouvons récupérer le style d?une partie du texte, le modifier et l?affecter à nouveau. ? La propriété alwaysShowSelection permet de conserver la selection du texte, même si la souris entre en intéraction avec d?autres objets interactifs. Au cours du prochain chapitre nous découvrirons les nouvelles fonctionnalités apportées par ActionScript 3 en matière de son et de vidéo. Chapitre 17 ? Son et vidéo ? version 0.1.1 1 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org 17 Son et vidéo LECTURE DE SONS .............................................................................................. 1 LIRE UN SON PROVENANT DE LA BIBLIOTHEQUE ..................................................... 2 LIRE UN SON DYNAMIQUE ....................................................................................... 4 LA CLASSE SOUNDLOADERCONTEXT ..................................................................... 7 TRANSFORMATION DU SON ..................................................................................... 9 MODIFICATION GLOBALE DU SON ......................................................................... 30 LIRE LE SPECTRE D?UN SON................................................................................... 31 TRANSFORMÉE DE FOURIER.................................................................................. 55 LE FORMAT MPEG-4 AUDIO ................................................................................ 58 LA VIDEO DANS FLASH.................................................................................... 62 LE FORMAT MPEG-4 VIDEO................................................................................. 63 LA CLASSE VIDEO ................................................................................................. 65 TRANSFORMATION DU SON LIE A UN OBJET NETSTREAM...................................... 69 MODE PLEIN-ECRAN.............................................................................................. 70 Lecture de sons Le son fut d?abord considéré comme un élément secondaire sur le web. La situation s?est inversée peu à peu jusqu?à aujourd?hui où le lecteur Flash figure parmi les principaux promoteurs du son. De nombreuses sociétés ont d?ailleurs choisi le lecteur Flash comme plateforme de diffusion ou de lecture. Nous allons découvrir au cours de ce chapitre comment utiliser les différentes classes liées au son et à la vidéo en ActionScript 3, afin d?intégrer au mieux ces supports dans nos applications Flash. Nous allons nous attarder dès à présent sur les différentes classes liées au son dans Flash : ? flash.media.Sound : la classe Sound permet la lecture de sons. Chapitre 17 ? Son et vidéo ? version 0.1.1 2 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? flash.media.SoundTransform : la classe SoundTransform permet de modifier le son (volume, balance). ? flash.media.SoundChannel : la classe SoundChannel permet de contrôler le son. Chaque son en cours de lecture est associé à un objet SoundChannel. ? flash.media.LoaderContext : la classe LoaderContext est liée au préchargement et au modèle de securité du lecteur Flash. ? flash.media.SoundMixer : la classe SoundMixer offre un contrôle global sur les sons en cours de lecture. ? flash.net.NetConnection : la classe NetConnection est utilisée pour le chargement de fichiers audio MPEG-4. ? flash.net.NetStream : la classe NetStream est utilisée pour la manipulation de fichiers audio MPEG-4. La liste peut paraître longue, mais nous verrons que leur utilisation s?avère d?une étonnante simplicité. Lire un son provenant de la bibliothèque Afin d?entamer notre aventure, nous allons commencer par lire un son provenant de la bibliothèque. Pour cela, nous utilisons le raccourci clavier CTRL+R ou l?option Importer dans la bibliothèque. Par le panneau Liaisons, nous associons le son en bibliothèque à une classe auto générée Rythmique, celle-ci étend la classe Sound : Figure 17-1. Son associée à la classe Rythmique auto générée. Le son est instancié à l?aide de l?opérateur new : // instanciation du son var rythme:Rythmique = new Rythmique(); Chapitre 17 ? Son et vidéo ? version 0.1.1 3 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de démarrer la lecture du son créé, nous utilisons la méthode play dont voici la signature : public function play(startTime:Number = 0, loops:int = 0, sndTransform:SoundTransform = null):SoundChannel Celle-ci accepte trois paramètres : ? startTime : la position en milli secondes à partir de laquelle la lecture doit commencer. ? loops : nombre de boucles. ? sndTransform : un objet de transformation associé au son joué. Nous reviendrons très vite sur la classe SoundTransform. Contrairement à la classe Sound présente en ActionScript 1 et 2, la classe Sound ne dispose pas en ActionScript 3 de méthode stop. L?appel de la méthode play affecte le son joué à un canal audio, et retourne un objet de type SoundChannel représentant ce canal. C?est grâce à ce dernier que nous pouvons contrôler le son et l?arrêter. Notons que le lecteur Flash 9 peut lire désormais jusqu?à 32 canaux simultanés. Dans le code suivant, nous lisons le son depuis le départ une seule fois seulement : // instanciation du son var rythme:Rythmique = new Rythmique(); // lecture du son et recupération de l'objet SoundChannel associé au son var canalRythme:SoundChannel = rythme.play(); En spécifiant le paramètre startTime, nous pouvons décaler le point de départ de la lecture du son : var canalRythme:SoundChannel = rythme.play( 6000 ); Le son est ainsi lu à partir de la sixième seconde. Par défaut, le son est joué une seule fois, mais nous pouvons spécifier un nombre de boucles grâce au deuxième paramètre loops : var canalRythme:SoundChannel = rythme.play( 6000, 2 ); Notons que si un nombre de boucles est spécifié ainsi qu?une position de départ, celle-ci est conservée lors de la boucle. Lors de la lecture en boucle d?un son, la propriété position de l?objet SoundChannel ne se réinitialise pas à zéro. Pour un son d?une durée de 5000 milli- secondes lu en boucle 3 fois, celle-ci vaut 15 000 ms en Chapitre 17 ? Son et vidéo ? version 0.1.1 4 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org fin de lecture. Il s?agit d?un bogue du lecteur Flash 9.0.115. L?objet SoundChannel étant renvoyé par l?appel de la méthode play, il n?est pas possible d?étendre la classe SoundChannel afin de corriger ce bogue. A l?aide de la méthode stop définie par la classe SoundChannel, nous stoppons le son lorsque l?utilisateur clique sur la scène : // instanciation du son var rythme:Rythmique = new Rythmique(); // lecture du son et récupération de l'objet SoundChannel associé au son var canalRythme:SoundChannel = rythme.play( 6000, 2 ); stage.addEventListener( MouseEvent.CLICK, stoppeSon ); function stoppeSon ( pEvt:MouseEvent ):void { // le son est coupé canalRythme.stop(); } Afin de garantir un poids minimum et d?assurer un chargement à la demande, il peut être intéressant de charger dynamiquement les sons de l?application, c?est ce que nous allons voir dans cette nouvelle partie. A retenir ? La méthode play démarre la lecture du son et l?associe à un canal audio représenté par un objet SoundChannel. ? Le lecteur Flash 9 permet la lecture de 32 sons simultanés. ? La lecture du son associé au canal est interrompue grâce à la méthode stop de l?objet SoundChannel. Lire un son dynamique Le chargement de son peut être réalisé de manière dynamique. Les fichiers audio résident à l?extérieur de l?animation et sont chargées dynamiquement. Deux techniques peuvent être employées : La première consiste à créer un objet URLRequest valide pointant vers le fichier MP3 et passer ce dernier au constructeur de la classe Sound : Chapitre 17 ? Son et vidéo ? version 0.1.1 5 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'un objet Sound, la méthode load est automatiquement appelée var monSon:Sound = new Sound ( new URLRequest ("son.mp3") ); Si la classe Sound détecte un objet URLRequest valide, le son est chargé automatiquement au sein du lecteur à l?aide de la méthode load. Le cas échéant, l?appel de la méthode load est obligatoire pour démarrer le chargement du son. La méthode load possède la signature suivante : public function load(stream:URLRequest, context:SoundLoaderContext = null):void Voici le détail de chacun des paramètres : ? stream : un objet URLRequest pointant vers le fichier MP3 à charger. ? context : un objet SoundLoaderContext spécifiant la durée de préchargement en mémoire tampon ainsi que des consignes liées au chargement de fichiers de régulation. Attention, contrairement à l?intégration de son au sein de la bibliothèque compatible avec la plupart des formats audio, la méthode load de la classe Sound permet uniquement le chargement de fichiers MP3. Si nous tentons de charger un autre format de fichiers audio, aucune erreur spécifique n?est levée, le son n?est pas joué. La seconde technique consiste à créer l?objet Sound puis appeler manuellement la méthode load. Dans le code suivant nous ne passons pas d?objet URLRequest au constructeur de la classe Sound, le son est chargé à l?aide de la méthode load : // création d'un objet Sound var monSon:Sound = new Sound(); // chargement dynamique du son son.load ( new URLRequest ("son.mp3") ); Afin de gérer le chargement de son dynamique, l?objet Sound diffuse différents événements dont voici le détail : ? Event.COMPLETE : diffusé lorsque le chargement du son est terminé. ? Event.ID3: diffusé lorsque les informations ID3 sont disponibles. ? IOErrorEvent.IO_ERROR : diffusé lorsque le chargement du son échoue. ? Event.OPEN : diffusé lorsque le lecteur commence à charger le son. ? ProgressEvent.PROGRESS : diffusé lorsque le chargement est en cours. Celui-ci renseigne sur le nombre d?octets chargés et totaux. Dans le code suivant nous écoutons chacun des événements : Chapitre 17 ? Son et vidéo ? version 0.1.1 6 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org // instanciation d'un objet Sound var son:Sound = new Sound(); // chargement dynamique du son son.load ( new URLRequest ("son.mp3") ); // écoute des différents événements son.addEventListener( Event.OPEN, chargementDemarre ); son.addEventListener( Event.ID3, informationsID3 ); son.addEventListener( ProgressEvent.PROGRESS, chargementEnCours ); son.addEventListener( Event.COMPLETE, chargementTermine ); son.addEventListener( IOErrorEvent.IO_ERROR, erreurChargement ); function chargementDemarre ( pEvt:Event ):void { trace("chargement démarré"); } function informationsID3 ( pEvt:Event ):void { trace("informations ID3"); } function chargementEnCours ( pEvt:ProgressEvent ):void { trace("chargement en cours : " + pEvt.bytesLoaded + " / " + pEvt.bytesTotal ); } function chargementTermine ( pEvt:Event ):void { trace("chargement terminé"); } function erreurChargement ( pEvt:IOErrorEvent ):void { trace("erreur de chargement"); } Quelle que soit la technique employée pour charger dynamiquement un son, sa lecture doit être initiée à l?aide de la méthode play. Si celle-ci est appelée en même temps que la méthode load, la lecture du son est entamée lorsque suffisamment de données audio ont été téléchargées. Si nous tentons de démarrer la lecture du son avant l?avoir chargé, une erreur de type ArgumentError est levée : ArgumentError: Error #2068: Son non valide Chapitre 17 ? Son et vidéo ? version 0.1.1 7 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Attention, dans un contexte de chargement dynamique, la méthode load ne renvoie pas d?objet SoundChannel. Seule la méthode play le permet : // instanciation d'un objet Sound, la méthode load est automatiquement appelée var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // lecture du son, la méthode play retourne un objet SoundChannel var canalSon:SoundChannel = son.play(); Il est important de noter que le chargement dynamique de sons est régit par le modèle de sécurité du lecteur. Par défaut, le chargement et la lecture d?un fichier son provenant d?un domaine différent est autorisée, mais l?accès aux données du fichier son est régulée. Dans un contexte inter-domaine, l?utilisation de la propriété id3 de l?objet Sound, de la méthode computeSpectrum, de la classe SoundMixer ou de l?objet SoundTransform lève une erreur de type SecurityError. Il convient alors d?utiliser un fichier de régulation sur le domaine distant afin d?autoriser la manipulation du son. Rappelez-vous que le lecteur Flash ne charge pas automatiquement de fichier de régulation afin de limiter la bande passante utilisée. Nous avons vu au cours du chapitre 13 intitulé Chargement de contenu que la classe LoaderContext permettait de spécifier si le lecteur Flash devait tenter de charger un fichier de régulation. Une classe équivalente nommée SoundLoaderContext existe au sein du paquetage flash.media dans le cas de chargement de son dynamique. A retenir ? Le chargement d?un son dynamique est assuré par la méthode load de la classe Sound. ? Si la méthode play est appelée après l?appel de la méthode load, la lecture du son démarre lorsque le lecteur Flash a chargé suffisamment de données. La classe SoundLoaderContext Lorsqu?un son est chargé depuis un domaine différent, celui-ci peut seulement être chargé et lu. Afin d?extraire des informations du son, ou d?utiliser des méthodes telles computeSpectrum de la classe Chapitre 17 ? Son et vidéo ? version 0.1.1 8 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org SoundMixer, un fichier de régulation doit être placé sur le serveur l?hébergeant afin d?autoriser le SWF ayant initié le chargement. Nous avions utilisé au cours du chapitre 13 la classe LoaderContext afin d?indiquer au lecteur Flash de charger un fichier de régulation. Dans le cas de chargement de fichiers audio, nous devons utiliser la classe SoundLoaderContext dont voici la signature du constructeur : public function SoundLoaderContext(bufferTime:Number = 1000, checkPolicyFile:Boolean = false) Voici le détail de chaque paramètre : ? bufferTime : le paramètre bufferTime permet de définir le temps de préchargement en mémoire tampon avant que la lecture du son ne démarre. La valeur par défaut est de 1000 milli-secondes. Cette fonctionnalité permet de charger à l?avance quelques secondes du flux afin d?assurer une lecture ininterrompue en cas de problème de connexion. ? checkPolicyFile : en passant la valeur true au paramètre checkPolicyFile nous demandons au lecteur Flash de télécharger un fichier de régulation à la racine du serveur hébergeant le fichier audio. Dans le code suivant, nous chargeons un son hébergé sur un domaine distant, nous forçons le téléchargement d?un fichier de régulation : // création d'un objet son et chargement d'un mp3 var monSon:Sound = new Sound (); // création d'un objet SoundLoaderContext // 5 secondes sont mises en mémoire tampon // le lecteur Flash tente de charger un fichier de régulation var contexteAudio:SoundLoaderContext = new SoundLoaderContext ( 5000, true ); // chargement d'un son auprès d'un domaine distant monSon.load ( new URLRequest ("http://www.monDomaineDistant.org/son.mp3") ); // lecture du son var canalSon:SoundChannel = monSon.play(); Au lieu d?initialiser l?objet SoundLoaderContext par les paramètres du constructeur, les propriétés équivalentes peuvent être utilisées : // création d'un objet SoundLoaderContext var contexteAudio:SoundLoaderContext = new SoundLoaderContext (); // 5 secondes sont mises en mémoire tampon contexteAudio.bufferTime = 5000; // le lecteur Flash tente de charger un fichier de régulation contexteAudio.checkPolicyFile = true; // chargement d'un son auprès d'un domaine distant Chapitre 17 ? Son et vidéo ? version 0.1.1 9 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org monSon.load ( new URLRequest ("http://www.monDomaineDistant.org/son.mp3") ); Rappelez-vous, par défaut le lecteur Flash tente de charger le fichier de régulation à la racine du serveur. Dans le code précédent, le lecteur Flash tentera de charger le fichier de régulation à l?adresse suivante : http://www.monDomaineDistant.org/crossdomain.xml Si nous souhaitons spécifier un emplacement différent, nous utiliserons la méthode loadPolicyFile définie par la classe flash.system.Security. A retenir ? La classe SoundLoaderContext permet de préciser la durée de mise en mémoire tampon ainsi qu?une indication concernant le chargement du fichier de régulation. Transformation du son Afin de modifier le volume ou la balance d?un son, nous devons utiliser la classe SoundTransform. Le moyen le plus simple pour modifier le son est d?extraire l?objet de transformation audio par la propriété soundTransform de l?objet SoundChannel : // instanciation d'un objet Sound, la méthode load est automatiquement appelée var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // lecture du son var canalSon:SoundChannel = son.play(); // récupération de l'objet SoundTransform associé au son en cours de lecture var transformationSon:SoundTransform = canalSon.soundTransform; Différentes propriétés sont définies par la classe SoundTransform dont voici le détail : ? leftToLeft : indique la quantité d'entrée gauche à émettre dans le haut-parleur gauche. ? leftToRight : indique la quantité d'entrée gauche à émettre dans le haut-parleur droit. ? pan : définit la balance du son, la valeur du paramètre varie de -1 à 1. ? rightToLeft : indique la quantité d'entrée droite à émettre dans le haut-parleur gauche. ? rightToRight : indique la quantité d'entrée droite à émettre dans le haut-parleur droit. Chapitre 17 ? Son et vidéo ? version 0.1.1 10 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? volume : détermine la puissance du volume. Le paramètre varie entre 0 pour un son nul, et 1 pour le volume maximal. Il est important de noter que la modification du son ne se fait plus par l?intermédiaire de méthodes telles setVolume ou setPan ou autres. Pour modifier le volume d?un son, nous devons procéder en plusieurs étapes précises : ? 1. Créer ou récupérer un objet de transformation SoundTransform. ? 2. Modifier la propriété volume de ce dernier. ? 3. Affecter à nouveau l?objet de transformation à la propriété soundTransform de l?objet SoundChannel. Dans le code suivant nous réduisons le volume du son en cours de lecture de 50% : // instanciation d'un objet Sound, la méthode load est automatiquement appelée var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // lecture du son var canalSon:SoundChannel = son.play(); // récupération de l'objet SoundTransform associé au son en cours de lecture var transformationSon:SoundTransform = canalSon.soundTransform; // réduction du volume de 50% transformationSon.volume = .5; // application de l'objet de transformation canalSon.soundTransform = transformationSon De la même manière nous pouvons modifier la balance horizontale du son à l?aide de la propriété pan : // instanciation d'un objet Sound, la méthode load est automatiquement appelée var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // lecture du son var canalSon:SoundChannel = son.play(); // récupération de l'objet SoundTransform associé au son en cours de lecture var transformationSon:SoundTransform = canalSon.soundTransform; // réduction du volume de 50% transformationSon.volume = .5; // passage de la totalité du son dans le canal droit transformationSon.pan = 1; // application de l'objet de transformation canalSon.soundTransform = transformationSon; Nous nous rendons compte que la manipulation du son ne s?avère pas simplifiée, il serait intéressant de concevoir une classe appropriée permettant de rendre transparentes toutes ces manipulations. Chapitre 17 ? Son et vidéo ? version 0.1.1 11 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Pour cela, nous allons concevoir une classe nommée Amplificateur. Celle-ci contiendra différentes méthodes telles affecteVolume, affecteBalance, recupereVolume et recupereBalance. Dans un paquetage org.bytearray.media nous créons la classe Amplificateur suivante : package org.bytearray.media { public class Amplificateur { public function Amplificateur () { } } } Le constructeur de la classe Amplificateur nécessite en paramètre le canal audio à modifier. Afin de l?accueillir nous ajoutons un paramètre pCanal : package org.bytearray.media { import flash.media.SoundChannel; public class Amplificateur { private var canalSon:SoundChannel; public function Amplificateur ( pCanal:SoundChannel ) { canalSon = pCanal; } } } Puis nous ajoutons les méthodes spécifiques permettant de modifier le volume ou la balance : package org.bytearray.media Chapitre 17 ? Son et vidéo ? version 0.1.1 12 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org { import flash.media.SoundChannel; import flash.media.SoundTransform; public class Amplificateur { private var canalSon:SoundChannel; private var transformation:SoundTransform; public function Amplificateur ( pCanal:SoundChannel ) { canalSon = pCanal; transformation = pCanal.soundTransform; } public function affecteVolume ( pVolume:Number ):void { transformation.volume = pVolume; canalSon.soundTransform = transformation; } public function recupereVolume ():Number { return canalSon.soundTransform.volume; } public function affecteBalance ( pBalance:Number ):void { transformation.pan = pBalance; canalSon.soundTransform = transformation; } public function recupereBalance ():Number { return canalSon.soundTransform.pan; } } } Chapitre 17 ? Son et vidéo ? version 0.1.1 13 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org La classe Amplificateur s?occupe uniquement de la transformation du son. La création de l?objet Sound ne lui est pas associée, cela rendrait notre classe rigide. La modification du volume ou de la balance est ainsi simplifiée, dans le code suivant nous réduisons le volume de 50 % : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // instanciation d'un objet Sound, la méthode load est automatiquement appelée var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // lecture du son var canalSon:SoundChannel = son.play(); // création du mixeur de son var monAmpli:Amplificateur = new Amplificateur( canalSon ); // réduction du volume de 50% monAmpli.affecteVolume ( .5 ); De la même manière, nous pouvons modifier la balance horizontale du son à l?aide de la méthode affecteBalance : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // instanciation d'un objet Sound, la méthode load est automatiquement appelée var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // lecture du son var canalSon:SoundChannel = son.play(); // création du mixeur de son var monAmpli:Amplificateur = new Amplificateur( canalSon ); // modification de la balance horizontale du son dans le haut parleur droit monAmpli.affecteBalance ( 1 ); Nous allons enrichir la classe Amplificateur en ajoutant une nouvelle méthode nommée appliqueEffet. Celle-ci permettra l?ajout d?effets appliqués aux sons. Nous allons revoir quelques notions essentielles propre à la programmation orientée objet dans cet exemple. L?idée est de pouvoir passer à la méthode appliqueEffet un effet spécifique. Le code suivant illustre le concept : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import de la classe d'effet Fondu import org.bytearray.media.effets.Fondu; var son:Sound = new Sound ( new URLRequest ("son.mp3") ); var canalSon:SoundChannel = son.play(); Chapitre 17 ? Son et vidéo ? version 0.1.1 14 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org var monAmpli:Amplificateur = new Amplificateur( canalSon ); // création d'un effet de fondu var monEffet:Fondu = new Fondu (); // application de l'effet monAmpli.appliqueEffet ( monEffet ); Nous pourrions alors imaginer de signer la méthode appliqueEffet en spécifiant un paramètre de type Fondu : public function appliqueEffet ( pEffet:Fondu ):void { } Malheureusement, cette orientation verrait rapidement ses limites, car nous ne pourrions passer en paramètre que des effets de type Fondu. Afin de pouvoir passer n?importe quel type d?effets, nous devons trouver un type commun à tous les effets et typer notre paramètre du même type. Afin de bénéficier d?un type commun, nous pensons immédiatement à la notion d?héritage traitée lors du chapitre 8 intitulé Programmation orientée objet. Nous allons donc créer une classe EffetSonore au sein du paquetage org.bytearray.media.effets dont toutes les classes d?effets devront hériter. Celle-ci définit une méthode executeEffet que toutes les classes enfants doivent surcharger afin d?implémenter leur propre effet : package org.bytearray.media.effets { import flash.media.SoundChannel; public class EffetSonore { public function EffetSonore () { } public function executeEffet ( pCanal:SoundChannel ):void { } Chapitre 17 ? Son et vidéo ? version 0.1.1 15 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org } } Puis nous étendons la classe EffetSonore à travers la classe Fondu tout en surchargeant la méthode executeEffet : package org.bytearray.media.effets { import flash.media.SoundChannel; public class Fondu extends EffetSonore { public function Fondu () { } override public function executeEffet ( pCanal:SoundChannel ):void { trace("application de l'effet sonore"); } } } Grâce à cette approche, les classes d?effets devront toujours hériter de la classe EffetSonore et posséderont ainsi ce type commun. Nous pouvons désormais ajouter une méthode appliqueEffet à la classe Amplificateur en utilisant le type commun EffetSonore en paramètre : package org.bytearray.media { import flash.media.SoundChannel; import flash.media.SoundTransform; import org.bytearray.media.effets.EffetSonore; public class Amplificateur { private var canalSon:SoundChannel; private var transformation:SoundTransform; public function Amplificateur ( pCanal:SoundChannel ) { Chapitre 17 ? Son et vidéo ? version 0.1.1 16 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org canalSon = pCanal; transformation = pCanal.soundTransform; } public function affecteVolume ( pVolume:Number ):void { transformation.volume = pVolume; canalSon.soundTransform = transformation; } public function recupereVolume ():Number { return canalSon.soundTransform.volume; } public function affecteBalance ( pBalance:Number ):void { transformation.pan = pBalance; canalSon.soundTransform = transformation; } public function recupereBalance ():Number { return canalSon.soundTransform.pan; } public function appliqueEffet ( pEffet:EffetSonore ):void { pEffet.executeEffet ( canalSon ); } } } En testant le code suivant : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import de la classe d'effet Fondu import org.bytearray.media.effets.Fondu; var son:Sound = new Sound ( new URLRequest ("son.mp3") ); Chapitre 17 ? Son et vidéo ? version 0.1.1 17 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org var canalSon:SoundChannel = son.play(); var monAmpli:Amplificateur = new Amplificateur( canalSon ); // création d'un effet de fondu var monEffet:Fondu = new Fondu (); // application de l'effet monAmpli.appliqueEffet ( monEffet ); Le message suivant est affiché dans la fenêtre de sortie : application de l'effet sonore Nous retrouvons dans le code précédent les avantages liés à la liaison dynamique du polymorphisme. Lors de la compilation, le compilateur ne sait pas quel sera le type exact de l?objet reférencé par le paramètre pEffet. La définition de la méthode executeEffet qui sera déclenchée est évaluée à l?exécution. La classe Amplificateur possède maintenant une nouvelle méthode appliqueEffet. Afin d?achever la classe Fondu, il ne nous reste plus qu?à implémenter l?effet au sein de celle-ci : package org.bytearray.media.effets { import flash.media.SoundChannel; import flash.media.SoundTransform; import fl.transitions.Tween; import fl.transitions.easing.Regular; import fl.transitions.TweenEvent; public class Fondu extends EffetSonore { private var duree:Number; private var volume:Number; private var canalSon:SoundChannel; private var transformation:SoundTransform; private var objetTween:Tween; public function Fondu ( pDuree:Number, pVolume:Number ) { duree = pDuree; volume = pVolume; } Chapitre 17 ? Son et vidéo ? version 0.1.1 18 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org override public function executeEffet ( pCanal:SoundChannel ):void { canalSon = pCanal; transformation = canalSon.soundTransform; objetTween = new Tween ( transformation, "volume", Regular.easeInOut, transformation.volume, volume, duree, true ); objetTween.addEventListener ( TweenEvent.MOTION_CHANGE , appliqueEffet ); objetTween.addEventListener ( TweenEvent.MOTION_FINISH , effetTermine ); } private function appliqueEffet ( pEvt:TweenEvent ):void { canalSon.soundTransform = transformation; } private function effetTermine ( pEvt:TweenEvent ):void { objetTween.removeEventListener( TweenEvent.MOTION_CHANGE, appliqueEffet ); } } } La classe Fondu accepte deux paramètres dont voici le détail : ? pDuree : la durée du fondu. ? pVolume : le niveau de volume vers lequel le fondu se dirige. Dans le code suivant, nous appliquons un effet de fondu de 3 secondes vers un volume à 0 : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import de la classe d'effet Fondu import org.bytearray.media.effets.Fondu; var son:Sound = new Sound ( new URLRequest ("son.mp3") ); var canalSon:SoundChannel = son.play(); var monAmpli:Amplificateur = new Amplificateur( canalSon ); // création d'un effet de fondu vers un volume à 0 en 3 secondes var monEffet:Fondu = new Fondu ( 3, 0 ); Chapitre 17 ? Son et vidéo ? version 0.1.1 19 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org // aplication de l'effet monAmpli.appliqueEffet ( monEffet ); Nous pouvons réduire le volume à 0 puis augmenter progressivement le son jusqu?à un volume de 1 en 5 secondes. Un objet SoundTransform peut être passé en troisième paramètre de la méthode play : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import de la classe d'effet Fondu import org.bytearray.media.effets.Fondu; var son:Sound = new Sound ( new URLRequest ("son.mp3") ); // le son est lu avec un volume à 0 var canalSon:SoundChannel = son.play ( 0, 0, new SoundTransform ( 0 ) ); var monAmpli:Amplificateur = new Amplificateur( canalSon ); // création d'un effet de fondu vers un volume à 1 en 5 secondes var monEffet:Fondu = new Fondu ( 5, 1 ); // aplication de l'effet monAmpli.appliqueEffet ( monEffet ); Nous pouvons ainsi créer d?autres types d?effets. Afin d?étendre le concept d?effets nous allons créer une classe AutoBalance. Celle-ci provoquera une balance horizontale entre les deux hauts parleurs. Voici le code de la classe AutoBalance : package org.bytearray.media.effets { import flash.events.TimerEvent; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.utils.Timer; import fl.transitions.easing.Regular; public class AutoBalance extends EffetSonore { private var duree:Number; private var vitesse:Number; private var balance:Number; private var i:Number; private var canalSon:SoundChannel; private var transformation:SoundTransform; private var minuteur:Timer; private var minuteurArret:Timer; public function AutoBalance ( pDuree:Number, pVitesse:Number ) { Chapitre 17 ? Son et vidéo ? version 0.1.1 20 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org balance = i = 0; duree = pDuree; vitesse = pVitesse; minuteur = new Timer ( 100, 0 ); minuteurArret = new Timer ( pDuree * 1000, 1 ); minuteur.addEventListener ( TimerEvent.TIMER, appliqueEffet ); minuteurArret.addEventListener ( TimerEvent.TIMER_COMPLETE, effetTermine ); } override public function executeEffet ( pCanal:SoundChannel ):void { canalSon = pCanal; transformation = canalSon.soundTransform; minuteur.start(); minuteurArret.start(); } private function appliqueEffet ( pEvt:TimerEvent ):void { balance = Math.sin( i += vitesse ); transformation.pan = balance; canalSon.soundTransform = transformation; } private function effetTermine ( pEvt:TimerEvent ):void { minuteur.stop(); transformation.pan = 0; canalSon.soundTransform = transformation; } } } La classe AutoBalance accepte deux paramètres dont voici le détail : Chapitre 17 ? Son et vidéo ? version 0.1.1 21 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? pDuree : la durée de la balance. ? pVitesse : la vitesse de la balance horizontale. La méthode écouteur appliqueEffet fait osciller la propriété balance entre -1 et 1 grâce à la méthode sin de la classe Math. Dans le code suivant, nous appliquons un effet de balance pendant 15 secondes avec une vitesse réduite : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import de la classe d'effet AutoBalance import org.bytearray.media.effets.AutoBalance; var son:Sound = new Sound ( new URLRequest ("son.mp3") ); var canalSon:SoundChannel = son.play (); var monAmpli:Amplificateur = new Amplificateur( canalSon ); // création d'un effet de balance automatique pendant 15 secondes avec une vitesse réduite var monEffet:AutoBalance = new AutoBalance ( 15, .1 ); // application de l'effet monAmpli.appliqueEffet ( monEffet ); Nous avons eu recours à l?héritage afin de bénéficier du polymorphisme et d?un type commun, malheureusement notre conception souffre d?une faiblesse importante. Que se passe-t-il si nous souhaitons ajouter un nouvel effet, héritant déjà d?une classe spécifique ? Il nous serait impossible d?étendre la classe EffetSonore, l?héritage multiple étant impossible en ActionScript 3. Souvenez-vous, nous avons vu lors du chapitre 8 intitulé Programmation orientée objet que l?héritage n?était pas la seule solution afin d?obtenir un ensemble d?objets ayant un type commun. Il est possible de faire partager à plusieurs classes un même type grâce aux interfaces. Au lieu de définir une classe EffetSonore dont toutes les classes d?effets doivent hériter, nous allons simplement créer une interface IEffetSonore que toute classe d?effet se devra d?implémenter. De par l?implémentation, toutes les classes d?effets seront de leurs types respectifs ainsi que du type IeffetSonore. Pour cela, nous définissons l?interface IEffetSonore suivante au sein du paquetage org.bytearray.media.effets : Chapitre 17 ? Son et vidéo ? version 0.1.1 22 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org package org.bytearray.media.effets { import flash.media.SoundChannel; public interface IEffetSonore { function executeEffet ( pCanal:SoundChannel ):void; } } Cette interface définit une seule méthode executeEffet que chaque classe d?effet se doit d?implémenter. Souvenez-vous, cette même méthode était surchargée au sein des sous classes dans notre exemple précédent. Nous allons a présent modifier les classes Fondu et AutoBalance en implémentant l?interface IEffetSonore. Notez que la méthode executeEffet ne doit plus être marquée comme méthode surchargeante, nous supprimons donc l?attribut override : package org.bytearray.media.effets { import flash.events.TimerEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.utils.Timer; import fl.transitions.Tween; import fl.transitions.easing.Regular; import fl.transitions.TweenEvent; public class Fondu implements IEffetSonore { private var duree:Number; private var volume:Number; private var canalSon:SoundChannel; private var transformation:SoundTransform; private var objetTween:Tween; public function Fondu ( pDuree:Number, pVolume:Number ) { duree = pDuree; volume = pVolume; } Chapitre 17 ? Son et vidéo ? version 0.1.1 23 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org public function executeEffet ( pCanal:SoundChannel ):void { canalSon = pCanal; transformation = canalSon.soundTransform; objetTween = new Tween ( transformation, "volume", Regular.easeInOut, transformation.volume, destination, duree, true ); objetTween.addEventListener ( TweenEvent.MOTION_CHANGE , appliqueEffet ); objetTween.addEventListener ( TweenEvent.MOTION_FINISH , effetTermine ); } private function appliqueEffet ( pEvt:TweenEvent ):void { canalSon.soundTransform = transformation; } private function effetTermine ( pEvt:TweenEvent ):void { objetTween.removeEventListener( TweenEvent.MOTION_CHANGE, appliqueEffet ); } } } La classe AutoBalance implémente aussi la classe IEffetSonore : package org.bytearray.media.effets { import flash.events.TimerEvent; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.utils.Timer; import fl.transitions.easing.Regular; public class AutoBalance implements IEffetSonore { private var duree:Number; private var vitesse:Number; private var balance:Number; private var i:Number; private var canalSon:SoundChannel; Chapitre 17 ? Son et vidéo ? version 0.1.1 24 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org private var transformation:SoundTransform; private var minuteur:Timer; private var minuteurArret:Timer; public function AutoBalance ( pDuree:Number, pVitesse:Number ) { balance = i = 0; duree = pDuree; vitesse = pVitesse; minuteur = new Timer ( 100, 0 ); minuteurArret = new Timer ( pDuree * 1000, 1 ); minuteur.addEventListener ( TimerEvent.TIMER, appliqueEffet ); minuteurArret.addEventListener ( TimerEvent.TIMER_COMPLETE, effetTermine ); } public function executeEffet ( pCanal:SoundChannel ):void { canalSon = pCanal; transformation = canalSon.soundTransform; minuteur.start(); minuteurArret.start(); } private function appliqueEffet ( pEvt:TimerEvent ):void { balance = Math.sin( i += vitesse ); transformation.pan = balance; canalSon.soundTransform = transformation; } private function effetTermine ( pEvt:TimerEvent ):void { minuteur.stop(); transformation.pan = 0; canalSon.soundTransform = transformation; } Chapitre 17 ? Son et vidéo ? version 0.1.1 25 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org } } En implémentant l?interface IEffetSonore les classes d?effets sont obligées de définir une méthode executeEffet, le cas échéant la compilation est impossible le message suivant est affiché : 1044: La méthode d'interface executeEffet de l'espace de nom org.bytearray.media.effets:IEffetSonore n'est pas implémentée par la classe org.bytearray.media.effets:Fondu. La méthode appliqueEffet de la classe Amplificateur accepte désormais un paramètre de type IEffetSonore : package org.bytearray.media { import flash.errors.IllegalOperationError; import flash.media.SoundChannel; import flash.media.SoundTransform; import org.bytearray.media.effets.IEffetSonore; public class Amplificateur { private var canalSon:SoundChannel; private var transformation:SoundTransform; public function Amplificateur ( pCanal:SoundChannel ) { canalSon = pCanal; transformation = pCanal.soundTransform; } public function affecteVolume ( pVolume:Number ):void { transformation.volume = pVolume; canalSon.soundTransform = transformation; } public function recupereVolume ():Number { return canalSon.soundTransform.volume; } public function affecteBalance ( pBalance:Number ):void Chapitre 17 ? Son et vidéo ? version 0.1.1 26 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org { transformation.pan = pBalance; canalSon.soundTransform = transformation; } public function recupereBalance ():Number { return canalSon.soundTransform.pan; } public function appliqueEffet ( pEffet:IEffetSonore ):void { pEffet.executeEffet ( canalSon ); } } } Grâce à la notion d?interfaces, les classes Fondu et AutoBalance sont de type IEffetSonore. Si une sous-classe souhaite devenir une classe d?effet, celle-ci n?a qu?à implémenter l?interface IEffetSonore et définir la méthode executeEffet. Pour terminer, nous allons ajouter la diffusion d?un événement depuis la classe Fondu afin de faciliter son utilisation. Celle-ci diffusera les deux événements suivants : ? EvenementFondu.DEMARRE : l?effet est démarré. ? EvenementFondu.TRANSITION : l?effet est en cours. ? EvenementFondu.TERMINE: l?effet est terminé. Afin de pouvoir diffuser ces derniers, la classe Fondu étend la classe EventDispatcher : public class Fondu extends EventDispatcher implements IEffetSonore Veillez à importer la classe EventDispatcher : import flash.events.EventDispatcher; Puis nous définissons la classe EvenementFondu au sein du paquetage org.bytearray.media.effets.evenements : package org.bytearray.media.effets.evenements Chapitre 17 ? Son et vidéo ? version 0.1.1 27 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org { import flash.events.Event; public class EvenementFondu extends Event { public static const DEMARRE:String = "demarre"; public static const TRANSITION:String = "transition"; public static const TERMINE:String = "termine"; public function EvenementFondu ( pType:String ) { super( pType, false, false ); } public override function clone ():Event { return new EvenementFondu ( type ); } public override function toString ():String { return '[EvenementFondu type="'+ type +'" bubbles=' + bubbles + ' eventPhase='+ eventPhase + ' cancelable=' + cancelable +']'; } } } Puis nous diffusons les événements appropriés pour chaque phase liée à l?effet : package org.bytearray.media.effets { import flash.events.Event; import flash.events.EventDispatcher; import flash.events.TimerEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.utils.Timer; import fl.transitions.Tween; import fl.transitions.easing.Regular; import fl.transitions.TweenEvent; import org.bytearray.media.effets.evenements.EvenementFondu; public class Fondu extends EventDispatcher implements IEffetSonore Chapitre 17 ? Son et vidéo ? version 0.1.1 28 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org { private var duree:Number; private var destination:Number; private var canalSon:SoundChannel; private var transformation:SoundTransform; private var objetTween:Tween; public function Fondu ( pDuree:Number, pDestination:Number ) { duree = pDuree; destination = pDestination; } public function executeEffet ( pCanal:SoundChannel ):void { canalSon = pCanal; transformation = canalSon.soundTransform; objetTween = new Tween ( transformation, "volume", Regular.easeInOut, transformation.volume, destination, duree, true ); dispatchEvent ( new EvenementFondu (EvenementFondu.DEMARRE) ); objetTween.addEventListener ( TweenEvent.MOTION_CHANGE , appliqueEffet ); objetTween.addEventListener ( TweenEvent.MOTION_FINISH , effetTermine ); } private function appliqueEffet ( pEvt:TweenEvent ):void { canalSon.soundTransform = transformation; dispatchEvent ( new EvenementFondu (EvenementFondu.TRANSITION) ); } private function effetTermine ( pEvt:TweenEvent ):void { objetTween.removeEventListener( TweenEvent.MOTION_CHANGE, appliqueEffet ); dispatchEvent ( new EvenementFondu (EvenementFondu.TERMINE) ); } } Chapitre 17 ? Son et vidéo ? version 0.1.1 29 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org } Chaque événement peut ensuite être écouté afin de pouvoir facilement synchroniser l?application : // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import des classes d?effets import org.bytearray.media.effets.AutoBalance; import org.bytearray.media.effets.Fondu; // import de la classe EvenementFondu import org.bytearray.media.effets.evenements.EvenementFondu; var son:Sound = new Sound ( new URLRequest ("son.mp3") ); var canalSon:SoundChannel = son.play (); var monMixeur:Amplificateur = new Amplificateur( canalSon ); // création d'un effet de fondu vers un volume à 0 en 10 secondes var monEffet:Fondu = new Fondu ( 10, 0 ); // écoute des événements EvenementFondu.DEMARRE et EvenementFondu.TERMINE monEffet.addEventListener ( EvenementFondu.DEMARRE, effetDemarre ); monEffet.addEventListener ( EvenementFondu.TRANSITION, effetEnCours ); monEffet.addEventListener ( EvenementFondu.TERMINE, effetTermine ); // aplication de l'effet monMixeur.appliqueEffet ( monEffet ); function effetDemarre ( pEvt:EvenementFondu ):void { trace("effet demarré "); } function effetEnCours ( pEvt:EvenementFondu ):void { trace("effet en cours "); } function effetTermine ( pEvt:EvenementFondu ):void { trace("effet terminé "); } A vous d?intégrer les mêmes événements au sein de la classe AutoBalance et pourquoi pas d?ajouter de nouvelles classes d?effets ! Chapitre 17 ? Son et vidéo ? version 0.1.1 30 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? Afin de modifier le son plus facilement, nous avons créé une classe Amplificateur. Celle-ci possède une méthode appliqueEffet permettant d?affecter différents effets sonore. ? Les classes Fondu et AutoBalance peuvent être utilisées comme effets sonore. D?autres effets peuvent être ajoutés très simplement. ? Les classes d?effets doivent obligatoirement implémenter l?interface IEffetSonore afin d?être compatible. ? Après une première approche basée sur l?héritage, nous avons préféré utiliser une interface IEffetSonore afin de rendre plus souple la création de nouveaux effets. Modification globale du son Nous venons d?étudier la modification de chaque son de manière individuelle, ActionScript 3 introduit une classe SoundMixer permettant de travailler de manière globale sur les sons d?une application. Celle-ci définit une méthode stopAll permettant l?arrêt de tous les sons en cours de lecture : // stoppe tous les sons en cours de lecture SoundMixer.stopAll(); Si nous souhaitons modifier le volume global, nous pouvons utiliser la propriété statique soundTransform de la même classe. Dans le code suivant, nous réduisons le volume global à 10% : // réduit tous les sons en cours de lecture SoundMixer.soundTransform = new SoundTransform ( .1 ); Nous allons voir dans la partie suivante, que la classe SoundMixer ne se limite pas à cela. Celle-ci nous réserve une fonctionnalité fort intéressante ouvrant de nouvelles possibilités en matière d?application audio. A retenir Chapitre 17 ? Son et vidéo ? version 0.1.1 31 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? La modification d?un son est assurée par la classe SoundTransform. ? Un objet SoundChannel est renvoyé par la méthode play de l?objet Sound. ? Les objets de type SoundChannel possèdent une propriété soundTransform renvoyant un objet de type SoundTransform. ? La classe SoundMixer permet de travailler de manière globale sur les sons d?une application. Lire le spectre d?un son ActionScript 3 intègre une nouvelle fonctionnalité très intéressante au travers de la méthode computeSpectrum de la classe SoundMixer. Celle ci permet de récupérer le spectre de la totalité des sons en cours de lecture afin d?en offrir une représentation graphique. Attention, le son issu de la classe Microphone ne peut être rédirigé vers la classe Sound et n?est donc pas pris en considération par la méthode computeSpectrum. Dans le cas contraire, cela nous aurait permis de travailler sur la reconnaissance vocale au sein de Flash. Nous pouvons espérer que cette fonctionnalité soit intégrée dans une prochaine version du lecteur. En attendant, revenons à la méthode computeSpectrum dont voici la signature : public static function computeSpectrum(outputArray:ByteArray, FFTMode:Boolean = false, stretchFactor:int = 0):void Voici le détail de chacun des paramètres : ? outputArray : le tableau binaire dans lequel placer les données liées au spectre du son. Une instance de la classe ByteArray est nécessaire. ? FFTMode : permet de spécifier si une transformation de Fourier doit être appliquée au spectre généré. Nous verrons plus loin en quoi consiste cette transformation. ? stretchFactor : échantillonnage des données générées. La valeur par défaut est 0 c'est-à-dire 44,1 KHz. Lorsque la méthode computeSpectrum est exécutée, celle-ci place au sein du tableau binaire le spectre de la totalité des sons en cours de lecture. Chapitre 17 ? Son et vidéo ? version 0.1.1 32 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Par défaut, ce spectre est représenté par 512 valeurs oscillant entre -1 et 1, dont les 256 premières valeurs concernent le haut parleur gauche, et les 256 suivantes le haut parleur droit. La figure 17-2 illustre l?oscillation des valeurs retournées par la méthode computeSpectrum : Figure 17-2. Oscillation des valeurs retournées par la méthode computeSpectrum. Afin de garantir un rafraîchissement des données au sein du tableau binaire, nous pouvons placer l?appel de la méthode computeSpectrum au sein d?un événement Event.ENTER_FRAME : // création d'un objet son et chargement d'un mp3 var monSon:Sound = new Sound ( new URLRequest ("son.mp3") ); // démarrage du son var canalSon:SoundChannel = monSon.play(); // création d'un tableau binaire vide pour accueillir le flux audio var fluxSpectre:ByteArray = new ByteArray(); // calcul du spectre addEventListener ( Event.ENTER_FRAME, calculSpectre ); function calculSpectre ( pEvt:Event ):void { // calcul du spectre en continu SoundMixer.computeSpectrum( fluxSpectre ); // affiche : 2048 trace( fluxSpectre.length ); } Chapitre 17 ? Son et vidéo ? version 0.1.1 33 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Notez que l?utilisation d?un objet Timer serait aussi envisageable si nous ne souhaitons pas être lié à la cadence de l?animation. Nous venons de voir que la méthode computeSpectrum renvoie 512 valeurs. Pourtant, lorsque nous accédons à la propriété length du tableau fluxSpectre, celle-ci nous renvoie 2048. Comment expliquons-nous cela ? Dans le cas de l?utilisation de la méthode computeSpectrum, les valeurs placées au sein du tableau fluxSpectre sont stockées sous la forme de nombres à virgule flottante 32 bits (IEEE 754) codés sur 4 octets. Chaque index d?un tableau binaire représentant un octet, le stockage d?un flottant requiert 4 octets, donc 4 index. Si nous multiplions les 512 valeurs par 4 nous obtenons bien 2048. Nous allons concevoir une classe Equaliseur afin de représenter graphiquement le spectre. Au sein du paquetage org.bytearray.media.spectres nous définissons la classe Equaliseur suivante : package org.bytearray.media.spectres { import flash.display.Bitmap; public class Equaliseur extends Bitmap { public function Equaliseur () { } } } Afin d?assurer des performances optimales nous évitons la manipulation de données vectorielles et privilégions l?utilisation de données bitmap. De ce fait, la classe Equaliseur étend la classe Bitmap. Souvenez-vous, nous avons vu au cours du chapitre 12 intitulé Programmation Bitmap qu?il était préférable d?utiliser des données bitmap lorsque cela était possible afin d?accélérer la vitesse de rendu. Chapitre 17 ? Son et vidéo ? version 0.1.1 34 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Nous ajoutons à présent notre mécanisme d?activation et de désactivation de l?objet graphique : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.events.Event; public class Equaliseur extends Bitmap { public function Equaliseur () { addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { trace("activation"); } private function desactivation ( pEvt:Event ):void { trace("desactivation"); } } } Nous en profitons pour ajouter trois paramètres au constructeur permettant de spécifier les dimensions du spectre ainsi que sa couleur : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; public class Equaliseur extends Bitmap { private var largeur:int; private var hauteur:int; private var couleur:Number; Chapitre 17 ? Son et vidéo ? version 0.1.1 35 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org public function Equaliseur ( pLargeur:Number, pHauteur:Number, pCouleurSpectre:Number ) { largeur = pLargeur; hauteur = pHauteur; couleur = pCouleurSpectre; addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { bitmapData = new BitmapData ( largeur, hauteur, false, 0 ); } private function desactivation ( pEvt:Event ):void { bitmapData.dispose(); } } } Notez que le paramètre pCouleurSpectre n?est pas lié à l?instance de BitmapData créé. Nous utiliserons la couleur passée en paramètre pour teinter les pixels dessinés plus tard au sein du bitmap. Afin de dessiner le spectre nous devons d?abord définir la surface à peindre. Au sein de la méthode activation, nous affectons à la propriété bitmapData héritée une instance de la classe flash.display.BitmapData. Lorsqu?il est supprimé de la liste d?affichage, l?équaliseur est automatiquement désactivé grâce à la méthode dispose. Nous pouvons tester la classe en cours, à l?aide du code suivant : // import de la classe Equaliseur import org.bytearray.media.spectres.Equaliseur; // création d'un équaliseur de 512 pixels de largeur et 256 pixels de hauteur var monEqualiseur:Equaliseur = new Equaliseur( 512, 256, 0 ); addChild ( monEqualiseur ); // centrage de l'équaliseur // >> 1 permet de diviser par 2 de manière plus optimisée ;) Chapitre 17 ? Son et vidéo ? version 0.1.1 36 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org monEqualiseur.x = (stage.stageWidth - monEqualiseur.width) >> 1; monEqualiseur.y = (stage.stageHeight - monEqualiseur.height) >> 1; La figure 17-3 illustre le résultat : Figure 17-3. Instance de la classe Equaliseur. Nous intégrons au sein de la méthode activation l?écoute de l?événement Event.ENTER_FRAME afin de calculer le spectre : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.utils.ByteArray; import flash.media.SoundMixer; public class Equaliseur extends Bitmap { private var largeur:int; private var hauteur:int; private var couleur:Number; private var fluxSpectre:ByteArray; public function Equaliseur ( pLargeur:Number, pHauteur:Number, pCouleurSpectre:Number ) { Chapitre 17 ? Son et vidéo ? version 0.1.1 37 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org largeur = pLargeur; hauteur = pHauteur; couleur = pCouleurSpectre; fluxSpectre = new ByteArray(); addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { bitmapData = new BitmapData ( largeur, hauteur, false, 0 ); addEventListener ( Event.ENTER_FRAME, calculSpectre ); } private function desactivation ( pEvt:Event ):void { bitmapData.dispose(); } private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); // affiche : 2048 trace( fluxSpectre.length ); } } } Nous allons nous attarder sur la méthode calculSpectre et lire les données du spectre stockées au sein du tableau binaire fluxSpectre. Nous ajoutons la définition de deux propriétés i et oscillation : private var i:int; private var oscillation:Number; Puis nous modifions la méthode calculSpectre : private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = 512; Chapitre 17 ? Son et vidéo ? version 0.1.1 38 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org while ( i-- ) { oscillation = fluxSpectre.readFloat(); // affiche : valeur comprise entre -1 et 1 trace (oscillation); } } La boucle while intégrée à la méthode calculSpectre nous permet de lire les données contenues au sein du tableau fluxSpectre. Contrairement aux tableaux traditionnels, les tableaux binaires possèdent de nombreuses méthodes facilitant leur lecture. Afin de lire un nombre à virgule flottante, nous devons utiliser la méthode readFloat définie par la classe ByteArray. Celle-ci déplace automatiquement la propriété position du tableau binaire de 4 index à chaque appel. Les 512 itérations de la boucle permettent donc le parcours des 512 valeurs. Comme nous l?avons vu précédemment, le spectre est décrit par 512 valeurs oscillant entre entre -1 et 1. Ces valeurs ne sont pas exploitables graphiquement car trop réduites, nous devons donc les multiplier par une valeur spécifique afin d?obtenir une amplitude suffisante. Dans le code suivant, nous multiplions les valeurs par la hauteur du spectre divisée par 2 : private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = 512; while ( i-- ) { oscillation = fluxSpectre.readFloat() * (hauteur >> 1); // si la hauteur du spectre est de 256 pixels // affiche : valeur comprise entre et -128 et 128 trace (oscillation); } } Chapitre 17 ? Son et vidéo ? version 0.1.1 39 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org La propriété oscillation évolue désormais entre -128 et 128, cela nous permet de dessiner les bâtonnets constituant notre futur spectre. La figure 17-4 illustre la nouvelle oscillation pour un spectre d?une hauteur de 256 pixels : Figure 17-4. Oscillation des valeurs retournées par la méthode computeSpectrum. A l?aide de la méthode fillRect de l?objet BitmapData, nous allons dessiner une succession de bâtonnets représentants le spectre. Un objet Rectangle est utilisé afin de définir la surface de chaque bâtonnet. Celui-ci aura une hauteur définie par la propriété oscillation. Nous définissons une propriété surface afin de stocker l?objet Rectangle : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.geom.Rectangle; import flash.utils.ByteArray; import flash.media.SoundMixer; public class Equaliseur extends Bitmap { private var largeur:int; private var hauteur:int; private var couleur:Number; private var fluxSpectre:ByteArray; Chapitre 17 ? Son et vidéo ? version 0.1.1 40 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org private var i:int; private var oscillation:Number; private var surface:Rectangle; public function Equaliseur ( pLargeur:Number, pHauteur:Number, pCouleurSpectre:Number ) { largeur = pLargeur; hauteur = pHauteur; couleur = pCouleurSpectre; surface = new Rectangle ( 0, 0, 3, 4 ); fluxSpectre = new ByteArray(); addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { bitmapData = new BitmapData ( largeur, hauteur, false, 0 ); addEventListener ( Event.ENTER_FRAME, calculSpectre ); } private function desactivation ( pEvt:Event ):void { bitmapData.dispose(); } private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = 512; while ( i-- ) { oscillation = fluxSpectre.readFloat() * (hauteur >> 1); surface.x = i * 4; if ( oscillation > 0 ) { surface.y = (bitmapData.height >> 1) - oscillation; surface.height = oscillation; Chapitre 17 ? Son et vidéo ? version 0.1.1 41 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org } else { surface.y = (bitmapData.height >> 1); surface.height = -oscillation; } bitmapData.fillRect ( surface, 0xFFFFFF ); } } } } Nous créons un objet Rectangle de 3 pixels de large, cette surface va nous permettre de dessiner chaque bâtonnet de l?équaliseur. Au sein de la boucle while nous déplaçons l?objet Rectangle afin de dessiner un bâtonnet tous les 4 pixels. Rappelez-vous que nous devons lire 512 valeurs et que celles-ci doivent être rendues à l?affichage. Une largeur de 512 pixels minimum est requise afin de pouvoir dessiner la totalité du spectre. Si nous souhaitons dessiner un spectre de taille réduite, nous devons sauter certaines valeurs du tableau binaire. Nous devons donc tout d?abord diviser la largeur du spectre spécifiée par 4 afin de déterminer le nombre d?itérations nécessaire pour afficher la totalité des bâtonnets : private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = bitmapData.width / 4; while ( i-- ) { oscillation = fluxSpectre.readFloat() * (hauteur >> 1); surface.x = i * 4; if ( oscillation > 0 ) { surface.y = (bitmapData.height >> 1) - oscillation; Chapitre 17 ? Son et vidéo ? version 0.1.1 42 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org surface.height = oscillation; } else { surface.y = (bitmapData.height >> 1); surface.height = -oscillation; } bitmapData.fillRect ( surface, 0xFFFFFF ); } } Grâce au code précédent, nous positionnons les bâtonnets sur la largeur du spectre spécifiée, mais nous ne parcourons plus les données totales du tableau binaire spectreFlux. Souvenez-vous que 512 appels à la méthode readFloat sont nécessaires pour parcourir le tableau complet, nous devons donc nous arranger pour sauter certaines valeurs tout en s?assurant que nous sommes bien allés jusqu?à la fin du tableau fluxSpectre. Pour cela, nous définissons une propriété decalage : private var decalage:int; Puis nous modifions la méthode calculSpectre en sautant certaines valeurs à l?aide de la propriété position de l?objet ByteArray : private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = bitmapData.width / 4; decalage = 2048 / i; while ( i-- ) { fluxSpectre.position = i * decalage; oscillation = fluxSpectre.readFloat() * (hauteur >> 1); surface.x = i * 4; if (oscillation > 0 ) { surface.y = (bitmapData.height >> 1) ? oscillation; surface.height = oscillation; Chapitre 17 ? Son et vidéo ? version 0.1.1 43 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org } else { surface.y = (bitmapData.height >> 1); surface.height = -oscillation; } bitmapData.fillRect ( surface, 0xFFFFFF ); } } Afin de comprendre le code précédent, considérons le scénario suivant : Une largeur de 256 pixels est spécifiée dans le constructeur de la classe Equaliseur. En divisant 256 par 4 nous obtenons 64 itérations afin de positionner les bâtonnets représentant le spectre. Nous divisons 2048 par 64 et obtenons un décalage de 32 octets. Ainsi, pour chaque itération, nous sautons au sein du tableau 32 octets soit 32 index. En fin de boucle nous avons parcouru la totalité du tableau binaire car 64 * 32 = 2048. En testant la classe Equaliseur à l?aide du code suivant : // import de la classe Equaliseur import org.bytearray.media.spectres.Equaliseur; // création d'un objet son et chargement d'un mp3 var monSon:Sound = new Sound ( new URLRequest ("son.mp3") ); // démarrage du son var canalSon:SoundChannel = monSon.play(); // création d'un équaliseur de 512 pixels de largeur et 300 pixels de hauteur var monEqualiseur:Equaliseur = new Equaliseur( 512, 300, 0 ); addChild ( monEqualiseur ); monEqualiseur.x = (stage.stageWidth - monEqualiseur.width) >> 1; monEqualiseur.y = (stage.stageHeight - monEqualiseur.height) >> 1; Nous obtenons le résultat illustré en figure figure 17-5 : Chapitre 17 ? Son et vidéo ? version 0.1.1 44 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-5. Instance de la classe Equaliseur. En observant notre équaliseur évoluer, nous remarquons que le spectre dessiné demeure à l?affichage. Afin de corriger cela, nous ajoutons un nouvel appel à la méthode fillRect au sein de la méthode calculSpectre : private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); bitmapData.fillRect ( bitmapData.rect, 0 ); i = bitmapData.width / 4; decalage = 2048 / i; while ( i-- ) { fluxSpectre.position = i * decalage; oscillation = fluxSpectre.readFloat() * (hauteur >> 1); surface.x = i * 4; if ( oscillation > 0 ) { Chapitre 17 ? Son et vidéo ? version 0.1.1 45 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org surface.y = (bitmapData.height >> 1) - oscillation; surface.height = oscillation; } else { surface.y = (bitmapData.height >> 1); surface.height = -oscillation; } bitmapData.fillRect ( surface, 0xFFFFFF ); } } Le premier appel à la méthode fillRect permet de supprimer les pixels précédents. En testant à nouveau notre équaliseur, nous remarquons que les bâtonnets disparaissent à présent, l?équaliseur est correctement rafraîchi. Nous allons modifier le rendu du spectre en ajoutant une dissolution des pixels progressive afin de donner un effet de fondu plus esthétique. Pour dissoudre les pixels, nous utilisons un filtre de flou appliqué en continu. Pour cela, nous utilisons la méthode applyFilter de la classe BitmapData : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.filters.BlurFilter; import flash.geom.Point; import flash.geom.Rectangle; import flash.utils.ByteArray; import flash.media.SoundMixer; public class Equaliseur extends Bitmap { private var largeur:int; private var hauteur:int; private var couleur:Number; private var fluxSpectre:ByteArray; private var i:int; private var oscillation:Number; private var surface:Rectangle; private var decalage:int; private var filtreFlou:BlurFilter; private var point:Point; Chapitre 17 ? Son et vidéo ? version 0.1.1 46 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org public function Equaliseur ( pLargeur:Number, pHauteur:Number, pCouleurSpectre:Number ) { largeur = pLargeur; hauteur = pHauteur; couleur = pCouleurSpectre; surface = new Rectangle ( 0, 0, 3, 4 ); fluxSpectre = new ByteArray(); filtreFlou = new BlurFilter ( 0, 4, 4 ); point = new Point(); addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { bitmapData = new BitmapData ( largeur, hauteur, false, 0 ); addEventListener ( Event.ENTER_FRAME, calculSpectre ); } private function desactivation ( pEvt:Event ):void { bitmapData.dispose(); } private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = bitmapData.width / 4; decalage = 2048 / i; while ( i-- ) { fluxSpectre.position = i * decalage; oscillation = fluxSpectre.readFloat() * (hauteur >> 1); surface.x = i * 4; if ( oscillation > 0 ) Chapitre 17 ? Son et vidéo ? version 0.1.1 47 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org { surface.y = (bitmapData.height >> 1) - oscillation; surface.height = oscillation; } else { surface.y = (bitmapData.height >> 1); surface.height = -oscillation; } bitmapData.fillRect ( surface, 0xFFFFFF ); } bitmapData.applyFilter ( bitmapData, bitmapData.rect, point, filtreFlou ); } } } Nous passons en paramètre à la méthode applyFilter l?objet BitmapData en cours, ainsi que sa propriété rect afin de définir la surface sur laquelle appliquer le filtre. L?objet Point passé en dernier paramètre permet d?indiquer le point de départ d?affectation du filtre. La figure 17-6 illustre le résultat : Chapitre 17 ? Son et vidéo ? version 0.1.1 48 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-6. Equaliseur avec fondu progressif. Notre équaliseur commence à prendre forme, il ne nous reste plus qu?à ajouter une couleur de spectre aléatoire. Pour cela nous importons la classe BitmapOutils du paquetage org.bytearray.outils développée au cours du chapitre 12 intitulé Programmation bitmap, puis nous créons un objet ColorTransform appliqué en continu au spectre : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.filters.BlurFilter; import flash.geom.Point; import flash.geom.Rectangle; import flash.geom.ColorTransform; import flash.utils.ByteArray; import flash.media.SoundMixer; import org.bytearray.outils.BitmapOutils; public class Equaliseur extends Bitmap { private var largeur:int; private var hauteur:int; private var couleur:Number; Chapitre 17 ? Son et vidéo ? version 0.1.1 49 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org private var fluxSpectre:ByteArray; private var i:int; private var oscillation:Number; private var surface:Rectangle; private var decalage:int; private var filtreFlou:BlurFilter; private var point:Point; private var transformationCouleur:ColorTransform; public function Equaliseur ( pLargeur:Number, pHauteur:Number, pCouleurSpectre:Number ) { largeur = pLargeur; hauteur = pHauteur; couleur = pCouleurSpectre; surface = new Rectangle ( 0, 0, 3, 4 ); fluxSpectre = new ByteArray(); filtreFlou = new BlurFilter ( 0, 4, 4 ); point = new Point(); var composants:Object = BitmapOutils.hexRgb ( pCouleurSpectre ); transformationCouleur = new ColorTransform ( composants.rouge/255, composants.vert/255, composants.bleu/255 ); addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { bitmapData = new BitmapData ( largeur, hauteur, false, 0 ); addEventListener ( Event.ENTER_FRAME, calculSpectre ); } private function desactivation ( pEvt:Event ):void { bitmapData.dispose(); } private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre ); i = bitmapData.width / 4; Chapitre 17 ? Son et vidéo ? version 0.1.1 50 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org decalage = 2048 / i; while ( i-- ) { fluxSpectre.position = i * decalage; oscillation = fluxSpectre.readFloat() * (hauteur >> 1); surface.x = i * 4; if ( oscillation > 0 ) { surface.y = (bitmapData.height >> 1) - oscillation; surface.height = oscillation; } else { surface.y = (bitmapData.height >> 1); surface.height = -oscillation; } bitmapData.fillRect ( surface, 0xFFFFFF ); } bitmapData.applyFilter ( bitmapData, bitmapData.rect, point, filtreFlou ); bitmapData.colorTransform ( bitmapData.rect, transformationCouleur ); } } } Puis nous passons la couleur spécifique lors de l?instanciation de l?objet Equaliseur : // création d'un équaliseur de 512 pixels de largeur et 300 pixels de hauteur de couleur jaune var monEqualiseur:Equaliseur = new Equaliseur( 512, 300, 0xDDDDA5 ); La figure 17-7 illustre le résultat : Chapitre 17 ? Son et vidéo ? version 0.1.1 51 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-7. Equaliseur audio en couleurs. Nous pouvons modifier les dimensions de l?équaliseur de manière significative afin de le réduire à un élément secondaire : // import de la classe Equaliseur import org.bytearray.media.spectres.Equaliseur; // création d'un objet son et chargement d'un mp3 var monSon:Sound = new Sound ( new URLRequest ("son.mp3") ); // démarrage du son var canalSon:SoundChannel = monSon.play(); // création d'un équaliseur de 64 pixels de largeur et 35 pixels de hauteur var monEqualiseur:Equaliseur = new Equaliseur( 64, 35, 0xDDDDA5 ); addChild ( monEqualiseur ); // placement de l?équaliseur monEqualiseur.x = stage.stageWidth - monEqualiseur.width - 15; monEqualiseur.y = stage.stageHeight - monEqualiseur.height - 15; La figure 17-8 illustre le résultat : Chapitre 17 ? Son et vidéo ? version 0.1.1 52 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-8. Spectre audio réduit. Nous pouvons tester à présent la classe Amplificateur développée précédemment afin de voir le spectre modifié en temps réel. Nous importons la classe Amplificateur puis nous appliquons un effet de balance en plaçant la totalité du son dans le haut parleur gauche : // import de la classe Equaliseur import org.bytearray.media.spectres.Equaliseur; // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // création d'un objet son et chargement d'un mp3 var monSon:Sound = new Sound ( new URLRequest ("son.mp3") ); // démarrage du son var canalSon:SoundChannel = monSon.play(); // création de l'objet Amplificateur var monAmplificateur:Amplificateur = new Amplificateur ( canalSon ); // balance horizontale du son dans le haut parleur gauche monAmplificateur.affecteBalance ( -1 ); // création d'un équaliseur de 512 pixels de largeur et 200 pixels de hauteur var monEqualiseur:Equaliseur = new Equaliseur( 512, 200, 0xDDDDA5 ); addChild ( monEqualiseur ); // centrage de l'équaliseur monEqualiseur.x = (stage.stageWidth - monEqualiseur.width) >> 1; Chapitre 17 ? Son et vidéo ? version 0.1.1 53 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org monEqualiseur.y = (stage.stageHeight - monEqualiseur.height) >> 1; La figure 17-9 illustre le résultat : Figure 17-9. Spectre altéré par une avec modification de la balance horizontale. Nous voyons que notre spectre est fidèle au flux renvoyé par la méthode computeSpectrum. En réalité, lorsqu?une transformation est appliquée au son, le spectre renvoyé par la méthode computeSpectrum est lui aussi modifié. Dans le code suivant, nous appliquons un effet à l?aide de la classe AutoBalance développée auparavant : // import de la classe Equaliseur import org.bytearray.media.spectres.Equaliseur; // import de la classe Amplificateur import org.bytearray.media.Amplificateur; // import de la classe AutoBalance import org.bytearray.media.effets.AutoBalance; // création d'un objet son et chargement d'un mp3 var monSon:Sound = new Sound ( new URLRequest ("son.mp3") ); // démarrage du son var canalSon:SoundChannel = monSon.play(); // création de l'objet Amplificateur var monAmplificateur:Amplificateur = new Amplificateur ( canalSon ); Chapitre 17 ? Son et vidéo ? version 0.1.1 54 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'un effet de balance horizontale pendant 3 secondes à vitesse réduite var effetBalance:AutoBalance = new AutoBalance ( 30, .1 ); // application de l'effet monAmplificateur.appliqueEffet ( effetBalance ); // création d'un équaliseur de 512 pixels de largeur et 200 pixels de hauteur var monEqualiseur:Equaliseur = new Equaliseur( 512, 200, 0xDDDDA5 ); addChild ( monEqualiseur ); // centrage de l'équaliseur monEqualiseur.x = (stage.stageWidth - monEqualiseur.width) >> 1; monEqualiseur.y = (stage.stageHeight - monEqualiseur.height) >> 1; En appliquant un effet de balance automatique progressif, le spectre est modifié en temps réel, la figure 17-10 illustre le rendu : Figure 17-10. Spectre altéré par une modification progressive de la balance horizontale. La méthode computeSpectrum nous réserve encore quelques surprises, c?est ce que nous allons découvrir à présent. A retenir Chapitre 17 ? Son et vidéo ? version 0.1.1 55 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode computeSpectrum de la classe SoundMixer permet de calculer le spectre de la totalité des sons en cours de lecture. ? La méthode computeSpectrum renvoie 512 valeurs. ? La propriété position de l?objet ByteArray permet de se déplacer manuellement au sein des octets. ? Si une transformation est appliquée à un son, le flux renvoyé par la méthode computeSpectrum reflète cette transformation. Transformée de Fourier Comme nous l?avons vu lors du détail des différents paramètres de la méthode computeSpectrum, il est possible d?appliquer une transformée de Fourier au spectre. En activant celle-ci, le spectre généré reflète alors les fréquences des sons en cours de lecture au lieu de l?onde sonore. Nous allons ajouter deux propriétés constantes au sein de la classe Equaliseur afin de pouvoir facilement choisir entre un équaliseur de fréquences ou d?onde sonore. Pour cela, nous définissons trois propriétés SPECTRE et FREQUENCE et fourier : package org.bytearray.media.spectres { import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.filters.BlurFilter; import flash.geom.Point; import flash.geom.Rectangle; import flash.geom.ColorTransform; import flash.utils.ByteArray; import flash.media.SoundMixer; import org.bytearray.outils.BitmapOutils; public class Equaliseur extends Bitmap { public static const SPECTRE:Boolean = false; public static const FREQUENCE:Boolean = true; private var largeur:int; private var hauteur:int; private var couleur:Number; private var fluxSpectre:ByteArray; private var i:int; private var amplitude:Number; private var surface:Rectangle; Chapitre 17 ? Son et vidéo ? version 0.1.1 56 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org private var decalage:int; private var filtreFlou:BlurFilter; private var point:Point; private var transformationCouleur:ColorTransform; private var fourier:Boolean; public function Equaliseur ( pLargeur:Number, pHauteur:Number, pCouleurSpectre:Number, pFourier:Boolean=false ) { largeur = pLargeur; hauteur = pHauteur; couleur = pCouleurSpectre; fourier = pFourier; surface = new Rectangle ( 0, 0, 3, 4 ); fluxSpectre = new ByteArray(); filtreFlou = new BlurFilter ( 0, 4, 4 ); point = new Point(); var composants:Object = BitmapOutils.hexRgb ( pCouleurSpectre ); transformationCouleur = new ColorTransform ( composants.rouge/255, composants.vert/255, composants.bleu/255 ); addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { bitmapData = new BitmapData ( largeur, hauteur, false, 0 ); addEventListener ( Event.ENTER_FRAME, calculSpectre ); } private function desactivation ( pEvt:Event ):void { bitmapData.dispose(); } private function calculSpectre ( pEvt:Event ):void { SoundMixer.computeSpectrum( fluxSpectre, fourier ); i = bitmapData.width / 4; decalage = 2048 / i; Chapitre 17 ? Son et vidéo ? version 0.1.1 57 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org while ( i-- ) { fluxSpectre.position = i * decalage; amplitude = fluxSpectre.readFloat() * ((hauteur - 10) >> 1); surface.x = i * 4; if ( amplitude > 0 ) { surface.y = (bitmapData.height >> 1) - amplitude; surface.height = amplitude; } else { surface.y = (bitmapData.height >> 1); surface.height = -amplitude; } bitmapData.fillRect ( surface, 0xFFFFFF ); } bitmapData.applyFilter ( bitmapData, bitmapData.rect, point, filtreFlou ); bitmapData.colorTransform ( bitmapData.rect, transformationCouleur ); } } } Il est important de noter que dans le cas de l?utilisation de la transformée de Fourier, les valeurs renvoyées par la méthode computeSpectrum oscillent entre 0 et 1. Nous obtiendrons dans ce cas des bâtonnets dans la partie supérieure du spectre uniquement. Grâce aux deux propriétés nous pouvons facilement spécifier le type d?équaliseur voulu. Dans le code suivant nous créons un spectre permettant d?afficher les fréquences des sons : // création d'un équaliseur avec transformation de fourier var monEqualiseur:Equaliseur = new Equaliseur( 512, 200, 0xDDDDA5, Equaliseur. FREQUENCE ); La figure 17-11 illustre le résultat : Chapitre 17 ? Son et vidéo ? version 0.1.1 58 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-11. Equaliseur avec transformation de fourier. A retenir ? La transformée de Fourier permet d?isoler les fréquences des sons en cours de lecture. Le format MPEG-4 Audio La version 9.0.115 du lecteur Flash 9 intègre une compatibilité MPEG-4 et permet la lecture de fichiers audio encodés avec l?algorithme Advanced Audio Coding plus communément appelé AAC. Développé à l?origine par l?institut Fraunhofer, ce format compressé est considéré comme le remplaçant du célèbre codec de compression MP3. A qualité d?écoute égale, le format AAC est environ 30% plus légér que le format MP3. Cette optimisation du poids des fichiers audio permet donc une réduction de la bande passante utilisée par les sons sur un site à large trafic. Des portails audio comme I-Tunes utilisent déjà le format AAC comme format de distribution. Des périphériques tels le I-Phone, la PlayStation Portable ainsi qu?un grand nombre de téléphones portables sont aussi compatibles avec ce format. Chapitre 17 ? Son et vidéo ? version 0.1.1 59 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Le tableau suivant recense les différentes extensions de fichiers MPEG-4 permettant de contenir du son au format AAC compatible avec le lecteur Flash 9 : Extension Description .M4A Fichier audio .M4V Fichier video i-Tunes .AAC Fichier audio AAC .3GP Fichier audio et vidéo utilisé sur les téléphones 3G .MP4 Fichier video Tableau 1. Extensions de fichiers conteneur du format AAC. Notons que le lecteur Flash ne gère pas la lecture de fichiers AAC contenant une piste MP3, ni les fichiers AAC protégés téléchargés depuis des plates-formes telles I-Tunes. De la même manière, les fichiers audio AAC protégés par la technologie de gestion des droits numériques FairPlay ne sont pas compatibles. Dans le code suivant nous chargeons un fichier son MPEG-4 AAC : // instanciation d'un objet NetConnection var chargeurSon:NetConnection = new NetConnection(); // lors d'un chargement de fichier local nous nous connectons à null chargeurSon.connect(null); // création d'un objet NetStream var fluxAudio:NetStream = new NetStream ( chargeurSon ); // lecture du son fluxAudio.play ("son.m4a"); Bien que cela puisse vous surprendre, sachez que la lecture de fichiers audio au format AAC n?est pas assurée par la classe Sound mais par les classes NetStream et NetConnection. Celles-ci sont utilisées dans le cas d?applications connectée à un serveur de type Flash Media Server ou dans le cas de lecture de vidéos au format FLV. En testant le code précédent, le fichier son est lu mais l?erreur suivante est levée et affichée dans la fenêtre de sortie : Error #2044: AsyncErrorEvent non pris en charge : text=Error #2095: flash.net.NetStream n?a pas été en mesure d?appeler l?élément de rappel onMetaData. Dans le cas de chargement de fichiers à l?aide de la classe NetStream, il convient de toujours définir la propriété client avant d?appeler la méthode play. Chapitre 17 ? Son et vidéo ? version 0.1.1 60 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org La propriété client, permet de préciser l?objet sur lequel est définie la méthode onMetaData. Aussi étrange que cela puisse paraître, l?objet NetStream ne diffuse pas d?événement lié aux métasdonnées du média chargé. Nous retrouvons ci le modèle événementiel présent en ActionScript 1 et 2. Dans le code suivant, nous utilisons le scénario principal comme client : // instanciation d'un objet NetConnection var chargeurSon:NetConnection = new NetConnection(); // lors d'un chargement de fichier local nous nous connectons à null chargeurSon.connect(null); // création d'un objet NetStream var fluxAudio:NetStream = new NetStream ( chargeurSon ); // lecture du son fluxAudio.play ("son.m4a"); // le scénario joue le rôle du client fluxAudio.client = this; /// méthode onMetaData définie sur le scénario principal function onMetaData ( pMeta ):void { /* duration : 395.90022675736964 trackinfo : [object Object] audiochannels : 2 aacaot : 2 audiosamplerate : 44100 tags : moovposition : 40 audiocodecid : mp4a */ for ( var p in pMeta ) trace( p + " : " + pMeta[p] ); } Le paramètre pMeta reçoit un objet contenant différentes propriétés liées aux métasdonnés du média chargé. Voici en détail chacune des propriétés : ? aacaot : le type de fichier audio AAC, cette propriété peut avoir la valeur 0 pour AAC Main, 1 pour AAC LC et 2 pour SBR audio types. ? audiochannels : le nombre de canaux du média chargé. Dans le cas de fichiers audio AAC multicanaux, ces derniers sont décodés sur deux canaux seulement par le lecteur Flash. ? audiocodecid : le codec audio utilisé du média chargé. La chaîne de caractères mp4a est utilisée pour le format AAC, et .mp3 pour les fichiers MP3. Chapitre 17 ? Son et vidéo ? version 0.1.1 61 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? audiosamplerate : fréquence d?échantillonnage du fichier audio. ? duration : la durée en secondes du média chargé. ? moovposition : La position de l?atome moov au sein du média chargé. ? tags : un objet comprenant différentes informations liées au média chargé. L?équivalent des données ID3 du format MP3. ? trackinfo : un objet contenant les eventuelles illustrations liées au média (pochettes, photos) sous la forme de ByteArray. Au cas où le fichier MPEG-4 n?est pas compatible nous pouvons écouter l?événement NetStatusEvent.NET_STATUS : // instanciation d'un objet NetConnection var chargeurSon:NetConnection = new NetConnection(); // lors d'un chargement de fichier local nous nous connectons à null chargeurSon.connect(null); // création d'un objet NetStream var fluxAudio:NetStream = new NetStream ( chargeurSon ); // écoute de l'événement NetStatusEvent.NET_STATUS fluxAudio.addEventListener( NetStatusEvent.NET_STATUS, etatLecture ); function etatLecture ( pEvt:NetStatusEvent ):void { if ( pEvt.info.code == "NetStream.FileStructureInvalid" ) trace("fichier non compatible"); else if ( pEvt.info.code == "NetStream.NoSupportedTrackFound" ) trace("aucune piste trouvée"); } // lecture du son fluxAudio.play ("son.m4a"); // le scénario joue le rôle du client fluxAudio.client = this; /// méthode onMetaData définie sur le scénario principal function onMetaData ( pMeta ):void { /* duration : 395.90022675736964 trackinfo : [object Object] audiochannels : 2 aacaot : 2 audiosamplerate : 44100 tags : moovposition : 40 audiocodecid : mp4a */ for ( var p in pMeta ) trace( p + " : " + pMeta ); Chapitre 17 ? Son et vidéo ? version 0.1.1 62 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org } L?objet événementiel diffusé par l?événement NetStatusEvent.NET_STATUS possède une propriété info contenant un objet disposant d?informations sur l?état de la connexion. Cet objet possède les deux propriétés suivantes : ? code : une chaîne de caractères indiquant l?état de la connexion. Consultez la documentation pour connaître les différentes valeurs renvoyées. ? level : renvoie la chaîne de caractère status si la connexion est réussie ou error si celle-ci échoue. Malheureusement, il n?existe pas de propriétés constantes de classe afin de tester les valeurs retournées par les propriétés code et level. Nous venons de terminer notre aventure sonore, nous allons nous intéresser dans la partie suivante aux différentes nouveautés apportées par ActionScript 3 et la dernière version du lecteur Flash 9 en matière de vidéo. A retenir ? La version 9.0.115 du lecteur Flash 9 intègre un décodage des fichiers audio AAC. ? L?algorithme de compression AAC est considéré comme plus performant. C'est à beaucoup d'égards un remplaçant supérieur au format MP3. ? Afin de lire un fichier audio AAC, nous utilisons les classes NetConnection et NetStream. ? La propriété client de l?objet NetStream permet de définir l?objet interceptant l?événément onMetaData. ? L?événement NetStatusEvent.NET_STATUS diffusé par l?objet NetStream permet de savoir si une erreur de décodage est intervenue. ? La lecture de fichiers MPEG-4 fonctionne en ActionScript 1, 2 et 3. La vidéo dans Flash Le lecteur Flash s?est imposé aujourd?hui comme lecteur multimédia incontournable sur Internet. En plus d?offrir un décodage audio MPEG-4, la dernière version du lecteur Flash révolutionne la vidéo sur réseaux en intégrant une compatibilité avec le codec de compression vidéo MPEG-4 H.264. Chapitre 17 ? Son et vidéo ? version 0.1.1 63 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Depuis sa version 9.0.115, le lecteur Flash 9 intègre donc les 4 codecs suivants : ? Sorenson Spark : il s?agit du premier codec vidéo à être apparu dans le lecteur Flash. La qualité d?affichage n?est pas optimale, ce codec est voué à disparaître. ? Screen Video : Il s?agit du codec utilisé pour la capture d'écran par Connect (anciennement Breeze). ? On2 VP6 : ce codec fut introduit au sein du lecteur Flash 8. Il introduit une qualité d?image supérieure ainsi que la gestion du canal alpha. ? H.264 : ce codec fut introduit au sein du lecteur Flash 9.0.115. Il améliore à nouveau la qualité de l?image tout en garantissant l'interopérabilité et l'universalité des données vidéo. Voici la liste des différentes classes impliquées dans la lecture de flux vidéo au sein du lecteur Flash : ? flash.net.NetConnection : La classe NetConnection permet d?ouvrir la connexion. ? flash.net.NetStream : La classe NetStream permet de manipuler le flux en cours de lecture. ? flash.media.Video : La classe Video permet d?afficher le flux chargé. Passons à la pratique, dans cette nouvelle partie nous allons découvrir comment charger et lire une vidéo MPEG-4 de manière dynamique. Le format MPEG-4 Video Pour lire une vidéo MPEG-4 nous utilisons les classes NetStream et NetConnection, de la même manière qu?une vidéo au format FLV. Sachez que le lecteur Flash ne s?appuie pas sur les extensions de fichiers audio ou video afin de tester le type de la vidéo mais sur l'en- tête (binaire) du fichier en question. Dans le cas d?une ancienne application censée charger des fichiers vidéo au format FLV, il est tout à fait possible de renommer l?extension d?une vidéo MPEG-4 comme mov ou mp4 en flv, celle-ci sera lue sans problèmes. Dans le code suivant nous chargeons une vidéo MPEG-4 stockée dans un fichier QuickTime mov : // instanciation d'un objet NetConnection var chargeurVideo:NetConnection = new NetConnection(); // lors d'un chargement de fichier local nous nous connectons à null chargeurVideo.connect(null); // création d'un objet NetStream Chapitre 17 ? Son et vidéo ? version 0.1.1 64 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org var fluxVideo:NetStream = new NetStream ( chargeurVideo ); // écoute de l'événement NetStatusEvent.NET_STATUS fluxVideo.addEventListener( NetStatusEvent.NET_STATUS, etatLecture ); // lecture du fichier vidéo MPEG-4 fluxVideo.play ("video_hd.mov"); // le scénario joue le rôle du client fluxVideo.client = this; /// méthode onMetaData définie sur le scénario principal function onMetaData ( pMeta ):void { /* trackinfo : [object Object],[object Object],[object Object] audiochannels : 2 width : 640 videoframerate : 23.976 height : 268 duration : 95.15537414965986 videocodecid : avc1 audiosamplerate : 44100 seekpoints : [object Object],[object Object],[object Object] moovposition : 40 avcprofile : 77 aacaot : 2 audiocodecid : mp4a avclevel : 21 */ for ( var p in pMeta ) trace( p + " : " + pMeta[p] ); } function etatLecture ( pEvt:NetStatusEvent ):void { if ( pEvt.info.code == "NetStream.FileStructureInvalid" ) trace("fichier non compatible"); else if ( pEvt.info.code == "NetStream.NoSupportedTrackFound" ) trace("aucune piste trouvée"); } L?objet passé à la méthode onMetaData possède des propriétés quelque peu différentes de la lecture d?un fichier audio AAC. Voici le détail de chacune des propriétés : ? aacaot : le type de fichier audio AAC, cette propriété peut avoir la valeur 0 pour AAC Main, 1 pour AAC LC et 2 pour SBR audio types. ? audiochannels : le nombre de canaux du média chargé. Dans le cas de fichiers audio AAC multicanaux, ces derniers sont décodés sur deux canaux seulement par le lecteur Flash. Chapitre 17 ? Son et vidéo ? version 0.1.1 65 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? audiocodecid : le codec audio utilisé du média chargé. La chaîne de caractères mp4a est utilisée pour le format AAC, et .mp3 pour les fichiers MP3. ? audiosamplerate : fréquence d?échantillonnage du fichier audio. ? avclevel : cette propriété renvoie un nombre compris entre 10 et 51 donnant des informations au décodeur concernant les ressources nécessaires pour le décodage de la vidéo. ? avcprofile : le profil du fichier H.264, une valeur pouvant être 66, 77, 88, 100, 110, 122 ou 144. ? duration : la durée en secondes du média chargé. ? height : la hauteur en pixels de la vidéo. ? moovposition : la position de l?atome moov au sein du média chargé. ? seekpoints : points de repères permettant le chapitrage du média. ? tags : un objet comprenant différentes informations liées au média chargé. L?équivalent des données ID3 du format MP3. ? trackinfo : un objet contenant différentes informations liées au média chargé. ? videoframerate : un objet contenant différentes informations liées au média chargé. ? videocodecid : un objet contenant différentes informations liées au média chargé. ? width : la largeur en pixels de la vidéo. Nous venons de voir comment charger une vidéo dynamiquement, il nous faut maintenant l?afficher. Pour cela, nous devons lier le flux de l?objet NetStream à un objet Video. A retenir ? Les fichiers vidéo MPEG-4 sont lus à l?aide des classes NetConnection et NetStream. ? L?introduction du codec MPEG-4 n?empêche pas la lecture de vidéos au format FLV. La classe Video La classe Video réside au sein du paquetage flash.media. Contrairement aux précédentes versions d?ActionScript, celle-ci est désormais instanciable par programmation. La classe Video hérite de la classe DisplayObject, et possède donc toutes les propriétés et méthodes propres à un objet graphique. Chapitre 17 ? Son et vidéo ? version 0.1.1 66 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Grâce à la méthode attachNetStream nous pouvons lier le flux vidéo à l?objet Video : // instanciation d'un objet NetConnection var chargeurVideo:NetConnection = new NetConnection(); // lors d'un chargement de fichier local nous nous connectons à null chargeurVideo.connect(null); // création d'un objet NetStream var fluxVideo:NetStream = new NetStream ( chargeurVideo ); // écoute de l'événement NetStatusEvent.NET_STATUS fluxVideo.addEventListener( NetStatusEvent.NET_STATUS, etatLecture ); // création de l'objet Video var ecranVideo:Video = new Video(); // on attache le flux à l'écran vidéo ecranVideo.attachNetStream( fluxVideo ); // ajout à la liste d'affichage addChild ( ecranVideo ); // lecture du fichier vidéo MPEG-4 fluxVideo.play ("wall-e-tsr1_h.640.mov"); // le scénario joue le rôle du client fluxVideo.client = this; /// méthode onMetaData définie sur le scénario principal function onMetaData ( pMeta ):void { for ( var p in pMeta ) trace( p + " : " + pMeta[p] ); } function etatLecture ( pEvt:NetStatusEvent ):void { if ( pEvt.info.code == "NetStream.FileStructureInvalid" ) trace("fichier non compatible"); else if ( pEvt.info.code == "NetStream.NoSupportedTrackFound" ) trace("aucune piste trouvée"); } Le code précédent génère le résultat illustré par la figure 17-12 : Chapitre 17 ? Son et vidéo ? version 0.1.1 67 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-12. Lecture de vidéo MPEG-4. L?objet Video ne se redimensionne pas automatiquement aux dimensions du média. Celui-ci possède une largeur de 320 pixels en largeur et 240 pixels en hauteur. Nous allons utiliser les propriétés width et height de l?objet passé à la méthode onMetaData afin de redimensionner l?objet Video : // méthode onMetaData définie sur le scénario principal function onMetaData ( pMeta ):void { ecranVideo.width = pMeta.width; ecranVideo.height = pMeta.height; if ( !contains ( ecranVideo ) ) { addChild ( ecranVideo ); } ecranVideo.x = (stage.stageWidth - ecranVideo.width) >> 1; ecranVideo.y = (stage.stageHeight - ecranVideo.height) >> 1; } Nous ajoutons l?objet Video à la liste d?affichage au sein de la méthode onMetaData. Nous testons si l?objet Video n?est pas déjà présent à l?affichage, le cas échéant nous l?ajoutons, puis nous centrons la vidéo. La figure 17-13 illustre le résultat : Chapitre 17 ? Son et vidéo ? version 0.1.1 68 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-13. Vidéo MPEG-4 adaptée et centrée. Afin de conserver une image lissée, nous pouvons activer la propriété smoothing de l?objet Video dont la valeur par défaut est à false. Il est conseillé d?activer cette propriété lors de la lecture de vidéos au sein du lecteur Flash 9.0.115 et versions ultérieures afin de tirer profit de l?optimisation de l?image par mip-mapping. Attention toutefois l'utilisation conjointe du mode plein écran et de cette propriété, peut engendrer une surcharge du processeur importante sur les machines peu puissantes. Pour plus d?informations concernant le mip-mapping, consultez le chapitre 12 intitulé Programmation Bitmap. A retenir Chapitre 17 ? Son et vidéo ? version 0.1.1 69 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org ? En ActionScript 3 la classe Video est instanciable par programmation. ? L?objet Video est lié au flux chargé à l?aide de la méthode attachNetStream. Transformation du son lié à un objet NetStream Nous avons en début de chapitre comment modifier le son à l?aide de la propriété soundTransform de l?objet SoundChannel. Lorsqu?un média est lu à l?aide de la classe NetStream, aucun objet SoundChannel n?est disponible. L?objet SoundTransform lié au média en cours de lecture est accessible par la propriété soundTransform de l?objet NetStream. Dans le code suivant, nous réduisons le volume de la vidéo de 50 % : // instanciation d'un objet NetConnection var chargeurVideo:NetConnection = new NetConnection(); // lors d'un chargement de fichier local nous nous connectons à null chargeurVideo.connect(null); // création d'un objet NetStream var fluxVideo:NetStream = new NetStream ( chargeurVideo ); // récupération de l'objet SoundTransform associé au média chargé var transformation:SoundTransform = fluxVideo.soundTransform; // modification du volume transformation.volume = .5; // application de la modification fluxVideo.soundTransform = transformation; // écoute de l'événement NetStatusEvent.NET_STATUS fluxVideo.addEventListener( NetStatusEvent.NET_STATUS, etatLecture ); // création de l'objet Video var ecranVideo:Video = new Video(); // on attache le flux à l'écran vidéo ecranVideo.attachNetStream( fluxVideo ); // ajout à la liste d'affichage addChild ( ecranVideo ); // lecture du fichier vidéo MPEG-4 fluxVideo.play ("wall-e-tsr1_h.640.mov"); // le scénario joue le rôle du client fluxVideo.client = this; /// méthode onMetaData définie sur le scénario principal function onMetaData ( pMeta ):void { Chapitre 17 ? Son et vidéo ? version 0.1.1 70 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org for ( var p in pMeta ) trace( p + " : " + pMeta[p] ); } function etatLecture ( pEvt:NetStatusEvent ):void { if ( pEvt.info.code == "NetStream.FileStructureInvalid" ) trace("fichier non compatible"); else if ( pEvt.info.code == "NetStream.NoSupportedTrackFound" ) trace("aucune piste trouvée"); } Le même code s?applique dans le cas du chargement d?un son MPEG- 4 AAC. A retenir ? Afin de modifier le son d?un média associé à un objet NetStream nous utilisons sa propriété soundTransform. Mode plein-écran Afin de bénéficier pleinement du décodage video MPEG-4 du lecteur Flash 9, celui-ci intègre en plus depuis la version 9.0.28 la capacité de passer l?affichage en mode plein écran. Jusqu?à présent, cette fonctionnalité n?était reservée qu?au lecteur Flash autonome qui permettait le passage en mode plein écran par le biais du mode projecteur. Dans le cas de moniteurs multiples, l?écran ayant le plus de contenu Flash en cours d?affichage est choisi automatiquement par le lecteur. La classe Stage définit une propriété displayState permettant le passage du lecteur en plein écran, celle-ci accepte deux valeurs stockées au sein de la classe StageDisplayState : ? StageDisplayState.FULL_SCREEN : Mode plein écran. ? StageDisplayState.NORMAL : Mode normal. Attention, le passage en mode plein écran est soumis aux différentes restrictions suivantes : ? Le mode plein-écran ne peut pas être déclenché de manière autonome. Seule une action utilisateur clavier ou souris permet d?activer le mode plein écran. Dans le cas contraire une erreur est levée à l?exécution. ? La page contenant le lecteur Flash doit autoriser le mode plein-écran en activant l?attribut allowFullScreen des balises <embed> et Chapitre 17 ? Son et vidéo ? version 0.1.1 71 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org <object> à l?aide du booléen true. La valeur par défaut est false ce qui empêche l?activation du mode plein-écran. ? Les touches du clavier sont vérrouillées à l?exception de la touche ESC permettant de repasser en mode normal, la saisie de texte est donc impossible. Dans le code suivant, nous passons en mode plein-écran lorsque l?utilisateur clique sur la scène : // écoute de l'événement MouseEvent.CLICK auprès de l'objet Stage stage.addEventListener ( MouseEvent.CLICK, gestionAffichage ); function gestionAffichage ( pEvt:MouseEvent ):void { // passage en mode plein-écran stage.displayState = StageDisplayState.FULL_SCREEN; } Par défaut le code précédent n?est pas suffisant, afin d?autoriser le mode plein écran l?animation doit être lue au sein du lecteur Flash du navigateur et l?attribut allowFullScreen des balises <embed> et <object> doit être passé à true. Afin d?automatiser l?activation du mode plein écran au sein de la page conteneur, il est conseillé de sélectionner au sein de l?onglet HTML de panneau Paramètres de publication le modèle Flash seulement autorisation du plein écran. La figure 17-14 illustre le modèle prédéfini : Figure 17-14 : Onglet HTML du panneau Paramètres de publication. Une nouvelle propriété fullScreenSourceRect fut introduite au sein du lecteur Flash 9.0.115. Celle-ci permet de spécifier la surface à passer en plein écran. Ce paramètre est idéal pour déterminer quelle partie de l?application doit être redimensionnée. Pour l?utiliser nous devons créer une instance de la classe flash.geom.Rectangle afin de définir la surface voulue : // écoute de l'événement MouseEvent.CLICK auprès de l'objet Stage Chapitre 17 ? Son et vidéo ? version 0.1.1 72 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org stage.addEventListener ( MouseEvent.CLICK, gestionAffichage ); function gestionAffichage ( pEvt:MouseEvent ):void { // une surface est définie comme zone à passer en plein écran var surfacePleinEcran:Rectangle = new Rectangle ( 0, 0, 150, 150 ); // la zone est spécifiée stage.fullScreenSourceRect = surfacePleinEcran; // passage en mode plein-écran stage.displayState = StageDisplayState.FULL_SCREEN; } Ainsi, nous pouvons choisir comme zone à agrandir la surface occupée par la vidéo en cours de lecture : // écoute de l'événement MouseEvent.CLICK auprès de l'objet Stage stage.addEventListener ( MouseEvent.CLICK, goFull ); function goFull ( pEvt:MouseEvent ):void { // la surface occuppée par la vidéo est définie comme zone à agrandir var surfacePleinEcran:Rectangle = new Rectangle ( ecranVideo.x, ecranVideo.y, ecranVideo.width, ecranVideo.height ); // la zone est spécifiée stage.fullScreenSourceRect = surfacePleinEcran; // passage en mode plein-écran stage.displayState = StageDisplayState.FULL_SCREEN; } Afin de faciliter ce processus de redimensionnement, le lecteur Flash peut allouer cette tâche au processeur de la carte graphique afin de rendre le redimensionnement moins gourmand et plus fluide. Le lecteur Flash s?appuie sur Direct X sous Windows et Open GL sur Mac OS X. Au cas où la carte graphique ne serait pas compatible, une accélération dite logicielle est appliquée, le processeur est donc chargée de la tâche. Afin d?activer l?accélération matérielle, il suffit de sélectionner l?option Paramètres de la liste déroulante du lecteur Flash et de cocher la case Activer l?accélération matérielle au sein de l?onglet Affichage illustré par la figure 17-15 : Chapitre 17 ? Son et vidéo ? version 0.1.1 73 / 73 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 17-15 : Onglet Affichage du lecteur Flash. Il n?est pas possible d?activer ou désactiver l?accélération matérielle par programmation. A retenir ? La classe Stage définit une propriété displayState pouvant prendre comme valeur StageDisplayState.NORMAL et StageDisplayState.FULL_SCREEN. ? Le mode plein écran ne peut être déclenché de manière autonome. ? Le mode plein écran doit être autorisé au sein de la page conteneur grâce à l?attribut allowFullScreen. ? Les touches du clavier sont vérrouillées, à l?exception de la touche ESC. La saisie du texte est impossible, ce qui limite malheureusement l?exploitation du mode plein-écran. ActionScript 3 nous réserve encore des surprises, au cours du prochain chapitre nous allons découvrir un nouveau moyen de communiquer avec l?extérieur grâce aux connexions par socket. Chapitre 18 ? Sockets ? version 0.1 1 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org 18 Sockets UNE PASSERELLE UNIVERSELLE................................................................... 1 CREER UN SERVEUR DE SOCKET XML......................................................... 6 LA CLASSE XMLSOCKET........................................................................................ 7 CREER UN T?CHAT MULTI-UTILISATEUR................................................................ 11 LA CLASSE SOCKET.......................................................................................... 23 CREER UN SERVEUR DE SOCKET BINAIRE .............................................................. 24 ECHANGER DES DONNÉES ..................................................................................... 28 Une passerelle universelle Nous avons vu au cours des chapitres précédents comment le lecteur Flash pouvait communiquer avec l?extérieur. Nous allons aller plus loin en abordant à présent la notion de communication par socket. Une socket doit être considérée comme une passerelle de communication entre deux applications fonctionnant sur un réseau. Dans la vie courante, une socket pourrait être assimilée à une connexion téléphonique entre deux personnes. Pour entreprendre une communication par socket, deux acteurs au minimum sont nécessaires : ? Le serveur : le serveur de socket écoute les connexions entrantes, il se charge de gérer les clients connectés et communique avec eux. ? Le client : le client se connecte au serveur par la socket et communique avec le serveur. Notons que l?avantage principal d?une connexion par socket réside dans le caractère persistent de la communication entre les deux acteurs. Contrairement à une connexion HTTP traditionnelle, la socket Chapitre 18 ? Sockets ? version 0.1 2 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org maintient la connexion ouverte entre les deux acteurs et permet au serveur de transmettre des informations aux clients sans que ces derniers n?en fassent la demande. Ce comportement offre ainsi de nombreuses possibilités en termes d?applications dynamiques temps réel. Afin de bien comprendre la notion de socket, prenons l?exemple suivant : Lorsque vous naviguez sur Internet à l?aide votre navigateur favori, ce dernier demande au serveur distant de lui transmettre la page que vous souhaitez afficher. Ce langage commun entre les deux acteurs est appelé protocole d?application et fait partie de ce que l?on appelle la suite de protocoles Internet. Internet est basé sur cet ensemble de protocole généralement appelé modèle TCP/IP. Afin d?afficher une page spécifique, le navigateur envoie au serveur HTTP la requête suivante : GET /index.php HTTP/1.1 Host: www.bytearray.org Nous pouvons remarquer la présence de mots réservés tels GET ou Host faisant partie du protocole d?application HTTP. Par ces mots clés, le serveur comprend qu?il doit transmettre le contenu de la page index.php au client connecté, ici notre navigateur. Aussitôt la requête reçue, le serveur l?analyse et répond à l?aide du message suivant : HTTP/1.x 200 OK Date: Sun, 20 Jan 2008 14:30:34 GMT Server: Apache/2.0.54 (Debian GNU/Linux) PHP/4.3.10-22 mod_ssl/2.0.54 OpenSSL/0.9.7e mod_perl/1.999.21 Perl/v5.8.4 X-Powered-By: PHP/4.3.10-22 X-Pingback: http://www.bytearray.org/xmlrpc.php Content-Encoding: gzip Vary: Accept-Encoding Content-Length: 4260 Keep-Alive: timeout=15, max=97 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 En réalité, l?application serveur et le client communiquent par socket à l?aide d?un protocole commun. Un nombre illimité de protocoles d?applications peuvent ainsi être implémentés ou créés puis utilisés à l?aide d?une connexion par socket. Nous sommes donc en mesure d?utiliser au sein de Flash n?importe quel protocole d?application tel HTTP, FTP, SMTP ou autres. Chapitre 18 ? Sockets ? version 0.1 3 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Pour différencier les applications d?un ordinateur associées à chaque protocole, chaque application est associée à un port spécifique appelé couramment port logiciel. Différentes applications sont donc accessibles à partir d?une même adresse IP mais un port unique. Parmi les protocoles d?applications les plus connus, nous pouvons établir le tableau suivant : Port Application 21 FTP (transfert de fichiers) 25 SMTP (envoi de courrier électronique) 80 HTTP (transfert hypertexte) 110 POP (stockage de courrier électronique) Tableau 1. Liste de protocoles communs. Afin de connaître les ports logiciels associés à chaque protocole. Vous pouvez consulter la liste disponible aux adresses suivantes : ? http://fr.wikipedia.org/wiki/Liste_des_ports_logiciels ? http://www.iana.org/assignments/port-numbers Ainsi, nous pouvons placer les différents ports dans trois catégories : ? Les ports bien connus (0 à 1023) : Ces ports sont couramment utilisés par des processus systèmes ayant un accès privilégié. Il est donc déconseillé d?y avoir recours pour un serveur de socket personnalisé, un risque de collision serait encouru. ? Les ports enregistrés (1024 à 49151) : Ils sont dédiés aux développements d?applications courantes. La plupart de ces ports demeurent libres et peuvent être utilisés. ? Les ports dynamiques privés (49152 à 65535) : Ces ports demeurent généralement libres. Il est important de préciser que les classes de socket ActionScript 3 s?appuient sur un modèle de transmission TCP et sont considérées comme des passerelles d?échanges sûres, s?opposant au modèle de transmission UDP considéré comme peu fiable. Mais qu?entendons-nous par passerelle d?échange sûre ? En réalité, deux types de protocoles peuvent être utilisés sur Internet, le premier de type TCP puis le second de type UDP : ? TCP (Transmission Control Protocol) : Dans un contexte de connexion TCP, les données échangées sont contrôlées. Au cas où la transmission est trop rapide ou si des données sont perdues, le serveur ralentit le débit, et réexpédie les données non correctement transmises. ? UDP (User Datagram Packet) : Dans un contexte de connexion UDP, afin de gagner en vitesse de transfert aucun contrôle n?est effectué lors Chapitre 18 ? Sockets ? version 0.1 4 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org du transfert des données. En revanche certains paquets peuvent ne jamais parvenir. Ce type de socket est généralement utilisé dans le cas de jeux en temps réel où la perte de quelques paquets n?influence pas le bon fonctionnement de l?application. Les classes de socket ActionScript 3 s?appuient sur le protocole TCP, une compatibilité future avec le protocole UDP pourrait être intéressante pour certains types d?applications. Une communication par socket est généralement divisée en plusieurs phases distinctes : ? 1. Le client se connecte au serveur de socket en envoyant une commande spécifique afin d?initier la conversation ou de s?authentifier. ? 2. Le serveur décide d?accepter ou non le client. Cette étape peut être vérifiée de par la provenance du client ou les informations soumises par ce dernier lors de la procédure d?authentification. ? 3. La communication est établie. A tout moment, un des acteurs de la communication peut décider de clore la connexion. Le serveur peut décider d?interrompre la communication au cas où un client ne serait plus autorisé ou simplement lorsqu?un client souhaite se déconnecter. Voici les deux classes permettant de se connecter à un serveur de Socket en ActionScript 3 : ? flash.net.XMLSocket : la classe XMLSocket permet l?échange de données au format XML ou texte. ? flash.net.Socket : la classe Socket permet l?échange de données brutes au format binaire. Il est important de noter qu?ActionScript 3 n?intègre pas de classes permettant la création de serveurs de sockets, seules des applications clients pourront être développées. Il est donc impossible de connecter directement deux applications Flash au travers d?une connexion socket. Pour cela, l?une des applications devrait se comporter comme hôte ce qui est impossible en ActionScript 3. Si tel était le cas, la création d?un réseau peer 2 peer entre différentes applications Flash serait envisageable. Chapitre 18 ? Sockets ? version 0.1 5 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 18-1. Connexion peer 2 peer non supportée. Le choix du langage utilisé pour la création du serveur dépend de vos besoins. Des langages tels le C, C++, C#, Java ou encore PHP offrent tous la possibilité de créer des serveurs de socket. Figure 18-2. Connexion à un serveur de socket. Nous allons nous intéresser dans la partie suivante aux différents types de sockets disponibles en ActionScript 3. A retenir Chapitre 18 ? Sockets ? version 0.1 6 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org ? Une socket est une passerelle de communication entre deux applications évoluant sur un réseau. ? Les classes de Socket ActionScript 3 sont basées sur le modèle TCP/IP. ? Le protocole UDP n?est pas pris en charge. ? Les classes de socket ActionScript 3 ne permettent pas la création de serveur de socket. ? Le nombre de messages échangés à travers une connexion socket est illimité. ? ActionScript 3 intègre deux classes liées aux sockets : XMLSocket et Socket. Créer un serveur de socket XML Avant d?entamer le développement du serveur de socket, il convient de définir son rôle ainsi que son fonctionnement. Le rôle d?un serveur de socket est d?écouter les connexions entrantes puis de gérer par la suite la connexion ainsi que les échanges entre les acteurs impliqués dans la communication. Nous avons vu précédemment qu?il était impossible de connecter directement plusieurs applications Flash entre elles. A l?inverse, le serveur de socket peut agir comme relais afin de faire transiter les messages entre les différents clients. Vous pensiez peut être que cela était réservé à des technologies telles Flash Media Server ou Red 5, nous allons voir que quelques lignes de PHP vont nous permettre de connecter plusieurs applications Flash entre elles. Nous allons développer au cours de cette partie un serveur de socket XML afin de créer un t?chat multi utilisateurs. Comme nous l?avons vu précédemment, de nombreux langages permettent la création de serveur de socket de manière simplifiée. Parmi ceux là nous pouvons citer Java, C#, C++ ou PHP qui s?avère être le moyen le langage le plus simple pour déployer votre serveur de socket. Une explication approfondie de l?API de socket PHP serait hors sujet, c?est la raison pour laquelle nous ne rentrerons pas dans les détails du code du serveur. Nous allons donc commencer par un simple serveur renvoyant un message de bienvenue lorsque nous nous connectons. Le code PHP suivant est placé au sein d?un fichier nommé ServeurXMLSocket.php : #!/usr/local/bin/php -q Chapitre 18 ? Sockets ? version 0.1 7 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org <?php set_time_limit(0); $adresse = "localhost"; $port = "10000"; $connexion = socket_create (AF_INET, SOCK_STREAM, SOL_TCP); socket_bind ($connexion, $adresse, $port); socket_listen ($connexion, 1); echo "Le serveur de socket est en route !"; $client = socket_accept ($connexion); socket_close ($client); socket_close ($connexion); ?> Afin de démarrer convenablement le serveur, il convient de le lancer par ligne de commande. Pour cela, nous créons un nouveau fichier texte contenant le code suivant : C:/wamp/bin/php/php5.2.5/php.exe -q c:/wamp/www/serveur/ServeurXMLSocket.php Puis, nous sauvons le fichier texte sous le nom serveur.bat. Depuis la ligne de commande nous accédons au fichier .bat puis nous l?exécutons, le message illustré par la figure 18-3 doit s?afficher : Figure 18-3. Démarrage du serveur depuis la ligne de commande. Une fois le serveur démarré, nous devons nous y connecter, c?est ce que nous allons découvrir dans la partie suivante. La classe XMLSocket Afin de pouvoir se connecter à notre serveur de socket, nous pouvons utiliser une instance de la classe XMLSocket dont voici le constructeur : Chapitre 18 ? Sockets ? version 0.1 8 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org public function XMLSocket(host:String = null, port:int = 0) Voici le détail de chacun des paramètres : ? host : il s?agit de l?adresse du serveur de socket. Attention, si le serveur évolue au sein d?un autre domaine que le SWF, la connexion doit être autorisée par un fichier de régulation. ? port : le numéro de port TCP utilisé lors de la connexion. Par défaut il est impossible de se connecter à un port inférieur à 1024 pour des raisons de précautions. Si vous souhaitez tout de même utiliser un port inférieur vous devez utiliser un fichier de régulation. Dans le code suivant, nous établissons une connexion avec notre serveur de socket XML : // création de la connexion var connexion:XMLSocket = new XMLSocket("localhost", 10000); Au cas où l?adresse et le port utilisé ne seraient pas précisés lors de l?instanciation, nous pouvons utiliser la méthode connect : // création de l'objet XMLSocket var connexion:XMLSocket = new XMLSocket(); // connexion au serveur de socket connexion.connect("localhost", 10000); Attention, les classes XMLSocket et Socket n?ont pas la possibilité de se connecter à des ports supérieurs à 65535 et inférieurs à 1024. L?utilisation de ports inférieurs à 1024 pourrait entraîner une collision avec des serveur tels HTTP, FTP ou autres. Si vous souhaitez utiliser un port inférieur à 1024, l?utilisation d?un fichier de régulation est nécessaire. Afin d?écouter les différentes phases liées à la connexion nous utilisons les événements diffusés par l?objet XMLSocket dont voici la liste : ? Event.CLOSE : diffusé lorsque la connexion socket est interrompue. ? Event.CONNECT: diffusé lorsque la connexion au serveur a pu être réalisée. ? DataEvent.DATA : diffusé lors de l?envoi ou réception de données. ? IOErrorEvent.IO_ERROR : diffusé lorsque la connexion au serveur de socket n?a pas aboutie. ? SecurityError.SECURITY : diffusé lorsque la connexion socket tente de se connecter auprès d?un serveur no autorisé ou à port inférieur à 1024. Nous écoutons les différents événements de connexions ainsi que l?événement DataEvent.DATA : Chapitre 18 ? Sockets ? version 0.1 9 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'une instance de XMLSocket var connexion:XMLSocket = new XMLSocket("localhost", 10000); // écoute des événements connexion.addEventListener ( Event.CONNECT, connexionReussie ); connexion.addEventListener ( Event.CLOSE, fermetureConnexion ); connexion.addEventListener ( DataEvent.DATA, receptionDonnees ); function connexionReussie ( pEvt:Event ):void { trace("connexion réussie"); } function fermetureConnexion ( pEvt:Event ):void { trace("fermeture de la connexion"); } function receptionDonnees ( pEvt:DataEvent ):void { trace("réception des données"); } En testant le code précédent, nous voyons que la connexion aboutit, puis nous sommes immédiatement déconnectés. Les messages suivants sont affichés par la fenêtre de sortie : connexion réussie fermeture de la connexion Il serait intéressant de pouvoir parler au serveur de socket. Pour cela nous modifions les lignes PHP suivantes : #!/usr/local/bin/php -q <?php set_time_limit(0); $adresse = "localhost"; $port = "10000"; $connexion = socket_create (AF_INET, SOCK_STREAM, SOL_TCP); socket_bind ($connexion, $adresse, $port); socket_listen ($connexion, 1); echo "Le serveur de socket est en route !"; $client = socket_accept($connexion); Chapitre 18 ? Sockets ? version 0.1 10 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org $messageEntrant = socket_read ($client, 1024); $messageSortie = "Vous avez dit : ".$messageEntrant."\r\n"; socket_write ($client, $messageSortie); socket_close ($client); socket_close ($connexion); ?> Afin de prendre en considération les modifications, nous redémarrons le serveur de socket. Désormais, celui-ci est en attente d?un message du client. Nous allons envoyer une simple chaîne de caractères à l?aide de la méthode send de l?objet XMLSocket : // envoie d'une chaîne de caractère au serveur connexion.send ("il y'a quelqu'un ?"); En observant la fenêtre de sortie nous pouvons voir les messages suivants : connexion réussie réception des données fermeture de la connexion Aussitôt la méthode send exécutée, le lecteur Flash transmet au serveur de socket la chaîne de caractère spécifiée en terminant le message par un octet nul. Les données transmises par le serveur sont aussi terminées par un octet nul, ce marqueur permet au lecteur Flash de connaître en interne la fin du paquet transmis. Comme son nom l?indique, la classe XMLSocket est prévue pour échanger des données au format XML. Pourtant, nous échangeons ici une chaîne de caractère traditionnelle. Le format XML peut être utilisé si vous souhaitez échanger des données structurées, dans d?autres cas, des simples chaînes de caractères peuvent être utilisées. Afin de lire les données provenant du serveur, nous utilisons la l?événement DataEvent.DATA. L?objet événementiel diffusé possède une propriété data contenant les données transmises par le serveur : // création d'une instance de XMLSocket var connexion:XMLSocket = new XMLSocket("localhost", 10000); // envoie d'une chaîne de caractère au serveur connexion.send ("il y'a quelqu'un ?"); Chapitre 18 ? Sockets ? version 0.1 11 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org // écoute des événements connexion.addEventListener ( Event.CONNECT, connexionReussie ); connexion.addEventListener ( Event.CLOSE, fermetureConnexion ); connexion.addEventListener ( DataEvent.DATA, receptionDonnees ); function connexionReussie ( pEvt:Event ):void { trace("connexion réussie"); } function fermetureConnexion ( pEvt:Event ):void { trace("fermeture de la connexion"); } function receptionDonnees ( pEvt:DataEvent ):void { // affiche : Vous avez dit : il y'a quelqu'un ? trace ( pEvt.data ); } Attention, la méthode send n?est pas bloquante, nous devons garder à l?esprit le caractère asynchrone des échanges réalisés par une connexion socket. Seul l?événement DataEvent.DATA nous permet de récupérer les données transmises par le serveur et de déterminer à quel moment celles-ci sont disponibles. Nous allons aller plus loin en développant un t?chat multi-utilisateur. Le serveur de socket XML que nous allons développer servira de relais afin de faire transiter les messages des clients connectés. A retenir ? La classe XMLSocket permet la connexion auprès d?un serveur de socket. ? Les données échangées entre les deux acteurs doivent obligatoirement être au format XML ou texte. ? L?objet XMLSocket diffuse différents événements permettant d?indiquer l?état de la connexion et du transfert des données. Créer un t?chat multi-utilisateur Afin de développer notre t?chat multi utilisateur, nous allons réutiliser le moteur d?émoticones développé au cours du chapitre 16 intitulé Le texte. Chapitre 18 ? Sockets ? version 0.1 12 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Nous retrouvons à nouveau l?intérêt de la programmation orientée objet en facilitant la réutilisation d?objets prédéfinis. La figure 18-3 illustre le fonctionnement d?un chat multi utilisateur, comme nous l?avons vu précédemment, le serveur agit comme relais entre les différents clients connectés : Figure 18-3. T?chat multi-utilisateurs. Au sein d?un fichier nommé ServeurMessagerieXML.php nous définissons le code suivant : #!/usr/bin/php -q <?php set_time_limit(0); $adresse = 'localhost'; $port = 10000; $connexion = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($connexion, SOL_SOCKET,SO_REUSEADDR, 1); $ret = socket_bind($connexion, $adresse, $port); $ret = socket_listen($connexion, 5); $tabSockets = array($connexion); while (true) { $sockets = $tabSockets; socket_select($sockets, $write = NULL, $except = NULL, NULL); foreach($sockets as $socket) { Chapitre 18 ? Sockets ? version 0.1 13 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org if ($socket == $connexion) { if (($client = socket_accept($connexion)) < 0) continue; else array_push($tabSockets, $client); } else { $flux = socket_recv($socket, $buffer, 2048, 0); if ($flux == 0) { $index = array_search($socket, $tabSockets); unset($tabSockets[$index]); socket_close($socket); }else { $allclients = $tabSockets; array_shift($allclients); send_Message($allclients, $socket, $buffer); } } } } function send_Message($clients, $socket, $donnees) { foreach($clients as $client) { socket_write($client, $donnees); } } ?> Nous ne démarrons pas le serveur pour le moment, nous allons développer à présent la partie client de l?application de messagerie instantanée. Lors du chapitre 16, nous avions développé une classe MoteurEmoticone, celle-ci va nous permettre d?afficher le texte de la conversation. Grâce à ses fonctionnalités, les utilisateurs auront la possibilité d?utiliser un ensemble d?émoticones prédéfinis. Nous ajoutons les lignes suivantes à la classe MoteurEmoticone : Chapitre 18 ? Sockets ? version 0.1 14 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org package org.bytearray.emoticones { import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.geom.Rectangle; import flash.text.TextField; import flash.ui.Keyboard; import org.bytearray.evenements.EvenementSaisie; import org.bytearray.evenements.EvenementMessage; public class MoteurEmoticone extends Sprite { public var contenuHistorique:TextField; public var chaineHistorique:String; public var contenuSaisie:TextField; public var codesTouches:Array; public var tableauSmileys:Array; public var lngTouches:int; public var coordonnees:Rectangle; public var emoticone:Emoticone; public function MoteurEmoticone () { chaineHistorique = new String(); codesTouches = new Array (":)",":D",":(",";)",":p"); tableauSmileys = new Array (); lngTouches = codesTouches.length; addEventListener ( KeyboardEvent.KEY_DOWN, envoiMessage ); contenuHistorique.addEventListener ( Event.SCROLL, saisieUtilisateur ); } private function envoiMessage ( pEvt:KeyboardEvent ):void { if ( pEvt.keyCode == Keyboard.ENTER ) { dispatchEvent ( new EvenementSaisie ( EvenementSaisie.ENVOI_MESSAGE, contenuSaisie.text ) ); contenuSaisie.htmlText = ""; } } Chapitre 18 ? Sockets ? version 0.1 15 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org public function afficheMessage ( pEvt:EvenementMessage ):void { chaineHistorique += pEvt.message; contenuHistorique.text = chaineHistorique; contenuHistorique.dispatchEvent ( new Event ( Event.SCROLL ) ); } private function saisieUtilisateur ( pEvt:Event ):void { var i:int; var j:int; var nombreSmileys:int = tableauSmileys.length; for ( i = 0; i< nombreSmileys; i++ ) removeChild ( tableauSmileys[i] ); tableauSmileys = new Array(); for ( i = 0; i<lngTouches; i++ ) { j = pEvt.target.text.indexOf ( codesTouches[i] ); while ( j!= -1 ) { coordonnees = pEvt.target.getCharBoundaries ( j ); if ( coordonnees != null ) tableauSmileys.push ( ajouteSmiley ( coordonnees, i ) ); j = pEvt.target.text.indexOf ( codesTouches[i], j+1 ); } } } private function ajouteSmiley ( pRectangle:Rectangle, pIndex:int ):Emoticone { emoticone = new Emoticone(); emoticone.gotoAndStop ( pIndex + 1 ); emoticone.x = pRectangle.x + 1; emoticone.y = pRectangle.y - ((contenuHistorique.scrollV - 1)*(contenuHistorique.textHeight/contenuHistorique.numLines))+1; addChild ( emoticone ); return emoticone; Chapitre 18 ? Sockets ? version 0.1 16 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org } } } La classe MoteurEmoticone est liée à un clip contenant deux champs texte contenuHistorique et contenuSaisie. Afin de pouvoir informer le reste de l?application de la saisie utilisateur, la classe MoteurEmoticone diffuse un événement EvenementSaisie.ENVOI_MESSAGE. Au sein du paquetage org.bytearray.evenements nous définissons la classe EvenementSaisie suivante : package org.bytearray.evenements { import flash.events.Event; public class EvenementSaisie extends Event { public static const ENVOI_MESSAGE:String = "envoiMessage"; public var saisie:String; public function EvenementSaisie ( pType:String, pSaisie:String ) { super( pType, false, false ); saisie = pSaisie; } public override function clone ():Event { return new EvenementSaisie ( type, saisie ); } public override function toString ():String { return '[EvenementSaisie type="'+ type +'" bubbles=' + bubbles + ' eventPhase='+ eventPhase + ' cancelable=' + cancelable +' saisie=' + saisie +']'; } } Chapitre 18 ? Sockets ? version 0.1 17 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org } La classe EvenementMessage permet d?informer l?application de l?arrivée de nouveaux messages. Au sein du même paquetage que la classe EvenementSaisie nous définissons la classe EvenementMessage suivante : package org.bytearray.evenements { import flash.events.Event; public class EvenementMessage extends Event { public static const RECEPTION_MESSAGE:String = "receptionMessage"; public var message:String; public function EvenementMessage ( pType:String, pMessage:String ) { super( pType, false, false ); message = pMessage; } public override function clone ():Event { return new EvenementMessage ( type, message ); } public override function toString ():String { return '[EvenementMessage type="'+ type +'" bubbles=' + bubbles + ' eventPhase='+ eventPhase + ' cancelable=' + cancelable +' message=' + message + ']'; } } } Afin de tester la classe MoteurEmoticone nous associons la classe de document suivante à un nouveau document Flash CS3 : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; Chapitre 18 ? Sockets ? version 0.1 18 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org import org.bytearray.emoticones.MoteurEmoticone; public class Document extends ApplicationDefaut { public var client:MoteurEmoticone; public function Document () { client = new MoteurEmoticone(); client.x = (stage.stageWidth - client.width) >> 1; client.y = (stage.stageHeight - client.height) >> 1; addChild ( client ); } } } La figure 18-4 illustre l?interface de t?chat : Figure 18-4. Client chat multi-utilisateur. L?objet MoteurEmoticone diffuse un événement EvenementSaisie.ENVOI_MESSAGE que nous écoutons afin de récupérer le texte saisi : package org.bytearray.document Chapitre 18 ? Sockets ? version 0.1 19 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org { import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.emoticones.MoteurEmoticone; import org.bytearray.evenements.EvenementSaisie; public class Document extends ApplicationDefaut { public var client:MoteurEmoticone; public function Document () { client = new MoteurEmoticone(); client.x = (stage.stageWidth - client.width) >> 1; client.y = (stage.stageHeight - client.height) >> 1; addChild ( client ); client.addEventListener ( EvenementSaisie.ENVOI_MESSAGE, envoiMessage ); } private function envoiMessage ( pEvt:EvenementSaisie ):void { // affiche : [EvenementSaisie type="envoiMessage" bubbles=false eventPhase=2 cancelable=false saisie=salut !] trace( pEvt ); } } } Puis nous envoyons les données au serveur de socket grâce à la méthode send de l?objet XMLSocket : package org.bytearray.document { import flash.events.DataEvent; import flash.net.XMLSocket; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.emoticones.MoteurEmoticone; import org.bytearray.evenements.EvenementSaisie; public class Document extends ApplicationDefaut { private static const ADRESSE:String = "localhost"; private static const PORT:int = 10000; public var client:MoteurEmoticone; public var connexionSocket:XMLSocket; Chapitre 18 ? Sockets ? version 0.1 20 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org public function Document () { client = new MoteurEmoticone(); client.x = (stage.stageWidth - client.width) >> 1; client.y = (stage.stageHeight - client.height) >> 1; addChild ( client ); client.addEventListener ( EvenementSaisie.ENVOI_MESSAGE, envoiMessage ); connexionSocket = new XMLSocket ( Document.ADRESSE, Document.PORT ); connexionSocket.addEventListener ( DataEvent.DATA, receptionDonnees ); } private function envoiMessage ( pEvt:EvenementSaisie ):void { // affiche : [EvenementSaisie type="envoiMessage" bubbles=false eventPhase=2 cancelable=false saisie=salut !] trace( pEvt ); // envoi des données auprès du serveur de socket connexionSocket.send ( pEvt.saisie ); } private function receptionDonnees ( pEvt:DataEvent ):void { // affiche : salut ! trace( pEvt.data ); } } } Le principe est très simple, aussitôt les données envoyées, le serveur de socket transmet le texte saisi par un des participants à tous les clients connectés. Pour que l?objet MoteurEmoticone soit averti des messages provenant du serveur et affiche la conversation, nous diffusons un événement EvenementMessage.RECEPTION_MESSAGE : package org.bytearray.document { import flash.events.DataEvent; Chapitre 18 ? Sockets ? version 0.1 21 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.net.XMLSocket; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.emoticones.MoteurEmoticone; import org.bytearray.evenements.EvenementMessage; import org.bytearray.evenements.EvenementSaisie; public class Document extends ApplicationDefaut { private static const ADRESSE:String = "localhost"; private static const PORT:int = 10000; public var client:MoteurEmoticone; public var connexionSocket:XMLSocket; public function Document () { client = new MoteurEmoticone(); client.x = (stage.stageWidth - client.width) >> 1; client.y = (stage.stageHeight - client.height) >> 1; addChild ( client ); client.addEventListener ( EvenementSaisie.ENVOI_MESSAGE, envoiMessage ); // écoute de l'événement EvenementMessage.RECEPTION_MESSAGE afin d'afficher le texte addEventListener ( EvenementMessage.RECEPTION_MESSAGE, client.afficheMessage ); connexionSocket = new XMLSocket ( Document.ADRESSE, Document.PORT ); connexionSocket.addEventListener ( DataEvent.DATA, receptionDonnees ); } private function envoiMessage ( pEvt:EvenementSaisie ):void { // affiche : [EvenementSaisie type="envoiMessage" bubbles=false eventPhase=2 cancelable=false saisie=salut !] trace( pEvt ); // envoi des données auprès du serveur de socket connexionSocket.send ( pEvt.saisie ); } private function receptionDonnees ( pEvt:DataEvent ):void { // diffusion d'un événement EvenementMessage.RECEPTION_MESSAGE afin d'afficher les données reçues du serveur dispatchEvent ( new EvenementMessage ( EvenementMessage.RECEPTION_MESSAGE, pEvt.data ) ); Chapitre 18 ? Sockets ? version 0.1 22 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org } } } En testant notre client nous remarquons que le texte est bien affiché lorsque nous l?envoyons, comme l?illustre la figure 18-5 : Figure 18-5. Client flash connecté. Si nous connectons un deuxième client, notre chat multi utilisateur fonctionne. Vous pouvez à présent déployer le serveur de socket ainsi que le client Flash sur votre serveur et diffuser l?adresse aux personnes avec qui vous souhaitez discuter. La classe XMLSocket permet le développement d?applications temps réel de manière très souple. Bien entendu, de nombreux serveurs existent déjà et sont conçus pour fonctionner avec Flash à l?aide de la classe XMLSocket. Parmi ceux là nous pouvons citer les serveurs suivants : ? Unity : serveur de socket XML payant de Colin Moock - http://www.moock.org/unity ? SmartFox Server : serveur de socket XML payant - http://www.smartfoxserver.com Chapitre 18 ? Sockets ? version 0.1 23 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org ? Jabber : serveur de socket XML gratuit destiné à la création d?applications de messageries instantanées - http://www.jabber.org Une des limitations de la classe XMLSocket est liée à limitation du format des données échangées. Nous allons voir dans cette nouvelle partie dans quelle mesure la classe Socket peut s?avérer utile. A retenir ? La classe XMLSocket permet d?échanger des données au format XML et texte entre un client et un serveur de socket. ? En tant que relais, le serveur de socket permet de faire transiter des informations entre plusieurs clients connectés. ? Chaque message est terminé par un octet nul. La classe Socket ActionScript 3 intègre une nouvelle classe Socket se différenciant quelque peu de la classe XMLSocket. Comme nous l?avons vu précédemment, la classe XMLSocket transmet chaque message en le terminant par un octet nul. Ce comportement provoque un troncage des données binaire. La classe Socket ne souffre pas de cette limitation et offre la possibilité de transférer un flux binaire brut sans altérations. Si nous devions schématiser le fonctionnement d?un serveur de socket binaire nous pourrions l?illustrer de la manière suivante : Chapitre 18 ? Sockets ? version 0.1 24 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 18-6. XMLSocket et Socket. Nous allons ensemble développer un serveur de socket binaire afin de transférer par une connexion socket un élément graphique telle une image ou un SWF. Outre l?expérimentation technique, ce procédé permet d?éviter la mise en cache des éléments graphiques au sein du navigateur. L?élément graphique est chargé directement en mémoire par le lecteur Flash puis affiché, cette approche évite la mise en cache de fichiers SWF et rend leur décompilation difficile. A retenir ? La classe Socket permet d?échanger des données au format binaire brutes. ? Grâce au caractère bas niveau de la classe Socket, il est possible d?implémenter n?importe quel protocole d?application. Créer un serveur de socket binaire Nous allons à nouveau utiliser PHP afin de créer notre serveur de socket binaire. L?intérêt de ce dernier sera de pouvoir recevoir des requêtes et d?y répondre. Nous allons ainsi définir notre propre protocole afin de normaliser les échanges entre les deux acteurs. Dans un fichier PHP nommé ServeurSocket.php nous définissons le code suivant : Chapitre 18 ? Sockets ? version 0.1 25 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org #!/usr/bin/php -q <?php $adresse = "localhost"; $port = "10000"; $connexion = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($connexion, $adresse, $port); socket_listen($connexion, 1); echo "Le serveur de socket binaire est en route !"; $client = socket_accept($connexion); socket_close($client); socket_close($connexion); ?> Une fois le serveur démarré nous pouvons nous y connecter de la manière suivante : // création de la connexion var connexion:Socket = new Socket(); // connexion au serveur de socket connexion.connect( "localhost", 10000 ); // écoute des différents événements connexion.addEventListener( Event.CONNECT, clientConnecte ); connexion.addEventListener( Event.CLOSE, clientDeconnecte ); function clientConnecte ( pEvt:Event ):void { trace("client connecté"); } function clientDeconnecte ( pEvt:Event ):void { trace("client déconnecté"); } Si nous testons le code précédent, nous remarquons que la connexion est aussitôt fermée. Le panneau de sortie affiche les messages suivants : client connecté client déconnecté Chapitre 18 ? Sockets ? version 0.1 26 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Nous modifions le code du serveur de socket binaire en ajoutant un appel à la méthode socket_write pour envoyer les données au client en cours : #!/usr/bin/php -q <?php $adresse = "localhost"; $port = "10000"; $connexion = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($connexion, $adresse, $port); socket_listen($connexion, 1); echo "Le serveur de socket binaire est en route !"; $client = socket_accept($connexion); $messageSortant = "Bienvenue sur le serveur de socket, mais désolé vous êtes déconnecté !"; socket_write ($client, $messageSortant ); socket_close($client); socket_close($connexion); ?> Nous envoyons désormais des données depuis le serveur avant de clore la connexion. Lorsque le serveur transmet les données, l?événement ProgressEvent.SOCKET_DATA est diffusé : // création de la connexion var connexion:Socket = new Socket(); // connexion au serveur de socket connexion.connect( "localhost", 10000 ); // écoute des différents événements connexion.addEventListener( Event.CONNECT, clientConnecte ); connexion.addEventListener( ProgressEvent.SOCKET_DATA, donneesRecues ); connexion.addEventListener( Event.CLOSE, clientDeconnecte ); function clientConnecte ( pEvt:Event ):void { trace("client connecté"); } function donneesRecues ( pEvt:ProgressEvent ):void { Chapitre 18 ? Sockets ? version 0.1 27 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org trace("données reçues"); } function clientDeconnecte ( pEvt:Event ):void { trace("client déconnecté"); } Si nous testons le code précédent nous voyons que le client parvient à se connecter, puis les données sont reçues, enfin le client est automatiquement déconnecté. Le panneau de sortie affiche les messages suivants : client connecté données reçues client déconnecté Contrairement à la classe XMLSocket, les données reçues par la classe Socket doivent être décodées. Afin de lire les données transmises par le serveur nous utilisons ici la méthode readUTFBytes de l?objet Socket dont voici la signature : public function readUTFBytes(length:uint):String Celle-ci accepte un paramètre length correspondant au nombre d?octets à lire et à renvoyer sous forme de chaîne de caractères UTF-8. Afin de lire les données disponibles, nous utilisons la propriété bytesAvailable : function donneesRecues ( pEvt:ProgressEvent ):void { // affiche : Bienvenue sur le serveur de socket, mais désolé vous êtes déconnecté ! trace( pEvt.target.readUTFBytes ( pEvt.target.bytesAvailable ) ); } N?oubliez pas que dans un contexte de transfert de données TCP/IP, les données arrivent par paquet d?octets. Il est donc nécessaire de lire le flux de données au fur et à mesure que les données arrivent au sein du lecteur. Celles-ci s?accumulent au sein de l?objet Socket et peuvent être lues à l?aide des différentes méthodes définies par la classe Socket. La propriété bytesAvailable nous permet de récupérer le nombre d?octets disponibles actuellement au sein du flux téléchargé et évite de sortir du flux de données. Chapitre 18 ? Sockets ? version 0.1 28 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous tentons de lire des octets non disponibles, l?erreur suivante est levée à l?exécution : Error: Error #2030: Fin de fichier détectée. Nous reviendrons en détail sur les différentes méthodes de lecture et d?écriture de flux binaire au cours du chapitre 20 intitulé ByteArray. A retenir ? Contrairement à la classe XMLSocket, la classe Socket reçoit un flux binaire brut de la part du serveur. ? Ces données doivent être décodées à l?aide des différentes méthodes de la classe Socket. Echanger des données Nous avons évoqué en début de chapitre la notion de protocole d?application afin d?échanger des messages entre un client un serveur. Les protocoles d?applications tels FTP, SMTP, ou POP s?appuient sur des messages prédéfinis afin d?appeler certaines fonctionnalités auprès du serveur de socket. Nous allons reproduire ce même comportement en demandant à notre serveur de socket de transférer un fichier graphique présent à ses côtés. En passant la chaîne de caractère ?ENVOIE? suivie du nom du fichier, le serveur renverra le flux du fichier demandé. Afin d?intégrer ce protocole d?application nous modifions les lignes suivantes au sein du serveur de socket binaire : #!/usr/bin/php -q <?php $adresse = "localhost"; $port = "10000"; $connexion = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($connexion, $adresse, $port); socket_listen($connexion, 1); echo "Le serveur de socket binaire est en route !"; $client = socket_accept($connexion); $actif = true; $COMMANDE_ENVOIE = "ENVOIE"; do Chapitre 18 ? Sockets ? version 0.1 29 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org { $actif = socket_read($client, 1024, PHP_BINARY_READ); $array = split (" ", trim($actif)); $commande = $array[0]; $fichier = $array[1]; $pointeur = fopen ($fichier, "rb"); $donnees = fread ($pointeur, filesize ($fichier)); fclose ($pointeur); if ( $commande == $COMMANDE_ENVOIE ) { $longueur = pack('N', strlen($donnees)); socket_write($client, $longueur); socket_write($client, $donnees); } else socket_write ($client, "commande non reconnue"); } while ( $actif ); socket_close($client); socket_close($connexion); ?> Rappelez vous d?un point essentiel, le protocole TCP/IP transfère les données par paquets, c?est à nous de nous assurer que la totalité des données ont été transférées. Il est donc impératif de récupérer l?ensemble des paquets cotés client avant de tenter d?afficher le fichier transféré : // création de la connexion var connexion:Socket = new Socket(); // connexion au serveur de socket connexion.connect( "localhost", 10000 ); // écoute des différents événements connexion.addEventListener( Event.CONNECT, clientConnecte ); connexion.addEventListener( ProgressEvent.SOCKET_DATA, donneesRecues ); connexion.addEventListener( Event.CLOSE, clientDeconnecte ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); // demande du chargement du fichier connexion.writeUTFBytes ("ENVOIE:DSC02602.JPG\r\n"); connexion.flush(); function clientConnecte ( pEvt:Event ):void { trace("client connecté"); } function clientDeconnecte ( pEvt:Event ):void Chapitre 18 ? Sockets ? version 0.1 30 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org { trace("client déconnecté"); } function erreurConnexion ( pEvt:Event ):void { trace("erreur de connexion au serveur"); } // création d'un tableau binaire pour contenir les données entrantes var donnees:ByteArray = new ByteArray(); // nombre d'octets totaux à transférer var longueur:Number; function donneesRecues ( pEvt:ProgressEvent ):void { // nous récupérons le nombre d'octets totaux if ( !longueur ) longueur = pEvt.target.readUnsignedInt(); // les données sont progressivement stockées au sein du tableau donnees pEvt.target.readBytes ( donnees, donnees.length, pEvt.target.bytesAvailable ); // données chargées if ( donnees.length == longueur ) trace ("transfert des données terminée"); } Grâce à la méthode writeUTFBytes, nous encodons la chaîne de caractère passée en paramètre en flux binaire. Afin de transmettre celle-ci au serveur nous poussons les données à l?aide de la méthode flush. Aussitôt la commande reçue, le serveur renvoie la longueur totale des données à recevoir, puis le flux de l?élément graphique est transmis. Afin de sauver les données entrantes, nous créons un tableau de sauvegarde dans lequel nous plaçons les données téléchargées lors de la diffusion de l?événement ProgressEvent.SOCKET_DATA. Souvenez-vous, nous avions découvert l?objet Loader lors du chapitre 13, ce dernier permettait le chargement de contenu externe. La classe Loader définit une méthode loadBytes permettant de rendre graphiquement un flux binaire passé en paramètre. Voici la signature de la méthode loadBytes : public function loadBytes(bytes:ByteArray, context:LoaderContext = null):void Chapitre 18 ? Sockets ? version 0.1 31 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Détail de chacun des paramètres : ? bytes : le flux binaire à rendre graphiquement. ? context : le contexte de chargement, lié au modèle de sécurité. Attention, la méthode loadBytes n?accepte que des flux binaires compatibles avec l?objet Loader. En d?autres termes, seuls les flux d?images ou de SWF pourront être affichés. Si un flux non compatible est passé à la méthode loadBytes, une erreur de type IOErrorEvent.IO_ERROR est levée. Dans le code suivant, un objet Loader est créé. Une fois les données totalement téléchargées, nous les injectons au sein de ce dernier : // création de la connexion var connexion:Socket = new Socket(); // connexion au serveur de socket connexion.connect( "localhost", 10000 ); // écoute des différents événements connexion.addEventListener( Event.CONNECT, clientConnecte ); connexion.addEventListener( ProgressEvent.SOCKET_DATA, donneesRecues ); connexion.addEventListener( Event.CLOSE, clientDeconnecte ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); // demande du chargement du fichier connexion.writeUTFBytes ("ENVOIE:DSC02602.JPG\r\n"); connexion.flush(); function clientConnecte ( pEvt:Event ):void { trace("client connecté"); } function clientDeconnecte ( pEvt:Event ):void { trace("client déconnecté"); } function erreurConnexion ( pEvt:Event ):void { trace("erreur de connexion au serveur"); } var chargeur:Loader = new Loader(); Chapitre 18 ? Sockets ? version 0.1 32 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org addChild ( chargeur ); // création d'un tableau binaire pour contenir les données entrantes var donnees:ByteArray = new ByteArray(); // nombre d'octets totaux à transférer var longueur:Number; function donneesRecues ( pEvt:ProgressEvent ):void { // nous récupérons le nombre d'octets totaux if ( !longueur ) longueur = pEvt.target.readUnsignedInt(); // les données sont progressivement stockées au sein du tableau donnees pEvt.target.readBytes ( donnees, donnees.length, pEvt.target.bytesAvailable ); // lorsque le flux binaire est totalement chargé nous l?affichons grâce à l?objet Loader if ( donnees.length == longueur ) chargeur.loadBytes( donnees ); } En testant le code précédent nous remarquons que le fichier est téléchargé par la connexion socket puis affiché au sein du lecteur. Une fois les données injectées, l?objet Loader diffuse un événement Event.COMPLETE. Dans le code suivant, nous redimensionnons l?image et la centrons une fois le flux affiché : chargeur.contentLoaderInfo.addEventListener( Event.COMPLETE, injectionTerminee ); function injectionTerminee ( pEvt:Event ):void { var contenu:DisplayObject = pEvt.target.content; var objetChargeur:Loader = pEvt.target.loader; if ( contenu is Bitmap ) Bitmap ( contenu ).smoothing = true; var ratio:Number = Math.min ( 350 / pEvt.target.content.width, 350 / pEvt.target.content.height ); objetChargeur.scaleX = objetChargeur.scaleY = ratio; objetChargeur.x = (stage.stageWidth - objetChargeur.width) / 2; objetChargeur.y = (stage.stageHeight - objetChargeur.height) / 2; } Le résultat est illustré par la figure 18-7 : Chapitre 18 ? Sockets ? version 0.1 33 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 18-7. Image chargée par connexion socket. Il serait intéressant de rendre cette communication transparente au sein d?un objet spécifique. Nous allons créer une classe ChargeurFlux qui procèdera en interne aux différentes commandes du protocole. Au sein d?un paquetage org.bytearray.chargeur nous définissons la classe ChargeurFlux suivante : package org.bytearray.chargeur { import flash.events.EventDispatcher; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.Socket; public class ChargeurFlux extends EventDispatcher { private var connexion:Socket; public function ChargeurFlux ( pAdresse:String=null, pPort:int=0 ) { connexion = new Socket(pAdresse, pPort); connexion.addEventListener ( Event.CONNECT, redirigeEvenement ); Chapitre 18 ? Sockets ? version 0.1 34 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org connexion.addEventListener ( ProgressEvent.SOCKET_DATA, transfertDonnees ); connexion.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function transfertDonnees ( pEvt:ProgressEvent ):void { } } } Puis nous ajoutons un objet ByteArray interne afin d?accueillir le flux téléchargé : package org.bytearray.chargeur { import flash.events.EventDispatcher; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.Socket; import flash.utils.ByteArray; public class ChargeurFlux extends EventDispatcher { private var connexion:Socket; private var donnees:ByteArray; public function ChargeurFlux ( pAdresse:String=null, pPort:int=0 ) { connexion = new Socket(pAdresse, pPort); donnees = new ByteArray(); connexion.addEventListener ( Event.CONNECT, redirigeEvenement ); connexion.addEventListener ( ProgressEvent.SOCKET_DATA, transfertDonnees ); connexion.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } Chapitre 18 ? Sockets ? version 0.1 35 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function transfertDonnees ( pEvt:ProgressEvent ):void { } } } Puis nous implémentons les méthodes charge et connecte : package org.bytearray.chargeur { import flash.events.EventDispatcher; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.Socket; import flash.utils.ByteArray; public class ChargeurFlux extends EventDispatcher { private var connexion:Socket; private var donnees:ByteArray; private static const ENVOIE:String = "ENVOIE"; public function ChargeurFlux ( pAdresse:String=null, pPort:int=0 ) { connexion = new Socket(pAdresse, pPort); donnees = new ByteArray(); connexion.addEventListener ( Event.CONNECT, redirigeEvenement ); connexion.addEventListener ( ProgressEvent.SOCKET_DATA, transfertDonnees ); connexion.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } public function charge ( pFichier:String ):void { Chapitre 18 ? Sockets ? version 0.1 36 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org connexion.writeUTFBytes ( ChargeurFlux.ENVOIE + " " + pFichier + "\r\n" ); connexion.flush(); } public function connecte ( pAdresse:String, pPort:int ):void { connexion.connect ( pAdresse, pPort ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function transfertDonnees ( pEvt:ProgressEvent ):void { } } } Enfin, nous ajoutons la logique associée afin de déterminer la fin du chargement du flux : package org.bytearray.chargeur { import flash.events.EventDispatcher; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.Socket; import flash.utils.ByteArray; import org.bytearray.chargeur.evenements.EvenementChargeurFlux; public class ChargeurFlux extends EventDispatcher { private var connexion:Socket; private var donnees:ByteArray; private var longueur:Number; private static const ENVOIE:String = "ENVOIE"; public function ChargeurFlux ( pAdresse:String=null, pPort:int=0 ) { Chapitre 18 ? Sockets ? version 0.1 37 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org connexion = new Socket(pAdresse, pPort); donnees = new ByteArray(); connexion.addEventListener ( Event.CONNECT, redirigeEvenement ); connexion.addEventListener ( ProgressEvent.SOCKET_DATA, transfertDonnees ); connexion.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } public function charge ( pFichier:String ):void { connexion.writeUTFBytes ( ChargeurFlux.ENVOIE + " " + pFichier + "\r\n" ); connexion.flush(); } public function connecte ( pAdresse:String, pPort:int ):void { connexion.connect ( pAdresse, pPort ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function transfertDonnees ( pEvt:ProgressEvent ):void { if ( !longueur ) longueur = pEvt.target.readUnsignedInt(); pEvt.target.readBytes ( donnees, donnees.length, pEvt.target.bytesAvailable ); if ( donnees.length == longueur ) { dispatchEvent ( new EvenementChargeurFlux ( EvenementChargeurFlux.TERMINE, donnees ) ); donnees = new ByteArray(); } } } } Chapitre 18 ? Sockets ? version 0.1 38 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de charger un élément graphique stocké sur le serveur, nous instancions simplement l?objet ChargeurFlux puis nous appelons sa méthode charge : import org.bytearray.chargeur.ChargeurFlux; import org.bytearray.chargeur.evenements.EvenementChargeurFlux; var monChargeur:ChargeurFlux = new ChargeurFlux("localhost", 10000); monChargeur.charge ("blason.png"); var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener( Event.COMPLETE, injectionTerminee ); addChild ( chargeur ); monChargeur.addEventListener ( EvenementChargeurFlux.TERMINE, affiche ); function affiche ( pEvt:EvenementChargeurFlux ):void { chargeur.loadBytes( pEvt.donnees ); } function injectionTerminee ( pEvt:Event ):void { var contenu:DisplayObject = pEvt.target.content; var objetChargeur:Loader = pEvt.target.loader; if ( contenu is Bitmap ) Bitmap ( contenu ).smoothing = true; var ratio:Number = Math.min ( 350 / pEvt.target.width, 350 / pEvt.target.height ); objetChargeur.scaleX = objetChargeur.scaleY = ratio; objetChargeur.x = (stage.stageWidth - objetChargeur.width) / 2; objetChargeur.y = (stage.stageHeight - objetChargeur.height) / 2; } La classe ChargeurFlux peut ainsi être utilisée pour le chargement de fichiers SWF afin d?éviter leur mise en cache au sein du navigateur et rendre leur décompilation moins facile : monChargeur.charge ("monsite.swf"); A vous d?imaginer de nouvelles fonctionnalités ! A retenir Chapitre 18 ? Sockets ? version 0.1 39 / 39 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode writeUTFBytes permet d?écrire une chaîne de caractères au format binaire. ? La méthode flush permet d?envoyer les données binaires au serveur de socket. Dans le prochain chapitre nous découvrirons la technologie Flash Remoting afin de dialoguer de manière optimisée avec différents langages serveurs et bases de données. Chapitre 19 ? Flash Remoting ? version 0.1 1 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org 19 Flash Remoting LA TECHNOLOGIE............................................................................................... 1 UN FORMAT OPTIMISÉ ............................................................................................. 2 PASSERELLE REMOTING .................................................................................. 4 DÉPLOIEMENT......................................................................................................... 5 LE SERVICE............................................................................................................ 7 SE CONNECTER AU SERVICE .................................................................................. 15 LA CLASSE RESPONDER ........................................................................................ 16 APPELER UNE MÉTHODE DISTANTE ....................................................................... 18 ECHANGER DES DONNEES PRIMITIVES................................................................... 22 ECHANGER DES DONNEES COMPOSITES................................................................. 27 ECHANGER DES DONNEES TYPEES......................................................................... 32 ENVOYER UN EMAIL AVEC FLASH REMOTING....................................................... 33 EXPORTER UNE IMAGE .......................................................................................... 40 SE CONNECTER A UNE BASE DE DONNEES.............................................................. 49 SECURITE ............................................................................................................. 61 LA CLASSE SERVICE......................................................................................... 62 La technologie Développé à l?origine par Macromedia, Flash Remoting est une technologie visant à optimiser grandement les échanges client-serveur. Le lecteur Flash 6 fut le premier à en bénéficier, mais la puissance de celle-ci ne fut pas perçue immédiatement par la communauté due en partie à un manque de documentation et d?informations à ce sujet. Grâce à Flash Remoting nous allons optimiser notre temps de développement d?applications dynamiques et apprendre à penser différemment nos échanges client-serveur. Des frameworks tels Flex Chapitre 19 ? Flash Remoting ? version 0.1 2 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ou AIR s?appuient aujourd?hui fortement sur Flash Remoting trop souvent inconnu des développeurs Flash. Nous allons découvrir au sein de ce chapitre comment tirer profit de cette technologie en ActionScript 3 à travers différentes applications. A retenir ? La technologie Flash Remoting vu le jour en 2001 au sein du lecteur 6 (Flash MX). ? Flash Remoting permet d?optimiser les échanges client-serveur. Un format optimisé Toute la puissance de Flash Remoting réside dans le format d?échanges utilisé, connu sous le nom d?AMF (Action Message Format). Afin de bien comprendre l?intérêt de ce format, il convient de revenir sur le fonctionnement interne du lecteur Flash dans un contexte de chargement et d?envoi de données. Nous avons vu au cours du chapitre 14 intitulé Chargement et envoi de données que les données échangées entre le lecteur Flash et le script serveur étaient par défaut réalisées au format texte. Dans le cas d?échanges de variables encodées URL, une représentation texte des variables doit être réalisée. Nous devons donc nous assurer manuellement de la sérialisation et désérialisation des données ce qui peut s?avérer long et fastidieux, surtout dans un contexte de large flux de données. Nous pouvons alors utiliser le format XML, mais là encore bien que l?introduction de la norme ECMAScript pour XML (E4X) ait sensiblement amélioré les performances d?interprétation de flux XML, dans un contexte d?échanges, le flux XML reste transporté au format texte ce qui n?est pas optimisé en terme de bande passante. Grâce au format AMF, le lecteur Flash se charge de sérialiser et désérialiser les données ActionScript nativement. Nous pouvons ainsi échanger des données complexes typées, sans se soucier de la manière dont cela est réalisé. Lorsque nous souhaitons transmettre des données par Flash Remoting, le lecteur encode un paquet AMF binaire compressé contenant les données sérialisées, puis le transmet au script serveur par la méthode POST par le biais du protocole HTTP. Voici un extrait de la transaction HTTP : Chapitre 19 ? Flash Remoting ? version 0.1 3 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org POST gateway.php HTTP/1.1 Host: www.bytearray.org User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0 .8,image/png,*/*;q=0.5 Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://www.bytearray.org/wp- content/projects/jpegencoder/media_snapshot.swf Content-type: application/x-amf Content-length: 4944 Paquet AMF Une fois le paquet AMF décodé par le serveur, ce dernier répond au lecteur Flash en lui transmettant un nouveau paquet AMF de réponse : HTTP/1.1 200 OK Date: Sat, 02 Feb 2008 02:55:40 GMT Server: Apache/2.0.54 (Debian GNU/Linux) PHP/4.3.10-22 mod_ssl/2.0.54 OpenSSL/0.9.7e mod_perl/1.999.21 Perl/v5.8.4 X-Powered-By: PHP/4.3.10-22 Expires: Sat, 2 Feb 2008 03:55:40 GMT Cache-Control: no-store Pragma: no-store Content-length: 114 Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: application/x-amf Paquet AMF Il est important de comprendre que contrairement au format texte, le format AMF est un format binaire compressé natif du lecteur Flash. L?utilisation du format AMF nous permet donc d?optimiser la bande passante utilisée et d?augmenter les performances d?une application en s?affranchissant totalement de toute sérialisation manuelle des données. James Ward a dernièrement publié une application comparant les performances d?échanges au format JSON, XML et AMF. L?application est disponible à l?adresse suivante : http://www.jamesward.org/blazebench/ Bien que le lecteur Flash puisse nativement encoder ou décoder un paquet AMF, l?application serveur se doit elle aussi de pouvoir décoder ce même paquet. C?est ici qu?intervient la notion de passerelle remoting. A retenir Chapitre 19 ? Flash Remoting ? version 0.1 4 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?acronyme AMF signifie Action Message Format. ? Flash Remoting est basé sur un échange des données au format AMF binaire par le biais du protocole HTTP. ? Encoder et décoder un paquet AMF est beaucoup plus rapide et moins gourmand qu?une sérialisation et désérialisation au format texte. ? Deux versions du format AMF existent aujourd?hui : AMF0 (ActionScript 1 et 2) et AMF3 (ActionScript 3). ? Les formats AMF0 et AMF3 sont ouverts et documentés. ? Aujourd?hui, le format AMF est utilisé au sein des classes NetConnection, LocalConnection, SharedObject, ByteArray, Socket et URLStream. Passerelle remoting Lorsque le lecteur Flash transmet un paquet AMF à un script serveur, celui-ci n?est pas par défaut en mesure de le décoder. Des projets appelés passerelles remoting ont ainsi vu le jour, permettant à des langages serveurs tels PHP, Java ou C# de comprendre le format AMF. Chaque passerelle est ainsi liée à un langage serveur et se charge de convertir le flux AMF transmis en données compatibles. Afin de développer ces passerelles, le format AMF a du être piraté par la communauté afin de comprendre comment le décoder. Initialement non documenté, Adobe a finalement rendu public les spécifications du format AMF en décembre 2007. Toute personne souhaitant développer une passerelle pour un langage spécifique peut donc se baser sur ces spécifications fournies par Adobe. Celles ci peuvent être téléchargées à l?adresse suivante : http://labs.adobe.com/technologies/blazeds/ En plus d?ouvrir le format AMF, Adobe a décidé de fournir une passerelle officielle pour la plateforme Java J2EE nommée BlazeDS téléchargeable à la même adresse. Il existe aujourd?hui un grand nombre de passerelles remoting pour la plupart open source, le tableau suivant regroupe les plus connues associées à chaque langage : Nom Langage Open Source Lien Chapitre 19 ? Flash Remoting ? version 0.1 5 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org AMFPHP PHP Oui http://www.amfphp.org Web ORB PHP, .NET, Ruby, Java. Non http://www.themidnightcod ers.com BlazeDS Java Oui http://labs.adobe.com/techn ologies/blazeds/ Ruby AMF Ruby Oui http://www.rubyamf.org Fluorine .NET Oui http://fluorine.thesilentgrou p.com Tableau 1. Passerelles AMF. La figure 19-1 illustre le modèle de communication entre le lecteur Flash et la passerelle remoting : Figure 19-1. Communication entre le lecteur Flash et la passerelle remoting. Les paquets AMF devant forcément transiter par la passerelle afin d?êtres décodés, le lecteur ne se connecte donc jamais directement auprès du script serveur, mais auprès de la passerelle. Nous allons utiliser tout au long du chapitre, la passerelle remoting AMFPHP afin de simplifier nos échanges avec le serveur. Déploiement Avant de déployer AMFPHP il convient de télécharger le projet à l?adresse suivante : http://sourceforge.net/projects/amfphp Chapitre 19 ? Flash Remoting ? version 0.1 6 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Attention, le format AMF3 n?est pris en charge que depuis la version 1.9, nous veillerons donc à télécharger une version égale ou ultérieure. Souvenez-vous, ActionScript 3 utilise le format AMF3, les anciennes versions d?ActionScript utilisent le format AMF0. Une fois AMFPHP téléchargé, nous extrayons l?archive et obtenons les répertoires illustrés par la figure 19-2 : Figure 19-2. Fichiers AMFPHP. Voici le détail des trois répertoires ainsi que du fichier gateway.php : ? browser : contient une application de déboguage que nous allons découvrir très bientôt. ? core : il s?agit des sources d?AMFPHP. Dans la majorité des cas nous n?irons jamais au sein de ce répertoire. ? services : les services distants sont placés au sein de ce répertoire. C?est le répertoire le plus important pour nous. ? gateway.php : la passerelle à laquelle nous nous connectons depuis Flash. Une des forces des différentes passerelles remoting réside dans leur simplicité de déploiement sur le serveur. Les passerelles sont déployables sur des serveurs mutualisés et ne nécessitent aucun accès privilégié au niveau de l?administration du serveur. AMFPHP requiert une version PHP 4.3.0 ou supérieure, l?utilisation de PHP 5 ajoutant quelques fonctionnalités intéressantes à AMFPHP. Nous plaçons le répertoire amfphp contenant les fichiers illustrés précédemment sur notre serveur au sein d?un répertoire echanges et accédons à l?adresse suivante pour vérifier que tout fonctionne correctement : http://localhost/echanges/gateway.php Chapitre 19 ? Flash Remoting ? version 0.1 7 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Si tout fonctionne, nous obtenons le message suivant : amfphp and this gateway are installed correctly. You may now connect to this gateway from Flash. Note: If you're reading an old tutorial, it will tell you that you should see a download window instead of this message. This confused people so this is the new behaviour starting from amfphp 1.2. Si vous souhaitez utiliser une autre passerelle remoting que AMFPHP, soyez rassurés, le fonctionnement de la plupart d?entre elles est similaire. A retenir ? La passerelle remoting joue le rôle d?intermédiaire entre le lecteur Flash et l?application serveur. ? Le déploiement d?une passerelle remoting ne prend que quelques secondes. ? Aucun droit spécifique n?est nécessaire auprès du serveur. Il est donc possible d?utiliser Flash Remoting sur un serveur mutualisé. ? AMFPHP requiert une version de PHP 4.3.0 au minimum. Le service Contrairement aux moyens de communication étudiés précédemment, Flash Remoting définit la notion de service. Le service est une classe définissant les méthodes que nous souhaitons appeler depuis l?application Flash. Nous pourrions ainsi appeler le service ?classe distante?. Un des avantages de Flash Remoting est lié à sa conception orientée objet. Au lieu d?appeler différents scripts serveurs stockés au sein de plusieurs fichiers PHP, nous appelons directement depuis Flash des méthodes définies au sein du service. La figure 19-3 illustre l?idée : Chapitre 19 ? Flash Remoting ? version 0.1 8 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 19-3. Service distant associé à la passerelle. Nous allons créer notre premier service en plaçant au sein du répertoire services, une classe PHP nommée Echanges.php. Celle-ci est définie au sein d?un paquetage org.bytearray.test : <?php class Echanges { function Echanges ( ) { } function premierAppel () { } } ?> La classe Echanges doit donc être accessible à l?adresse suivante : http://localhost/echanges/services/org/bytearray/test/Echanges.php Notons que contrairement à ActionScript 3, PHP 4 et 5 n?intègrent pas de mot clé package, ainsi la classe ne contient aucune indication liée à son paquetage. La classe PHP Echanges définit une seule méthode premierAppel que nous allons pouvoir tester directement depuis un navigateur grâce à un outil extrêmement pratique appelé Service Browser (explorateur de services). Dans notre exemple, l?explorateur de service est accessible à l?adresse suivante : http://localhost/echanges/browser/ L?explorateur de service est une application Flex développée afin de pouvoir tester depuis notre navigateur les différentes méthodes de notre service. La partie gauche de l?application indique les services actuellement déployés. En déroulant les n?uds nous pouvons accéder à un service spécifique. La figure 19-4 illustre l?explorateur : Chapitre 19 ? Flash Remoting ? version 0.1 9 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 19-4. Explorateur de service. En sélectionnant un service, la partie droite de l?application découvre les méthodes associées au service. Chaque méthode est accessible et peut être testée directement depuis le navigateur. Cela permet de pouvoir développer la logique serveur sans avoir à tester depuis Flash. L?onglet Results affiche alors le résultat de l?exécution de la méthode distante premierAppel : Figure 19-5. Explorateur de méthodes liées au service. Dans notre exemple, la méthode premierAppel ne renvoie aucune valeur, l?onglet Results affiche donc null. Afin de retourner des informations à Flash, la méthode doit obligatoirement utiliser le mot clé return : Chapitre 19 ? Flash Remoting ? version 0.1 10 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org <?php class Echanges { function Echanges ( ) { } function premierAppel () { return "cela fonctionne sans problème !"; } } ?> Si nous testons à nouveau la méthode, nous pouvons voir directement les valeurs retournées : Figure 19-6. Explorateur de méthodes liées au service. La possibilité de pouvoir exécuter depuis le navigateur les méthodes distantes est un avantage majeur. L?explorateur service doit être considéré comme un véritable outil de déboguage de service. Au cas où une erreur PHP serait présente au sein du service, l?explorateur de service nous l?indique au sein de l?onglet Results Chapitre 19 ? Flash Remoting ? version 0.1 11 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous oublions d?ajouter un point virgule en fin d?expression : <?php class Echanges { function Echanges ( ) { } function premierAppel () { return "cela fonctionne sans problème !" } } ?> En tentant d?exécuter la méthode, le message suivant est affiché au sein de l?onglet Results : Parse error: parse error, unexpected '}' in C:\Program Files\EasyPHP 2.0b1\www\echanges\services\org\bytearray\test\Echanges.php on line 18 Les onglets situés en dessous de l?onglet Test nous apportent d?autres informations comme les temps d?exécution ou le poids des données transférées. La figure 19-7 illustre les informations liées à l?appel de la méthode premierAppel : Chapitre 19 ? Flash Remoting ? version 0.1 12 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 19-7. Onglet Info. Imaginons que nous souhaitions afficher lors de l?exécution de la méthode premierAppel la longueur d?une chaîne. Nous pouvons utiliser pour cela la méthode statique trace de la classe NetDebug intégrée à AMFPHP. Dans le code suivant, nous affichons la longueur de la variable $chaine : <?php class Echanges { function Echanges ( ) { } Chapitre 19 ? Flash Remoting ? version 0.1 13 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org function premierAppel () { $chaine = "cela fonctionne sans problème !"; NetDebug::trace( "infos persos : " . strlen ( $chaine ) ); } } ?> En exécutant la méthode, l?onglet Trace retourne le message personnalisé telle une fenêtre de sortie classique. La figure 19-7 illustre le message affiché : Figure 19-7. Utilisation du déboguage personnalisé. Cette fonctionnalité s?avère très précieuse lors de déboguage de scripts, nous découvrirons l?intérêt des autres onglets très rapidement. Si vous disposez de PHP 5 sur votre serveur, vous avez la possibilité d?utiliser les modificateurs de contrôle d?accès similaire à ActionScript 3. Chapitre 19 ? Flash Remoting ? version 0.1 14 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org En définissant une méthode comme privée, celle-ci ne pourra plus être appelée depuis Flash, mais seulement depuis une autre méthode de la classe : <?php class Echanges { function Echanges ( ) { } private function premierAppel () { return "cela fonctionne sans problème !"; } } ?> L?explorateur de méthodes considère alors que cette méthode n?est plus disponible depuis l?extérieur. La figure 19-8 illustre l?état de l?explorateur de service : Figure 19-8. Aucune méthode disponible auprès du service. Nous retrouvons ici encore l?intérêt de la technologie Flash Remoting où nous évoluons dans un contexte orienté objet. L?utilisation de tels mécanismes améliore fortement la souplesse de développement d?une application dynamique. Il est temps de coder quelques lignes d?ActionScript, nous allons dès maintenant nous connecter au service Echanges et exécuter la méthode premierAppel. A retenir Chapitre 19 ? Flash Remoting ? version 0.1 15 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le service distant est une classe définissant différentes méthodes accessibles depuis Flash. ? L?explorateur de service nous permet de tester les méthodes en direct afin d?optimiser le déboguage. Se connecter au service Afin de se connecter à un service distant en ActionScript 3, nous disposons de différentes classes dont voici le détail : ? flash.net.NetConnection : la classe NetConnection permet de se connecter à un service distant ? flash.net.Socket : la classe Socket offre une connectivité TCP/IP offrant la possibilité d?échanger des données au format AMF à l?aide du protocole HTTP. Afin d?appeler la méthode premierAppel, nous créons une instance de la classe NetConnection : // création de la connexion var connexion:NetConnection = new NetConnection (); La classe NetConnection définit une propriété objectEncoding permettant de définir la version d?AMF utilisé pour les échanges. En ActionScript 3, le format AMF3 est utilisé par défaut : // création de la connexion var connexion:NetConnection = new NetConnection (); // affiche : 3 trace( connexion.objectEncoding ); // affiche : true trace( connexion.objectEncoding == ObjectEncoding.AMF3 ); Au cas où nous souhaiterions nous connecter à une passerelle n?étant pas compatible avec AMF3, nous devons spécifier explicitement d?utiliser le format AMF0 : // création de la connexion var connexion:NetConnection = new NetConnection (); // utilisation du format AMF0 pour les échanges connexion.objectEncoding = ObjectEncoding.AMF0; La version 1.9 d?AMFPHP étant compatible avec le format AMF3, nous ne modifions pas la propriété objectEncoding et nous connectons à la passerelle gateway.php à l?aide de la méthode connect : // création de la connexion var connexion:NetConnection = new NetConnection (); // connexion à la passerelle AMFPHP Chapitre 19 ? Flash Remoting ? version 0.1 16 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org connexion.connect ("http://localhost/echanges/gateway.php"); Même si l?adresse de la passerelle est erronée ou inaccessible, l?objet NetConnection ne renverra aucune erreur lors de l?appel de la méthode connect. L?objet NetConnection diffuse différents événements dont voici le détail : ? AsyncErrorEvent.ASYNC_ERROR : diffusé lorsqu?une erreur asynchrone intervient. ? IOErrorEvent.IO_ERROR : diffusé lorsque la transmission des données échoue. ? NetStatusEvent.NET_STATUS : diffusé lorsqu?un erreur fatale intervient. ? SecurityErrorEvent.SECURITY_ERROR : diffusé lorsque nous tentons d?appeler la méthode d?un service évoluant sur un autre domaine sans en avoir l?autorisation. Nous écoutons les différents événements en passant une seule fonction écouteur afin de centraliser la gestion des erreurs : // création de la connexion var connexion:NetConnection = new NetConnection (); // connexion à la passerelle amfphp connexion.connect ("http://localhost/echanges/gateway.php"); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion); function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } Nous devons maintenant gérer le retour du serveur, Flash CS3 intègre pour cela une classe spécifique. La classe Responder Avant d?appeler une méthode distante, il convient de créer au préalable un objet de gestion de retour des appels grâce à la classe flash.net.Responder. Le constructeur de la classe Responder possède la signature suivante : Chapitre 19 ? Flash Remoting ? version 0.1 17 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org public function Responder(result:Function, status:Function = null) Seul le premier paramètre est obligatoire : ? result : référence à la fonction gérant le retour du serveur en cas de réussite de l?appel. ? status : référence à la fonction gérant le retour du serveur en cas d?échec de l?appel. Dans le code suivant nous créons un objet Responder en passant deux fonctions succes et echec : // création de la connexion var connexion:NetConnection = new NetConnection (); // connexion à la passerelle amfphp connexion.connect ("http://localhost/echanges/gateway.php"); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } // création des fonctions de gestion de retour serveur function succes ( pRetour:* ):void { trace("retour serveur"); } function echec ( pErreur:* ):void { trace("echec de l'appel"); } // création d'un de gestionnaire de retour des appels var retourServeur:Responder = new Responder (succes, echec); Une fois l?objet Responder définit, nous pouvons appeler la méthode distante. A retenir Chapitre 19 ? Flash Remoting ? version 0.1 18 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les classes NetConnection et Socket permettent de se connecter à une passerelle remoting à l?aide de la méthode connect. ? En ActionScript 3, le format AMF3 est utilisé par défaut. ? Afin de gérer le retour du serveur, nous devons créer un objet Responder. ? Ce dernier nécessite deux fonctions qui seront déclenchées en cas de succès ou échec de l?appel. Appeler une méthode distante Afin d?appeler la méthode distante, nous utilisons la méthode call de l?objet NetConnection dont voici la signature : public function call(command:String, responder:Responder, ... arguments):void Les deux premiers paramètres sont obligatoires : ? command : la méthode du service à appeler. ? responder : l?objet Responder traitant les retours serveurs. ? arguments : les paramètres à transmettre à la méthode distante. Lorsque la méthode call est appelée, un paquet AMF est créé contenant le nom de la méthode à exécuter au sein du service ainsi que les données à transmettre. Dans le code suivant, nous passons le chemin complet de la méthode à appeler ainsi qu?une instance de la classe Responder pour gérer le retour du serveur : // création de la connexion var connexion:NetConnection = new NetConnection (); // connexion à la passerelle amfphp connexion.connect ("http://localhost/echanges/gateway.php"); // création des fonctions de gestion de retour serveur function succes ( pRetour:* ):void { trace("retour serveur"); } function echec ( pErreur:* ):void { trace("echec de l'appel"); } // création d'un objet de gestion de l'appel var retourServeur:Responder = new Responder (succes, echec); Chapitre 19 ? Flash Remoting ? version 0.1 19 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // appel de la méthode distante connexion.call ("org.bytearray.test.Echanges.premierAppel", retourServeur); En testant le code précédent, nous voyons que la fonction succes est exécutée. Attention, n?oubliez pas que Flash fonctionne de manière asynchrone. La méthode call de l?objet NetConnection ne renvoie donc aucune valeur. Seul l?objet Responder passé en paramètre permet de gérer le résultat de l?appel. Pour récupérer les données renvoyées par le serveur nous utilisons le paramètre pRetour de la fonction succes : function succes ( pRetour:* ):void { // affiche : cela fonctionne sans problème ! trace ( pRetour ); } Nous venons d?appeler notre première méthode distante par Flash Remoting en quelques lignes seulement. Souvenez-vous, lors du chapitre 14 intitulé Chargement et envoi de données nous devions nous assurer manuellement du décodage et de l?encodage UTF-8 afin de préserver les caractères spéciaux. Le format AMF encode les chaînes de caractères en UTF-8, et AMFPHP se charge automatiquement du décodage. Il n?est donc plus nécessaire de se soucier de l?encodage des caractères spéciaux avec Flash Remoting. Nous allons à présent modifier la méthode premierAppel afin que celle-ci accepte un paramètre $pMessage : <?php class Echanges { function Echanges ( ) { } function premierAppel ( $pMessage ) { return "vous avez dit : $pMessage ?"; } Chapitre 19 ? Flash Remoting ? version 0.1 20 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } ?> Lorsqu?un paramètre est ajouté à une méthode, l?explorateur de service nous donne la possibilité de passer dynamiquement les valeurs au sein d?un champ texte de saisie : Figure 19-9. Paramètre. Si nous testons à nouveau le code ActionScript précédent, nous remarquons que la fonction echec est déclenchée. La méthode premierAppel attend désormais un paramètre. En l?omettant, une erreur est levée que nous pouvons intercepter grâce à la fonction echec passée en paramètre à l?objet Responder. En ciblant la propriété description de l?objet retourné nous obtenons des informations liées à l?erreur en cours : function echec ( pErreur:* ):void { // affiche : Missing argument 1 for Echanges::premierAppel() trace( pErreur.description ) ; } Chapitre 19 ? Flash Remoting ? version 0.1 21 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Les informations renvoyées par AMFPHP nous permettent ainsi de déboguer l?application plus facilement. Ce type de mécanisme illustre la puissance de Flash Remoting en matière de déboguage. Dans le cas présent, nous apprenons de manière explicite que nous avons omis le paramètre attendu de la méthode distante premierAppel. Si nous itérons sur l?objet retourné nous découvrons de nouvelles propriétés : function echec ( pErreur:* ):void { /* affiche : details : C:\wamp\www\echanges\services\org\bytearray\test\Echanges.php level : Unknown error type description : Missing argument 1 for Echanges::premierAppel() line : 12 code : AMFPHP_RUNTIME_ERROR */ for ( var p:String in pErreur ) { trace( p + " : " + pErreur[p] ); } } Dans le code suivant, nous tentons d?appeler une méthode inexistante dû à une faute de frappe : connexion.call ("org.bytearray.test.Echanges.premierApel", retourServeur); La propriété description de l?objet retourné par le serveur nous indique qu?une telle méthode n?existe pas au sein du service : function echec ( pErreur:* ):void { // affiche : The method {premierApel} does not exist in class {Echanges}. trace( pErreur.description ); } De la même manière, si nous nous trompons de service : connexion.call ("org.bytearray.test.Echange.premierAppel", retourServeur); AMFPHP nous oriente à nouveau vers la cause de l?erreur : function echec ( pErreur:* ):void { // affiche : The class {Echange} could not be found under the class path {C:\wamp\www\echanges\services\/org/bytearray/test/Echange.php} trace( pErreur.description ); Chapitre 19 ? Flash Remoting ? version 0.1 22 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } Nous allons à présent nous intéresser aux différents types de données échangeables et découvrir l?intérêt de la sérialisation automatique assurée par Flash Remoting. A retenir ? La méthode call de l?objet NetConnection permet d?appeler une méthode distante. ? Si l?appel réussit, la fonction succes passée en paramètre à l?objet Responder est exécutée. ? Dans le cas contraire, la fonction echec est déclenchée. ? Flash Remoting gère l?encodage de caractères spéciaux automatiquement. Echanger des données primitives Comme nous l?avons expliqué précédemment, Flash Remoting se charge de la sérialisation et désérialisation des données. Afin de nous rendre compte de ce comportement, nous modifions la méthode premierAppel en la renommant echangesTypes : <?php class Echanges { function Echanges ( ) { } function echangeTypes ( $pDonnees ) { return gettype ( $pDonnees ); } } ?> Celle-ci accepte un paramètre et retourne désormais son type grâce à la méthode PHP gettype. Celle-ci est similaire à l?instruction typeof d?ActionScript 3. Cette méthode va nous permettre de savoir sous quel type les données envoyées depuis Flash arrivent coté PHP. Chapitre 19 ? Flash Remoting ? version 0.1 23 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Nous modifions les paramètres passés à la méthode call en spécifiant le nouveau nom de méthode ainsi que le valeur booléenne true : connexion.call ("org.bytearray.test.Echanges.echangeTypes", retourServeur, true); La fonction succes reçoit alors la chaîne boolean : function succes ( pRetour:* ):void { // affiche : boolean trace ( pRetour ); } Nous voyons que la passerelle AMFPHP a automatiquement conservé le type booléen entre ActionScript et PHP, on dit alors que les données échangées sont supportées. Mais que se passe t-il si AMFPHP ne trouve pas de type équivalent ? Dans ce cas, AMFPHP interprète le type de données et convertit les données en un type équivalent. Dans le code suivant, nous passons la valeur 5 de type int : connexion.call ("org.bytearray.Echanges.echangeTypes", retourServeur, 5); La méthode distante echangeTypes renvoie alors le type reçu, la fonction succes affiche les données retournées : function succes ( pRetour:* ):void { // affiche : double trace ( pRetour ); } Nous voyons que la passerelle AMFPHP a automatiquement convertit le type int en double, c'est-à-dire son équivalent PHP. La valeur 5 est envoyée au sein d?un paquet AMF à la méthode distante, AMFPHP désérialise automatiquement le paquet AMF et convertit le type int en un type compatible PHP. On dit alors que les données échangées sont interprétées. Le tableau ci-dessous regroupe les différents types primitifs supportés et interprétés par AMFPHP : Type ActionScript Type PHP Conversion automatique Version AMF Chapitre 19 ? Flash Remoting ? version 0.1 24 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org null NULL Oui AMF0 / AMF3 int double Oui AMF3 uint double Oui AMF3 Number double Oui AMF0 / AMF3 Boolean boolean Oui AMF0 / AMF3 String string Oui AMF0 / AMF3 Tableau 2. Tableau des types primitifs supportés et interprétés. Cette conversion automatique permet d?échanger des données primitives sans aucun travail de notre part. AMFPHP se charge de toute la sérialisation dans les deux sens, ce qui nous permet de nous concentrer sur d?autres parties plus importantes de l?application telles que l?interface ou autres. Sans Flash Remoting, nous aurions du sérialiser les données sous la forme d?une chaîne de caractères, puis désérialiser les données côté serveur manuellement. Grâce à AMFPHP, ce processus est simplement supprimé. Bien entendu, dans la plupart des applications dynamiques, nous n?échangeons pas de simples données comme celle-ci. Si nous souhaitons concilier l?avantage des deux technologies, nous pouvons faire transiter une chaîne de caractères compressée au sein d?AMF puis transformer celle-ci en objet XML au sein de Flash. Nous ajoutons une nouvelle méthode recupereMenu : <?php class Echanges { function Echanges ( ) { } function echangeTypes ( $pDonnees ) { Chapitre 19 ? Flash Remoting ? version 0.1 25 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org return gettype ( $pDonnees ); } function recupereMenu () { $menuXML = '<MENU> <RUBRIQUE titre = "Nouveautés" id="12"> <RUBRIQUE titre = "CD" id="487"/> <RUBRIQUE titre = "Vinyls" id="540"/> </RUBRIQUE> <RUBRIQUE titre = "Concerts" id="25"> <RUBRIQUE titre = "Funk" id="15"/> <RUBRIQUE titre = "Soul" id="58"/> </RUBRIQUE> </MENU>'; return $menuXML; } } ?> En appelant la méthode recupereMenu, nous recevons la chaîne de caractère que nous transformons aussitôt en objet XML : // création de la connexion var connexion:NetConnection = new NetConnection (); // connexion à la passerelle amfphp connexion.connect ("http://localhost/echanges/gateway.php"); // création des fonctions de gestion de retour serveur function succes ( pRetour:* ):void { try { // conversion de la chaîne en objet XML var menu:XML = new XML ( pRetour ); /* affiche : <RUBRIQUE titre="Concerts" id="25"> <RUBRIQUE titre="Funk" id="15"/> <RUBRIQUE titre="Soul" id="58"/> </RUBRIQUE> */ trace(menu.RUBRIQUE.(@id == 25) ); //affiche : Funk trace(menu..RUBRIQUE.(@id == 15).@titre ); } catch ( pErreur:Error ) { Chapitre 19 ? Flash Remoting ? version 0.1 26 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org trace( "erreur d'interprétation du flux XML"); } } function echec ( pErreur:* ):void { trace("echec de l'appel"); } // création d'un objet de gestion de l'appel var retourServeur:Responder = new Responder (succes, echec); // appel de la méthode recupereMenu distante, récupération du flux xml du menu connexion.call ("org.bytearray.test.Echanges.recupereMenu", retourServeur); En utilisant cette approche, nous conservons : ? La puissance de déboguage de Flash Remoting. ? L?appel de méthodes distantes directement depuis Flash. ? La simplicité du format XML. ? La puissance de la norme E4X. Même si la conversion de la chaîne en objet XML par ActionScript pourrait ralentir les performances de l?application si le flux XML devient important, cela resterait minime dans notre exemple. Il peut être nécessaire d?échanger des données plus complexes, nous allons voir que Flash Remoting va à nouveau nous faciliter les échanges. A retenir Chapitre 19 ? Flash Remoting ? version 0.1 27 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? Grâce à Flash Remoting les données primitives sont automatiquement sérialisées et désérialisées. ? Lorsqu?AMFPHP trouve un type correspondant, on dit que les données sont supportées. ? Dans le cas contraire, AMFPHP interprète les données en un type équivalent. ? Il est tout à fait possible de concilier un format de représentation de données tel XML avec Flash Remoting. ? Le compromis XML et AMF est une option élégante à prendre en considération. Echanger des données composites Dans la plupart des applications dynamiques, nous souhaitons généralement échanger des données plus complexes, comme des tableaux ou des objets associatifs. Voici le tableau des différents types composites supportés et interprétés par AMFPHP : Type ActionScript Type PHP Conversion automatique Version AMF Object associative array Oui AMF0 / AMF3 Array array Oui AMF0 / AMF3 XML (E4X) string Non AMF3 XMLDocument string Non AMF0 / AMF3 Date double Non AMF0 / AMF3 RecordSet Ressource MySQL Oui (Uniquement de MySQL vers Flash) AMF0 / AMF3 ByteArray ByteArray (classe AMFPHP) Oui AMF3 Chapitre 19 ? Flash Remoting ? version 0.1 28 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Tableau 3. Tableau des types composites supportés et interprétés. Dans le code suivant nous envoyons à la méthode distante un objet Date : connexion.call ("org.bytearray.test.Echanges.echangeTypes", retourServeur, new Date()); L?objet Date est transmis, AMFPHP ne trouvant pas de type équivalent convertit alors l?objet Date en timestamp Unix compatible. Ainsi, nous recevons côté PHP un double : function succes ( pRetour:* ):void { // affiche : double trace( pRetour ); } Très souvent, nous avons besoin d?envoyer au serveur un objet contenant différentes informations. Dans le code suivant, nous envoyons au serveur un tableau associatif contenant les informations liées à un utilisateur : // création d'un joueur fictif var joueur:Object = new Object(); joueur.nom = "Groove"; joueur.prenom = "Bob"; joueur.age = 29; joueur.ville = "Paris"; joueur.points = 35485; // appel de la méthode distante, le joueur est envoyé à la méthode distante connexion.call ("org.bytearray.test.Echanges.echangeTypes", retourServeur, joueur); Nous modifions la méthode distante en renvoyant simplement les données passées en paramètre : <?php class Echanges { function Echanges ( ) { } function echangeTypes ( $pDonnees ) { Chapitre 19 ? Flash Remoting ? version 0.1 29 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org return $pDonnees; } } ?> L?objet est retourné à Flash sous sa forme originale : function succes ( pRetour:* ):void { /* affiche : nom : Groove prenom : Bob age : 29 ville : Paris points : 35485 */ for ( var p:String in pRetour ) { trace( p, " : " + pRetour[p] ); } } Nous remarquons donc que la sérialisation automatique est opérée dans les deux sens et nous permet de gagner un temps précieux. Coté PHP nous accédons aux propriétés de l?objet de manière traditionnelle. Nous pouvons par exemple augmenter l?âge et le score du joueur passé en paramètre : <?php class Echanges { function Echanges ( ) { } function echangeTypes ( $pDonnees ) { $pDonnees["age"] += 10; $pDonnees["points"] += 500; return $pDonnees; } } ?> Chapitre 19 ? Flash Remoting ? version 0.1 30 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org L?objet est retourné modifié au lecteur Flash : function succes ( pRetour:* ):void { /* affiche : nom : Groove ville : Paris prenom : Bob age : 39 points : 35985 */ for ( var p:String in pRetour ) { trace( p, " : " + pRetour[p] ); } } Grâce au format AMF3 et la version 1.9 d?AMFPHP, il est possible d?échanger des objets de types ByteArray. Dans le code suivant, nous créons un tableau d?octets vierge, puis nous écrivons des données texte : // création d'un tableau d'octets vierge var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Voilà du texte encodé en binaire !"); L?instance de ByteArray est ensuite transmise à la méthode distante : // création d'un tableau d'octets vierge var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Voilà du texte encodé en binaire !"); // appel de la méthode distante, le ByteArray est envoyé au serveur connexion.call ("org.bytearray.test.Echanges.echangeTypes", retourServeur, fluxBinaire); Lorsque la fonction succes est exécutée, l?objet ByteArray retourné par le serveur est intact et les données préservées : function succes ( pRetour:* ):void { // affiche : true trace( pRetour is ByteArray ); // affiche : Voilà du texte encodé en binaire ! trace( pRetour.readUTFBytes ( pRetour.bytesAvailable ) ); } Chapitre 19 ? Flash Remoting ? version 0.1 31 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Bien que l?intérêt puisse paraître limité, cette fonctionnalité s?avère extrêmement puissante. Nous reviendrons très bientôt sur l?intérêt d?échanger des instances de ByteArray. Malheureusement, certains objets ne peuvent être sérialisés au format AMF, c?est le cas des objets de type DisplayObject. En modifiant la méthode echangeTypes nous pourrions penser pouvoir renvoyer l?objet graphique transmis : <?php class Echanges { function Echanges ( ) { } function echangeTypes ( $pDonnees ) { return $pDonnees; } } ?> Si nous tentons de passer une instance de MovieClip à cette même méthode : // création d'une instance de MovieClip var animation:MovieClip = new MovieClip(); // appel de la méthode distante, un objet graphique est passé à la méthode distante connexion.call ("org.bytearray.test.Echanges.echangeTypes", retourServeur, animation); La sérialisation AMF échoue, la méthode distante echangeTypes renvoie la valeur null : function succes ( pRetour:* ):void { // affiche : null trace( pRetour ); // affiche : true trace( pRetour == null ) } Chapitre 19 ? Flash Remoting ? version 0.1 32 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Cette limitation n?est pas due à la passerelle remoting AMFPHP mais au format AMF qui ne gère pas au jour d?aujourd?hui la sérialisation et désérialisation d?objets graphiques. Dans certains cas, nous pouvons avoir besoin de transmettre un flux XML. En passant un objet de type XML, celui-ci est alors aplati sous la forme d?une chaîne de caractères UTF-8 : // création d'un objet XML var donneesXML:XML = <SCORE><JOUEUR id='25' score='15888'/></SCORE>; // appel de la méthode echangeTypes distante, nous transmettons un objet XML connexion.call ("org.bytearray.test.Echanges.echangeTypes", retourServeur, donneesXML); Le flux XML revient sous la forme d?une chaîne de caractères : function succes ( pRetour:* ):void { // l'objet XML a été converti sous forme de chaîne de caractères trace( pRetour is String ); } AMFPHP préfère conserver une chaîne et nous laisser gérer la manipulation de l?arbre XML. A retenir ? Grâce à Flash Remoting les données composites sont automatiquement sérialisées et désérialisées. ? Lorsqu?AMFPHP trouve un type correspondant, on dit que les données sont supportées. ? Dans le cas contraire, AMFPHP interprète les données en un type équivalent. ? Les objets graphiques ne peuvent ne sont pas compatibles avec le format AMF. Echanger des données typées Au cas où nous souhaiterions échanger des types personnalisés avec la passerelle Remoting, AMFPHP intègre un mécanisme approprié. Une instance de classe personnalisée de type Utilisateur peut par exemple être définie côté Flash et transmise au service distant, en conservant côté PHP le type Utilisateur. De la même manière, une méthode distante peut renvoyer à Flash des instances de classes en assurant une conservation des types personnalisés. Chapitre 19 ? Flash Remoting ? version 0.1 33 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Pour plus d?informations, rendez vous à l?adresse suivante : http://amfphp.org/docs/classmapping.html Passons maintenant de la théorie à la pratique. Envoyer un email avec Flash Remoting Au cours du chapitre 14 intitulé Chargement et envoi de données nous avions développé un formulaire permettant d?envoyer un email depuis Flash. Nous allons reprendre l?application et remplacer les échanges réalisés à l?aide la classe URLLoader par un échange par Flash Remoting. La figure 19-10 illustre le formulaire : Figure 19-10. Formulaire d?envoi d?email. La classe de document suivante est associée : package org.bytearray.document { import flash.text.TextField; import flash.display.SimpleButton; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut Chapitre 19 ? Flash Remoting ? version 0.1 34 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; public function Document () { } } } Afin de pouvoir envoyer notre email, nous devons tout d?abord prévoir une méthode d?envoi. Pour cela nous ajoutons une méthode envoiMessage à notre service distant : <?php class Echanges { function echangeTypes ( $pDonnees ) { return $pDonnees; } function envoiMessage ( $pInfos ) { $destinaire = $pInfos["destinataire"]; $sujet = $pInfos["sujet"]; $message = $pInfos["message"]; return @mail ( $destinataire, $sujet, $message ); } } ?> La méthode envoiMessage reçoit un tableau associatif contenant les informations nécessaires et procède à l?envoi du message. La valeur renvoyée par la fonction PHP mail indiquant le succès ou l?échec de l?envoi est retournée à Flash. Afin d?appeler la méthode envoiMessage nous ajoutons les lignes suivantes à la classe de document définie précédemment : package org.bytearray.document Chapitre 19 ? Flash Remoting ? version 0.1 35 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.net.NetConnection; import flash.text.TextField; import flash.display.SimpleButton; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; private var connexion:NetConnection; public function Document () { //création de la connexion connexion = new NetConnection(); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } } } Puis nous ajoutons une propriété constante PASSERELLE contenant l?adresse de la passerelle et nous nous connectons à celle-ci : package org.bytearray.document { import flash.events.Event; Chapitre 19 ? Flash Remoting ? version 0.1 36 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.text.TextField; import flash.display.SimpleButton; import flash.net.NetConnection; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; private var connexion:NetConnection; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://localhost/echanges/gateway.php"; public function Document () { //création de la connexion connexion = new NetConnection(); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); // connexion à la passerelle connexion.connect ( Document.PASSERELLE ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } } } Nous ajoutons une instance de Responder afin de gérer les retours serveurs : package org.bytearray.document Chapitre 19 ? Flash Remoting ? version 0.1 37 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.net.Responder; import flash.text.TextField; import flash.display.SimpleButton; import flash.net.NetConnection; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; private var connexion:NetConnection; private var retourServeur:Responder; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://localhost/echanges/gateway.php"; public function Document () { //création de la connexion connexion = new NetConnection(); // création de l'objet de gestion des retours serveur retourServeur = new Responder ( succes, echec ); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, ecouteurCentralise ); connexion.addEventListener( IOErrorEvent.IO_ERROR, ecouteurCentralise ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, ecouteurCentralise ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, ecouteurCentralise ); // connexion à la passerelle connexion.connect ( Document.PASSERELLE ); } private function succes ( pRetour:* ):void { trace ( pRetour ); } private function echec ( pErreur:* ):void Chapitre 19 ? Flash Remoting ? version 0.1 38 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { trace ( pErreur ); } private function ecouteurCentralise ( pEvt:Event ):void { trace( pEvt ); } } } Puis nous appelons la méthode distante envoiMessage lorsque le bouton boutonEnvoi est cliqué : package org.bytearray.document { import flash.events.Event; import flash.events.MouseEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.net.Responder; import flash.text.TextField; import flash.display.SimpleButton; import flash.net.NetConnection; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public var destinataire:TextField; public var sujet:TextField; public var message:TextField; public var boutonEnvoi:SimpleButton; private var connexion:NetConnection; private var retourServeur:Responder; private var infos:Object; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://localhost/echanges/gateway.php"; public function Document () { //création de la connexion connexion = new NetConnection(); // création de l'objet de gestion des retours serveur Chapitre 19 ? Flash Remoting ? version 0.1 39 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org retourServeur = new Responder ( succes, echec ); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); // connexion à la passerelle connexion.connect ( Document.PASSERELLE ); boutonEnvoi.addEventListener ( MouseEvent.CLICK, envoiMail ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } private function succes ( pRetour:* ):void { trace ( pRetour ); } private function echec ( pErreur:* ):void { trace ( pErreur ); } private function envoiMail ( pEvt:MouseEvent ):void { infos = new Object(); infos.destinataire = destinataire.text; infos.sujet = sujet.text; infos.message = message.text; connexion.call ("org.bytearray.Echanges.envoiMessage", retourServeur, infos ); } } } Chapitre 19 ? Flash Remoting ? version 0.1 40 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org En testant le code précédent, la méthode écouteur succes est déclenchée et reçoit la valeur true du serveur indiquant que le mail a bien été envoyé. Afin de finaliser cet exemple, nous pouvons utiliser la classe OutilsFormulaire développée au cours du chapitre 14 et l?importer : import org.bytearray.outils.FormulaireOutils; Puis nous modifions la méthode envoiMail afin de tester la validité de l?adresse saisie : private function envoiMail ( pEvt:MouseEvent ):void { if ( FormulaireOutils.verifieEmail ( destinataire.text ) ) { infos = new Object(); infos.destinataire = destinataire.text; infos.sujet = sujet.text; infos.message = message.text; connexion.call ("org.bytearray.Echanges.envoiMessage", retourServeur, infos ); } else destinataire.text = "Email non valide !"; } Afin de valider l?envoi du message au sein de l?application nous ajoutons la condition suivante au sein de la méthode de retour succes : private function succes ( pRetour:* ):void { if ( pRetour ) message.text = "Message bien envoyé !"; else message.text = "Erreur d'envoi du message"; } Ainsi, nous travaillons de manière transparente avec le script serveur sans avoir à nous soucier de sérialiser et désérialiser les données envoyées et reçues. Exporter une image Nous avons vu précédemment qu?il était possible de transmettre une instance de ByteArray par Flash Remoting. Chapitre 19 ? Flash Remoting ? version 0.1 41 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Comme nous le verrons au cours du chapitre 21 intitulé ByteArray il est possible de générer en ActionScript 3 un flux binaire grâce à la classe ByteArray. Afin de pouvoir exploiter ce flux binaire, nous pouvons le transmettre au service distant afin que celui-ci le sauvegarde sur le serveur. Nous allons reprendre l?application de dessin développée au cours du chapitre 9 intitulé Etendre les classes natives et ajouter une fonctionnalité d?export de notre dessin sous la forme d?une image PNG. Nous définissons une classe de document associée : package org.bytearray.document { import flash.display.SimpleButton; import flash.display.Sprite; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { private var dessin:Sprite; private var stylo:Stylo; public var boutonEnvoi:SimpleButton; public function Document () { // création du conteneur de tracés vectoriels dessin = new Sprite(); // ajout du conteneur à la liste d'affichage addChild ( dessin ); // création du symbole stylo = new Stylo( .1 ); // passage du conteneur de tracés stylo.affecteToile ( dessin ); // ajout du symbole à la liste d'affichage addChild ( stylo ); // positionnement en x et y stylo.x = 250; stylo.y = 200; } } } Chapitre 19 ? Flash Remoting ? version 0.1 42 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Nous ajoutons un bouton boutonEnvoi, permettant d?exporter le dessin sous forme bitmap. La figure 19-11 illustre l?application : Figure 19-11. Application de dessin. Lors du clic bouton nous devons générer une image bitmap du dessin vectoriel, pour cela nous allons utiliser une classe d?encodage d?image EncodeurPNG. Nous écoutons l?événement MouseEvent.CLICK du bouton d?export : package org.bytearray.document { import flash.display.SimpleButton; import flash.display.Sprite; import flash.events.MouseEvent; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { private var dessin:Sprite; private var stylo:Stylo; private var renduBitmap:BitmapData; public function Document () Chapitre 19 ? Flash Remoting ? version 0.1 43 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { // création du conteneur de tracés vectoriels dessin = new Sprite(); // ajout du conteneur à la liste d'affichage addChild ( dessin ); // création du symbole stylo = new Stylo( .1 ); // passage du conteneur de tracés stylo.affecteToile ( dessin ); // ajout du symbole à la liste d'affichage addChild ( stylo ); // positionnement en x et y stylo.x = 250; stylo.y = 200; boutonEnvoi.addEventListener ( MouseEvent.CLICK, exportBitmap ); } private function exportBitmap ( pEvt:MouseEvent ):void { trace("export bitmap"); } } } Afin d?encoder notre dessin vectoriel en image PNG nous devons tout d?abord rendre sous forme bitmap les tracés vectoriels. Souvenez-vous, nous avons découvert lors du chapitre 12 intitulé Programmation bitmap qu?il était possible de rasteriser un élément vectoriel grâce à la méthode draw de la classe BitmapData. Nous modifions la classe de document afin d?intégrer une rasterisation du dessin vectoriel lorsque le bouton d?export est cliqué : package org.bytearray.document { import flash.display.BitmapData; import flash.display.SimpleButton; import flash.display.Sprite; import flash.events.MouseEvent; import flash.utils.ByteArray; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.encodage.images.EncodeurPNG; public class Document extends ApplicationDefaut Chapitre 19 ? Flash Remoting ? version 0.1 44 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { private var dessin:Sprite; private var stylo:Stylo; private var renduBitmap:BitmapData; public var boutonEnvoi:SimpleButton; public function Document () { // création du conteneur de tracés vectoriels dessin = new Sprite(); // ajout du conteneur à la liste d'affichage addChild ( dessin ); // création du symbole stylo = new Stylo( .1 ); // passage du conteneur de tracés stylo.affecteToile ( dessin ); // ajout du symbole à la liste d'affichage //addChild ( stylo ); // positionnement en x et y stylo.x = 250; stylo.y = 200; boutonEnvoi.addEventListener ( MouseEvent.CLICK, exportBitmap ); } private function exportBitmap ( pEvt:MouseEvent ):void { // création d'une image bitmap vierge renduBitmap = new BitmapData ( stage.stageWidth, stage.stageHeight ); // rasterisation des tracés renduBitmap.draw ( dessin ); // encodage de l'image bitmap au format PNG var fluxBinaire:ByteArray = EncodeurPNG.encode ( renduBitmap ); // affiche : 1488 trace( fluxBinaire.length ); } } } La variable fluxBinaire représente un objet ByteArray contenant l?image encodée au format PNG, nous devons à présent transmettre le flux de l?image. Nous ajoutons une nouvelle méthode sauveImage à notre service distant : Chapitre 19 ? Flash Remoting ? version 0.1 45 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org <?php class Echanges { function Echanges ( ) { } function echangeTypes ( $pDonnees ) { return $pDonnees; } function sauveImage ( $pFluxImage ) { //le ByteArray est placé au sein de la propriété data de l'objet reçu $flux = $pFluxImage->data; // nous décompressons le flux compressé coté Flash $flux = gzuncompress($flux); $nomImage = "capture.png"; // sauvegarde de l'image $fp = @fopen("./../../../../images/".$nomImage, 'wb'); $ecriture = @fwrite($fp, $flux); @fclose($fp); return $ecriture !== FALSE; } } ?> La méthode sauveImage reçoit le flux binaire en paramètre sous ma forme d?une instance de la classe ByteArray intégrée à AMFPHP. Le flux est accessible par la propriété data de l?instance de ByteArray, nous sauvegardons le flux à l?aide des fonctions d?écriture PHP fopen et fwrite. Attention, veillez à créer un répertoire nommé images, afin d?accueillir les futures images sauvées. Dans notre exemple, ce dernier est placé au même niveau que le répertoire services. Nous modifions la méthode exportBitmap afin de transmettre l?image à la méthode sauveImage : package org.bytearray.document Chapitre 19 ? Flash Remoting ? version 0.1 46 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { import flash.display.BitmapData; import flash.display.SimpleButton; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.utils.ByteArray; import flash.net.Responder; import flash.net.NetConnection; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.encodage.images.EncodeurPNG; public class Document extends ApplicationDefaut { private var dessin:Sprite; private var stylo:Stylo; private var renduBitmap:BitmapData; public var boutonEnvoi:SimpleButton; private var connexion:NetConnection; private var retourServeur:Responder; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://localhost/echanges/gateway.php"; public function Document () { //création de la connexion connexion = new NetConnection(); // création de l'objet de gestion des retours serveur retourServeur = new Responder ( succes, echec ); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); // connexion à la passerelle connexion.connect ( Document.PASSERELLE ); // création du conteneur de tracés vectoriels dessin = new Sprite(); // ajout du conteneur à la liste d'affichage addChild ( dessin ); Chapitre 19 ? Flash Remoting ? version 0.1 47 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // création du symbole stylo = new Stylo( .1 ); // passage du conteneur de tracés stylo.affecteToile ( dessin ); // ajout du symbole à la liste d'affichage addChild ( stylo ); // positionnement en x et y stylo.x = 250; stylo.y = 200; boutonEnvoi.addEventListener ( MouseEvent.CLICK, exportBitmap ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } private function succes ( pRetour:* ):void { if ( pRetour ) trace("image sauvegardée !"); else trace("erreur d'enregistrement"); } private function echec ( pErreur:* ):void { trace( pErreur ); } private function exportBitmap ( pEvt:MouseEvent ):void { // création d'une image bitmap vierge renduBitmap = new BitmapData ( stage.stageWidth, stage.stageHeight ); // rasterisation des traçés renduBitmap.draw ( dessin ); // encodage de l'image bitmap au format PNG var fluxBinaire:ByteArray = EncodeurPNG.encode ( renduBitmap ); // compression zlib du flux fluxBinaire.compress(); // transmission du ByteArray par Flash Remoting connexion.call ("org.bytearray.test.Echanges.sauveImage", retourServeur, fluxBinaire ); Chapitre 19 ? Flash Remoting ? version 0.1 48 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Nous dessinons quelques tracés, puis nous cliquons sur le bouton d?export comme l?illustre la figure 19-12 : Figure 19-12. Dessin. Voici les différentes étapes lorsque nous cliquons sur le bouton d?export : ? Les tracés vectoriels sont rasterisés sous la forme d?un objet BitmapData. ? Une image PNG est générée à l?aide des pixels accessibles depuis l?objet BitmapData. ? Le flux de l?image est envoyé au service distant qui se charge de la sauver sur le serveur. Lorsque la méthode succes est déclenchée, l?image est sauvée sur le serveur. En accédant au répertoire images nous découvrons l?image sauvegardée comme l?illustre la figure 19-13 : Chapitre 19 ? Flash Remoting ? version 0.1 49 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 19-13. Image sauvegardée. L?image pourrait aussi être sauvée directement en base de données au sein d?un champ de type BLOB. L?objet ByteArray pourrait ainsi être récupéré plus tard et affiché grâce à la méthode loadBytes de l?objet Loader. Il serait aussi envisageable de transmettre uniquement les pixels de l?image à l?aide de la méthode getPixels de la classe BitmapData. Puis de générer n?importe quel type d?image côté serveur à l?aide d?une librairie spécifique telle GD ou autres. Se connecter à une base de données Flash Remoting prend tout son sens lors de la manipulation d?une base de données. Nous allons intégrer Flash Remoting au menu développé au cours du chapitre 7 intitulé Interactivité. Pour cela nous devons définir une base de données, nous utiliserons dans cet exemple une base de données MySQL. Figure 19-14. Base de donnée MySQL. Nous créons une base de données intitulée maBase puis nous créons une table associée menu. Afin de créer celle-ci nous pouvons exécuter la requête MySQL suivante : -- -- Structure de la table `menu` -- CREATE TABLE `menu` ( `id` int(11) NOT NULL auto_increment, `intitule` varchar(30) NOT NULL default '', `couleur` int(11) NOT NULL default '0', Chapitre 19 ? Flash Remoting ? version 0.1 50 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ; -- -- Contenu de la table `menu` -- INSERT INTO `menu` (`id`, `intitule`, `couleur`) VALUES (1, 'Accueil', 16737536), (2, 'Nouveautés', 16737536), (3, 'Photos', 16737536), (4, 'Liens', 16737536), (5, 'Contact', 16737536); Quelques données sont présentes dans la table menu : Figure 19-15. Données de la table menu. Avant de récupérer les données de la table, notre service distant doit au préalable se connecter à la base MySQL. Nous ajoutons la connexion à la base au sein du constructeur du service distant : <?php class Echanges { function Echanges ( ) { // connexion au serveur MySQL mysql_connect ("localhost", "thibault", "20061982"); // sélection de la base mysql_select_db ("maBase"); } } ?> Puis nous ajoutons une méthode recupereMenu : <?php class Echanges Chapitre 19 ? Flash Remoting ? version 0.1 51 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { function Echanges ( ) { // connexion au serveur MySQL mysql_connect ("localhost", "bobgroove", "20061982"); // sélection de la base mysql_select_db ("maBase"); } function recupereMenu ( ) { return mysql_query ("SELECT * FROM menu"); } } ?> En nous rendant auprès de l?explorateur de services nous pouvons tester directement la méthode recupereMenu. A l?aide de l?onglet RecordSet view voyons directement au sein d?une liste le résultat de notre requête. La figure 19-16 illustre le résultat : Chapitre 19 ? Flash Remoting ? version 0.1 52 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 19-16. Aperçu en direct du résultat de la requête. Chaque champ de colonne correspond au champ de la table menu, grâce à ce mécanisme les données sont présentées en une seule ligne de code PHP. Dans un nouveau document Flash nous associons la classe de document suivante : package org.bytearray.document { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.net.Responder; import flash.net.NetConnection; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { private var connexion:NetConnection; Chapitre 19 ? Flash Remoting ? version 0.1 53 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org private var retourServeur:Responder; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://localhost/echanges/gateway.php"; public function Document () { //création de la connexion connexion = new NetConnection(); // création de l'objet de gestion des retours serveur retourServeur = new Responder ( succes, echec ); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); // connexion à la passerelle connexion.connect ( Document.PASSERELLE ); // appel de la méthode distante recupereMenu connexion.call ("org.bytearray.test.Echanges.recupereMenu", retourServeur ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } private function succes ( pRetour:* ):void { // affiche : [object Object] trace( pRetour ); } private function echec ( pErreur:* ):void { trace( pErreur.description ); } } Chapitre 19 ? Flash Remoting ? version 0.1 54 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } Dès l?initialisation de l?application nous appelons la méthode distante recupereMenu. AMFPHP retourne les données issues de la requête MySQL sous la forme d?un objet couramment appelé jeu d?enregistrements (RecordSet). Un objet RecordSet possède une propriété serverInfo contenant les propriétés suivantes : ? totalCount : le nombre d?enregistrements renvoyés. ? columnNames : un tableau contenant le nom des champs de la table. ? initialData : un tableau de tableaux, contenant les données de la table. ? Id : identifiant de la session en cours créée par AMFPHP. ? Version : la version de l?objet RecordSet. ? cursor : point de départ de la lecture des données. ? serviceName: nom du service distant. Si nous itérons au sein de l?objet serverInfo nous découvrons chacune des propriétés et leur valeurs : private function succes ( pRetour:* ):void { /* affiche : serviceName : PageAbleResult columnNames : id,intitule,couleur id : 952b03b6b26e39ff52418985866801ab initialData : 1,Accueil,16737536,2,Nouveautés,16737536,3,Photos,16737536,4,Liens,16737536,5,C ontact,16737536 totalCount : 5 version : 1 cursor : 1 */ for ( var p in pRetour.serverInfo ) { trace( p, " : " + pRetour.serverInfo[p] ); } } En utilisant AMFPHP à l?aide du framework Flex ou AIR, les ressources MySQL sont converties sous la forme d?objet ArrayCollection permettant un accès facilité aux données. Chapitre 19 ? Flash Remoting ? version 0.1 55 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Au sein de Flash CS3, aucune classe n?est prévue pour gérer l?interprétation des RecordSet, nous allons donc devoir développer une petite fonction de reorganisation des données. Nous ajoutons une méthode conversion : private function conversion ( pSource:Object ):Array { var donnees:Array = new Array(); var element:Object; for ( var p:String in pSource.initialData ) { element = new Object(); for ( var q:String in pSource.columnNames ) { element[pSource.columnNames[q]] = pSource.initialData[p][q]; } donnees.push ( element ); } return donnees; } Lorsque les données sont chargées nous construisons un tableau d?objets à l?aide de la méthode conversion : private function succes ( pRetour:* ):void { // le RecordSet est converti en tableau d'objets var donnees:Array = conversion ( pRetour.serverInfo ); } L?idéal étant d?isoler cette méthode afin de pouvoir la réutiliser à tout moment, dans n?importe quel projet. Nous pourrions imaginer une méthode conversion au sein d?une classe OutilsRemoting. Nous allons à présent intégrer le menu développé au cours du chapitre 7. Assurez vous d?avoir bien importé le bouton que nous avions créé associé à la classe Bouton : package org.bytearray.document { Chapitre 19 ? Flash Remoting ? version 0.1 56 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.display.Sprite; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.events.MouseEvent; import flash.net.Responder; import flash.net.NetConnection; import fl.transitions.Tween; import fl.transitions.easing.Elastic; import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { private var connexion:NetConnection; private var retourServeur:Responder; private var conteneurMenu:Sprite; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://localhost/echanges/gateway.php"; public function Document () { conteneurMenu = new Sprite(); conteneurMenu.x = 140; conteneurMenu.y = 120; addChild ( conteneurMenu ); conteneurMenu.addEventListener ( MouseEvent.ROLL_OVER, survolBouton, true ); conteneurMenu.addEventListener ( MouseEvent.ROLL_OUT, quitteBouton, true ); //création de la connexion connexion = new NetConnection(); // création de l'objet de gestion des retours serveur retourServeur = new Responder ( succes, echec ); // écoute des différents événements connexion.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); connexion.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); connexion.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); connexion.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); // connexion à la passerelle connexion.connect ( Document.PASSERELLE ); // appel de la méthode distante recupereMenu Chapitre 19 ? Flash Remoting ? version 0.1 57 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org connexion.call ("org.bytearray.test.Echanges.recupereMenu", retourServeur ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } private function succes ( pRetour:* ):void { // le RecordSet est converti en tableau d'objets var donnees:Array = filtre ( pRetour.serverInfo ); var lng:int = donnees.length; var monBouton:Bouton; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // instanciation du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = donnees[i].intitule; // disposition des occurrences monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, angle * (i+1), 2, true ); // on crée un objet Tween pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); } } private function echec ( pErreur:* ):void { trace( pErreur.description ); Chapitre 19 ? Flash Remoting ? version 0.1 58 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } function survolBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo (1.1, 2); } function quitteBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo (1, 2); } private function filtre ( pSource:Object ):Array { var donnees:Array = new Array(); var element:Object; for ( var p:String in pSource.initialData ) { element = new Object(); for ( var q:String in pSource.columnNames ) { element[pSource.columnNames[q]] = pSource.initialData[p][q]; } donnees.push ( element ); } return donnees; } } } En testant le code précédent, nous obtenons un menu dynamique connecté à notre base de données. La figure 19-17 illustre le résultat : Chapitre 19 ? Flash Remoting ? version 0.1 59 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 19-17. Menu dynamique connecté. Nous avons un champ de la base inutilisé pour le moment au sein de notre menu. Nous allons remédier à cela en liant la couleur de chaque bouton au champ couleur associé : private function succes ( pRetour:* ):void { // le RecordSet est converti en tableau d'objets var donnees:Array = filtre ( pRetour.serverInfo ); var lng:int = donnees.length; var monBouton:Bouton; var transformationCouleur:ColorTransform; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // instanciation du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = donnees[i].intitule; Chapitre 19 ? Flash Remoting ? version 0.1 60 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // recupération de l'objet de transformation de couleur transformationCouleur = monBouton.fondBouton.transform.colorTransform; // affectation d'une couleur dynamique transformationCouleur.color = donnees[i].couleur; // mise à jour de la couleur monBouton.fondBouton.transform.colorTransform = transformationCouleur; // disposition des occurrences monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, angle * (i+1), 2, true ); // on crée un objet Tween pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); } } En modifiant les champs couleur de chaque rubrique nous obtenons le résultat illustré par la figure 19-18 : Figure 19-18. Menu connecté avec couleurs dynamiques. Chapitre 19 ? Flash Remoting ? version 0.1 61 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Grâce à Flash Remoting, l?échange de données est réellement simplifié, nous pourrions ajouter de nombreuses fonctionnalités à notre menu. A vous de développer le gestionnaire d?administration de ce menu à l?aide de méthodes de mise à jour de la base ! A retenir ? Flash Remoting permet de retourner directement des ressources MySQL à Flash. ? La ressource MySQL est convertie en un objet appelé RecordSet. ? Dans Flash CS3, cet objet n?est pas exploitable directement, nous devons réorganiser les données manuellement. ? Pour cela, un script peut être developpé et réutilisé pour d?autres projets Flash Remoting. Sécurité Une fois le développement de notre service distant terminé et notre application déployée, nous devons impérativement supprimer l?explorateur de services. Cela évite de laisser à disposition l?accès aux méthodes distantes auprès d?une personne mal intentionnée. Pour supprimer l?explorateur de services, nous supprimons le répertoire browser. Garder à l?esprit que les paquets AMF échangés peuvent être décodés très facilement. Comme nous l?avons vu précédemment, chaque paquet AMF contient de nombreuses informations. Parmi celles-ci nous retrouvons le nom de la méthode distante à exécuter, l?adresse de la passerelle ainsi que les données à transmettre. De nombreux logiciels de déboguage tels ServiceCapture ou Charles permettent de lire ces informations. Ces derniers sont disponibles à l?adresse suivante : ? Service Capture : http://kevinlangdon.com/serviceCapture/ ? Charles : http://www.xk72.com/charles/ Une personne mal intentionnée pourrait être tentée de recupérer l?adresse de notre passerelle AMFPHP et de s?y connecter en appelant les méthodes distantes affichées par un logiciel de capture de paquets AMF tel Service Capture. Chapitre 19 ? Flash Remoting ? version 0.1 62 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de supprimer les éventuels messages émis par la méthode trace de la classe NetDebug, nous pouvons activer le mode « production » d?AMFPHP. Pour activer cette fonctionnalité il convient de passer la constante PRODUCTION_SERVER à true au sein du fichier gateway.php : define("PRODUCTION_SERVER", true); Si l?application Flash tentant d?appeler les méthodes de notre service est hébergée sur un autre serveur, l?appel de la méthode call de l?objet NetConnection entraîne une erreur de sécurité. Attention, si nous avons placé sur notre serveur un fichier de régulation autorisant tous les domaines, l?appel réussira et rendra notre service vulnérable. Le risque peut donc venir d?une personne se connectant à notre service distant depuis l?environnement auteur de Flash, car aucune restriction de sécurité n?est appliquée dans ce cas. Les versions 1.9 et ultérieures d?AMFPHP intègrent une nouvelle fonctionnalité permettant d?interdire toute connexion au service distant depuis le lecteur Flash autonome. L?activation de la constante PRODUCTION_SERVER active automatiquement ce contrôle et améliore la securité de notre service distant. Pour une sécurité optimale nous pouvons utiliser la méthode addHeader de la classe NetConnection afin d?authentifier l?utilisateur en cours au sein d?AMFPHP. Pour plus d?informations à ce sujet, rendez-vous à l?adresse suivante : http://www.amfphp.org/docs/authenticate.html A retenir ? Une fois l?application Flash Remoting en production il faut impérativement supprimer l?explorateur de services. ? Il est fortement recommandé d?activer le mode « production » d?AMFPHP grâce à la constante PRODUCTION_SERVER du fichier gateway.php. La classe Service Bien que Flash Remoting s?avère extrêmement pratique, l?utilisation de la classe NetConnection s?avère peu souple. La méthode distante à appeler doit être passée sous forme de chaîne de caractères à la méthode call ce qui s?avère peu pratique. Chapitre 19 ? Flash Remoting ? version 0.1 63 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Nous allons développer dans la partie suivante, une classe Service permettant de représenter le service distant, comme si ce dernier était un objet ActionScript. En d?autres termes, la classe Service nous permettra de représenter sous forme d?objet ActionScript le service distant. Pour appeler la méthode distante recupereMenu, nous écrirons : // création du service coté Flash var monService:Service = new Service(); // appel de la méthode distante monService.recupereMenu(); Tous les mécanismes internes liés à l?objet NetConnection seront totalement transparents, rendant le développement d?applications Flash Remoting encore plus simple. Au sein d?un paquetage org.bytearray.remoting nous définissons la classe Service suivante : package org.bytearray.remoting { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.Event; import flash.utils.Proxy; import flash.utils.flash_proxy; public dynamic class Service extends Proxy implements IEventDispatcher { private var diffuseur:EventDispatcher; public function Service () { diffuseur = new EventDispatcher(); } override flash_proxy function hasProperty(name:*):Boolean { return false; } override flash_proxy function getProperty(name:*):* { return undefined; } public function toString ( ):String Chapitre 19 ? Flash Remoting ? version 0.1 64 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { return "[object Service]"; } public function addEventListener( type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false ):void { diffuseur.addEventListener( type, listener, useCapture, priority, useWeakReference ); } public function dispatchEvent( event:Event ):Boolean { return diffuseur.dispatchEvent( event ); } public function hasEventListener( type:String ):Boolean { return diffuseur.hasEventListener( type ); } public function removeEventListener( type:String, listener:Function, useCapture:Boolean=false ):void { diffuseur.removeEventListener( type, listener, useCapture ); } public function willTrigger( type:String ):Boolean { return diffuseur.willTrigger( type ); } } } Dans un premier temps nous remarquons que la classe Service étend la classe flash.utils.Proxy et permet donc d?intercepter l?appel de méthodes inexistantes. Le code suivant illustre le concept : import org.bytearray.remoting.Service; var monService:Service = new Service(); monService.methodeInexistante(); En testant le code précédent, l?erreur suivante est levée à l?exécution : Error: Error #2090: La classe Proxy ne met pas en oeuvre callProperty. Elle doit être remplacée par une sous-classe. Afin de pouvoir intercepter les méthodes ou appels de propriétés nous devons définir une méthode callProperty surchargeante : override flash_proxy function callProperty ( nomMethode:*, ...parametres:* ):* { Chapitre 19 ? Flash Remoting ? version 0.1 65 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org trace ( nomMethode ); return null; } Lorsqu?une méthode est appelée sur l?instance de Service, la méthode callProperty est exécutée et renvoie en paramètre le nom de la propriété référencée. Rappelez-vous qu?une méthode est considérée dans le modèle objet d?ActionScript telle une fonction référencée par une propriété. La fonction s?ex écutant dans le contexte de l?instance. C?est la raison pour laquelle la méthode interceptant les appels est appelée callProperty. En appelant à nouveau une méthode inexistante, nous voyons que la méthode interne callProperty est exécutée et affiche le nom de la méthode appelée : import org.bytearray.remoting.Service; var monService:Service = new Service(); // affiche : methodeInexistante monService.methodeInexistante(); Nous allons profiter de ce comportement pour capturer le nom de la méthode et déléguer son appel auprès de l?objet NetConnection. Ainsi, pour appeler la méthode distante envoiMessage nous n?écrirons plus le code peu digeste suivant : connexion.call ("org.bytearray.Echanges.envoiMessage", retourServeur, infos ); Nous préférerons l?écriture simplifiée suivante : monService.envoiMessage( infos ); Pour mettre en place ce mécanisme, un objet NetConnection est enveloppé au sein de l?objet Service afin de lui déléguer les méthodes appelées : package org.bytearray.remoting { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.Event; import flash.events.NetStatusEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.net.NetConnection; Chapitre 19 ? Flash Remoting ? version 0.1 66 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.utils.Proxy; import flash.utils.flash_proxy; public dynamic class Service extends Proxy implements IEventDispatcher { private var diffuseur:EventDispatcher; private var connection:NetConnection; private var cheminService:String; private var passerelle:String; public function Service ( pService:String, pPasserelle:String, pEncodage:int=0 ) { diffuseur = new EventDispatcher(); connection = new NetConnection(); connection.objectEncoding = pEncodage; connection.client = this; cheminService = pService; passerelle = pPasserelle; connection.addEventListener( NetStatusEvent.NET_STATUS, ecouteurCentralise ); connection.addEventListener( IOErrorEvent.IO_ERROR, ecouteurCentralise ); connection.addEventListener( SecurityErrorEvent.SECURITY_ERROR, ecouteurCentralise ); connection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, ecouteurCentralise ); connection.connect( passerelle ); } private function ecouteurCentralise ( pEvt:Event ):void { diffuseur.dispatchEvent ( pEvt ); } override flash_proxy function callProperty ( nomMethode:*, ...parametres:* ):* { trace ( nomMethode ); return null; } override flash_proxy function hasProperty(name:*):Boolean { return false; } Chapitre 19 ? Flash Remoting ? version 0.1 67 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org override flash_proxy function getProperty(name:*):* { return undefined; } public function toString ( ):String { return "[object Service]"; } public function addEventListener( type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false ):void { diffuseur.addEventListener( type, listener, useCapture, priority, useWeakReference ); } public function dispatchEvent( event:Event ):Boolean { return diffuseur.dispatchEvent( event ); } public function hasEventListener( type:String ):Boolean { return diffuseur.hasEventListener( type ); } public function removeEventListener( type:String, listener:Function, useCapture:Boolean=false ):void { diffuseur.removeEventListener( type, listener, useCapture ); } public function willTrigger( type:String ):Boolean { return diffuseur.willTrigger( type ); } } } Nous instancions la classe Service en passant les paramètres nécessaires : import org.bytearray.remoting.Service; // création de la connexion var monService:Service = new Service( "org.bytearray.test.Echanges", "http://localhost/echanges/gateway.php", ObjectEncoding.AMF3 ); L?instance de la classe Service doit à présent appeler la méthode distante grâce à l?objet NetConnection interne. La classe AppelProcedure se charge d?appeler la méthode distante : Chapitre 19 ? Flash Remoting ? version 0.1 68 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org package org.bytearray.remoting { import flash.events.EventDispatcher; import flash.net.NetConnection; import flash.net.Responder; public final class ProcedureAppel extends EventDispatcher { private var recepteur:Responder; private var connexion:NetConnection; private var requete:String; private var parametresFinal:Array; private var parametres:Array; public function ProcedureAppel( pConnection:NetConnection, pRequete:String, pParametres:Array ) { recepteur = new Responder ( succes, echec ); connexion = pConnection; requete = pRequete; parametres = pParametres; } private function succes ( pEvt:Object ):void { } private function echec ( pEvt:Object ):void { } internal function appel ():void { parametresFinal = new Array( requete, recepteur ); connexion.call.apply ( connexion, parametresFinal.concat(parametres) ); } } } Afin que la procédure renseigne à notre service le résultat du serveur, nous définissons deux classes événementielles permettant de faire transiter les résultats. La classe EvenementResultat est définie au sein du paquetage org.bytearray.remoting.evenements : package org.bytearray.remoting.evenements { import flash.events.Event; Chapitre 19 ? Flash Remoting ? version 0.1 69 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org public final class EvenementResultat extends Event { public var resultat:Object; public static const RESULTAT:String = "resultat"; public function EvenementResultat (pType:String, pDonnees:Object) { super (pType, false, false); resultat = pDonnees; } } } Nous définissons une classe EvenementErreur au sein du même paquetage : package org.bytearray.remoting.evenements { import flash.events.Event; public final class EvenementErreur extends Event { public var erreur:Object; public static const ERREUR:String = "erreur"; public function EvenementErreur (pType:String, pFault:Object) { super (pType, false, false); erreur = pFault; } } } La classe ProcedureAppel est modifiée afin de diffuser deux événements EvenementResultat.RESULTAT et EvenementErreur.ERREUR : package org.bytearray.remoting { import flash.events.EventDispatcher; import flash.net.NetConnection; import flash.net.Responder; import org.bytearray.remoting.evenements.EvenementResultat; import org.bytearray.remoting.evenements.EvenementErreur; public final class ProcedureAppel extends EventDispatcher { Chapitre 19 ? Flash Remoting ? version 0.1 70 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org private var recepteur:Responder; private var connexion:NetConnection; private var requete:String; private var parametresFinal:Array; private var parametres:Array; public function ProcedureAppel( pConnection:NetConnection, pRequete:String, pParametres:Array ) { recepteur = new Responder ( succes, echec ); connexion = pConnection; requete = pRequete; parametres = pParametres; } private function succes ( pEvt:Object ):void { dispatchEvent ( new EvenementResultat ( EvenementResultat.RESULTAT, pEvt ) ); } private function echec ( pEvt:Object ):void { dispatchEvent ( new EvenementErreur ( EvenementErreur.ERREUR, pEvt ) ); } internal function appel ():void { parametresFinal = new Array( requete, recepteur ); connexion.call.apply ( connexion, parametresFinal.concat(parametres) ); } } } La classe Service instancie donc un objet ProcedureAppel à chaque appel de méthode et appel la méthode appel : override flash_proxy function callProperty ( nomMethode:*, ...parametres:* ):* { appelEnCours = cheminService + "." + nomMethode; procedureAppel = new ProcedureAppel ( connection, appelEnCours, parametres ); procedureAppel.appel(); return procedureAppel; Chapitre 19 ? Flash Remoting ? version 0.1 71 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } Lorsque nous appelons une méthode de l?objet Service, celui-ci délègue l?appel à l?objet NetConnection interne, la méthode est appelée grâce à l?instance de ProcedureAppel. Nous retournons celle-ci afin de pouvoir écouteur l?événement EvenementResultat.RESULTAT et EvenementErreur.ERREUR. Nous pouvons à présent intégrer la classe Service à notre menu dynamique créé auparavant : package org.bytearray.document { import flash.display.Sprite; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.events.MouseEvent; import flash.geom.ColorTransform; import flash.net.Responder; import flash.net.NetConnection; import flash.net.ObjectEncoding; import fl.transitions.Tween; import fl.transitions.easing.Elastic; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.remoting.Service; import org.bytearray.remoting.ProcedureAppel; import org.bytearray.remoting.evenements.EvenementResultat; import org.bytearray.remoting.evenements.EvenementErreur; public class Document extends ApplicationDefaut { private var service:Service; private var conteneurMenu:Sprite; // adresse de la passerelle AMFPHP private static const PASSERELLE:String = "http://www.bytearray.org/tmp/echanges/gateway.php"; private static const SERVICE_DISTANT:String = "org.bytearray.test.Echanges"; public function Document () { conteneurMenu = new Sprite(); conteneurMenu.x = 140; conteneurMenu.y = 120; addChild ( conteneurMenu ); Chapitre 19 ? Flash Remoting ? version 0.1 72 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org conteneurMenu.addEventListener ( MouseEvent.ROLL_OVER, survolBouton, true ); conteneurMenu.addEventListener ( MouseEvent.ROLL_OUT, quitteBouton, true ); //création de la connexion service = new Service( Document.PASSERELLE, Document.SERVICE_DISTANT, ObjectEncoding.AMF3 ); // appel de la méthode distante var procedureAppel:ProcedureAppel = service.recupereMenu(); // écoute du retour du serveur procedureAppel.addEventListener ( EvenementResultat.RESULTAT, succes ); procedureAppel.addEventListener ( EvenementErreur.ERREUR, echec ); // écoute des différents événements service.addEventListener( NetStatusEvent.NET_STATUS, erreurConnexion ); service.addEventListener( IOErrorEvent.IO_ERROR, erreurConnexion ); service.addEventListener( SecurityErrorEvent.SECURITY_ERROR, erreurConnexion ); service.addEventListener( AsyncErrorEvent.ASYNC_ERROR, erreurConnexion ); } private function erreurConnexion ( pEvt:Event ):void { trace( pEvt ); } private function succes ( pRetour:EvenementResultat ):void { // le RecordSet est converti en tableau d'objets var donnees:Array = filtre ( pRetour.resultat.serverInfo ); var lng:int = donnees.length; var monBouton:Bouton; var transformationCouleur:ColorTransform; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // instanciation du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants Chapitre 19 ? Flash Remoting ? version 0.1 73 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = donnees[i].intitule; // recupération de l'objet de transformation de couleur transformationCouleur = monBouton.fondBouton.transform.colorTransform; // affectation d'une couleur dynamique transformationCouleur.color = donnees[i].couleur; // mise à jour de la couleur monBouton.fondBouton.transform.colorTransform = transformationCouleur; // disposition des occurrences monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, angle * (i+1), 2, true ); // on crée un objet Tween pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); } } private function echec ( pErreur:EvenementErreur ):void { trace( pErreur.erreur.description ); } function survolBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo (1.1, 2); } function quitteBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo (1, 2); } private function filtre ( pSource:Object ):Array { Chapitre 19 ? Flash Remoting ? version 0.1 74 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org var donnees:Array = new Array(); var element:Object; for ( var p:String in pSource.initialData ) { element = new Object(); for ( var q:String in pSource.columnNames ) { element[pSource.columnNames[q]] = pSource.initialData[p][q]; } donnees.push ( element ); } return donnees; } } } Désormais les écouteurs de l?objet ProcedureAppel reçoivent un objet événementiel de type EvenementResultat contenant les données au sein de la propriété resultat. De la même manière si une erreur lors de l?appel est détectée, l?objet ProcedureAppel diffuse un événement EvenementErreur contenant les informations liées à l?erreur au sein de sa propriété erreur. La classe Service peut ainsi être réutilisée dans n?importe quel projet nécessitant une connexion Flash Remoting optimisée. Cet exemple illustre une des limitations de l?héritage et met en avant la notion de composition. A retenir ? La classe Proxy permet d?intercepter l?accès à une propriété. ? Grâce à la composition, nous déléguons les méthodes appelées auprès de l?instance de la classe Service à l?objet NetConnection interne. Chapitre 20 ? ByteArray ? version 0.1.1 1 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org 20 ByteArray LE CODAGE BINAIRE.......................................................................................... 1 POSITION ET POIDS DU BIT....................................................................................... 4 OCTET................................................................................................................... 10 ORDRE DES OCTETS............................................................................................... 13 LA CLASSE BYTEARRAY ................................................................................. 15 METHODES DE LECTURE ET D?ECRITURE............................................................... 16 COPIER DES OBJETS............................................................................................... 28 ECRIRE DES DONNEES AU FORMAT TEXTE ............................................................. 30 LIRE DES DONNEES AU FORMAT TEXTE ................................................................. 31 COMPRESSER DES DONNEES......................................................................... 34 SAUVEGARDER UN FLUX BINAIRE .............................................................. 38 GENERER UN PDF .............................................................................................. 40 Le codage binaire Il faut revenir à l?origine de l?informatique afin de comprendre les raisons de l?existence de la notation binaire. La plus petite unité de mesure en informatique est représentée par les chiffres 0 et 1 définissant un état au sein d?un circuit électrique : ? 0 : le circuit est fermé. ? 1 : le circuit est ouvert. Les processeurs équipant les ordinateurs ne comprennent donc que la notation binaire. Rassurez-vous, bien que tout cela puisse paraître compliqué dans un premier temps, il s?agit simplement d?une notation différente pour exprimer des données. Chapitre 20 ? ByteArray ? version 0.1.1 2 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Avant de s?intéresser au fonctionnement de la classe ByteArray, nous devons tout d?abord être à l?aise avec le concept de base arithmétique. De par l?histoire, l?homme compte en base 10 car ce dernier possède 10 doigts, c?est ce que nous appelons la base décimale. Pour exprimer la notion de temps, nous utilisons une base sexagésimale composée de 60 symboles. Ainsi, lorsque 60 secondes se sont écoulées, nous ne passons pas à 61 secondes, nous ajoutons une nouvelle unité, c'est-à- dire une minute et revenons à 0 en nombre de secondes. Nous utilisons dans la vie de tous les jours les 10 symboles suivant pour représenter les nombres : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Au delà de 9 nous devons donc combiner les symboles précédents. Pour cela, nous ajoutons une nouvelle unité, puis nous repartons de 0. A chaque déplacement sur la gauche, nous ajoutons une puissance de 10. La figure 20-1 illustre comment décomposer un nombre en différents groupes de puissances de 10 : Figure 20-1. Groupes de puissance de 10. Notre système décimal fonctionne par puissance de 10, nous pouvons donc exprimer le nombre 750 de la manière suivante : 7 * 100 + 5 * 10 = 7 * 102 + 5 * 101 Contrairement à la notation décimale, la notation binaire autorise l?utilisation des symboles 0 et 1 seulement. Le nombre 150 s?exprime en binaire de la manière suivante : 10010110 Nous allons apprendre à convertir la notation binaire en notation décimale. Pour cela, nous appliquons le même concept que pour la base décimale en travaillant cette fois par puissance de 2. La figure 20-2 illustre comment décomposer un nombre exprimé sous forme binaire en différents groupes : Chapitre 20 ? ByteArray ? version 0.1.1 3 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 20-2. Groupes de puissance de 2. Très jeune, nous avons appris à compter jusqu?à 10 en base décimale. Si la norme avait été l?utilisation de la base binaire, nous aurions mémorisé la colonne de droite du tableau suivant : Base décimale Base binaire 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111 8 1000 9 1001 10 1010 Tableau 1. Notation binaire. Afin de bien comprendre la notation binaire, il convient de s?attarder sur le concept de poids du bit. Une fois cette notion intégrée, nous introduirons la notion d?octet. A retenir Chapitre 20 ? ByteArray ? version 0.1.1 4 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org ? La notation binaire permet à l?origine de représenter un état au sein d?un circuit. ? Un processeur ne comprend que la notation binaire. ? Parmi les bases les plus courantes, nous comptons les bases décimales (10) et sexagésimales (60). ? La base 2 est appelée base binaire. Position et poids du bit Prenons le cas du nombre 150, que nous pouvons représenter de la même manière sous forme binaire : 10010110 Les symboles utilisés en notation binaire sont appelés bit, le terme provenant de l?Anglais binary digit. A l?inverse de la base décimale, où chaque déplacement sur la gauche incrémente d?une puissance de 10. En notation binaire, chaque déplacement du bit sur la gauche, augmente d?une puissance de 2. Nous exprimons la position de chaque bit composant l?expression sous forme de poids. Le bit de poids le plus fort (most significant bit ou msb) est toujours positionné l?extrême gauche, à l?inverse le bit le plus faible (less significant bit ou lsb) est positionné à l?extrême droite. La figure 20-3 illustre l?emplacement du bit le plus fort : Figure 20-3. Bit le plus fort. La figure 20-4 illustre l?emplacement du bit le plus faible : Figure 20-4. Bit le plus faible. Plus le poids d?un bit est fort, plus sa modification provoque un changement important du nombre. En continuant avec le nombre 150 nous voyons que si nous passons le bit le plus fort à 0 nous soustrayons 27 (128) au nombre 150 : Chapitre 20 ? ByteArray ? version 0.1.1 5 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 20-5. Modification du bit le plus fort. A l?inverse, si nous modifions un bit de poids plus faible, la répercussion est moindre sur le nombre : Figure 20-6. Modification d?un bit de poids plus faible. Afin de convertir la notation binaire en notation décimale, nous multiplions chaque bit par son poids et additionnons le résultat : 1*27 + 0*26 + 0*25 + 1*24 + 0*23 + 1*22 + 1*21 + 0*20 Soit, sous une forme plus compacte : 27 + 24 + 22 + 21 = 150 Nous retrouvons la notion de bits au sein des couleurs. Nous avons vu lors du chapitre 12 intitulé Programmation Bitmap que les couleurs étaient généralement codées en 8 bit, 16 bit ou 32 bit. Une blague d?informaticiens consiste à dire qu?il existe 10 types de personnes dans le monde. Ceux qui comprennent le binaire et ceux qui ne le comprennent pas. Nous savons que 10 en binaire correspond à 21 soit la valeur 2. Vous comprendrez donc sans problème la blague inscrite sur ce fameux t-shirt illustré par la figure 20-7 : Chapitre 20 ? ByteArray ? version 0.1.1 6 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 20-7. Humour et notation binaire. Comme pour la base 2, d?autres bases existent comme la base 16 appelée plus couramment base hexadécimale. Celle-ci est généralement utilisée pour représenter les couleurs. Nous avons déjà abordée cette notation au cours du chapitre 12 intitulé Programmation bitmap. Contrairement à la notation binaire composée de 2 symboles, la base 16 est composée de 16 symboles. Dans le code suivant nous évaluons la valeur 120 en base 16 : var entier:int = 120; // affiche : 78 trace ( entier.toString( 16 ) ); A l?inverse de la base 2, ou de la base 10, la base 16 utilise 16 symboles allant de 0 à F afin d?exprimer un nombre. Nous travaillons donc non plus en puissance de 2 ou 10 mais 16. La figure 20-8 illustre l?idée : Figure 20-8. Groupes de puissance de 16. Le tableau suivant regroupe les symboles utilisés en base 16 : Chapitre 20 ? ByteArray ? version 0.1.1 7 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Base hexadécimale Base décimale 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 A 10 B 11 C 12 D 13 E 14 F 15 Tableau 2. Notation hexadécimale. La valeur 1A peut donc être exprimée de la manière suivante : 1*161 + 10*160 = 26 Lorsque nous travaillons avec un flux binaire, il peut être nécessaire d?exprimer un nombre sous différentes notations. Au lieu d?effectuer la conversion manuellement, nous pouvons utiliser la méthode Chapitre 20 ? ByteArray ? version 0.1.1 8 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org toString de la classe Number. Celle-ci accepte en paramètre la base dans laquelle convertir le nombre. Dans le code suivant nous exprimons le nombre décimal 5 en notation binaire : var entier:int = 5; // affiche : 101 trace ( entier.toString( 2 ) ); En comptant de 1 à 10 en binaire nous retrouvons les valeurs du tableau 3 précédent : var zero:int = 0; var un:int = 1; var deux:int = 2; var trois:int = 3; var quatre:int = 4; var cinq:int = 5; var six:int = 6; var sept:int = 7; var huit:int = 8; var neuf:int = 9; var dix:int = 10; // affiche : 0 trace( zero.toString( 2 ) ); // affiche : 1 trace( un.toString( 2 ) ); // affiche : 10 trace( deux.toString( 2 ) ); // affiche : 11 trace( trois.toString( 2 ) ); // affiche : 100 trace( quatre.toString( 2 ) ); // affiche : 101 trace( cinq.toString( 2 ) ); // affiche : 110 trace( six.toString( 2 ) ); // affiche : 111 trace( sept.toString( 2 ) ); // affiche : 1000 trace( huit.toString( 2 ) ); // affiche : 1001 trace( neuf.toString( 2 ) ); // affiche : 1010 trace( dix.toString( 2 ) ); Chapitre 20 ? ByteArray ? version 0.1.1 9 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org De la même manière, nous pouvons convertir un nombre en base décimale en notation hexadécimale : var zero:int = 0; var un:int = 1; var deux:int = 2; var trois:int = 3; var quatre:int = 4; var cinq:int = 5; var six:int = 6; var sept:int = 7; var huit:int = 8; var neuf:int = 9; var dix:int = 10; var onze:int = 11; var douze:int = 12; var treize:int = 13; var quatorze:int = 14; var quinze:int = 15; // affiche : 0 trace( zero.toString( 16 ) ); // affiche : 1 trace( un.toString( 16 ) ); // affiche : 2 trace( deux.toString( 16 ) ); // affiche : 3 trace( trois.toString( 16 ) ); // affiche : 4 trace( quatre.toString( 16 ) ); // affiche : 5 trace( cinq.toString( 16 ) ); // affiche : 6 trace( six.toString( 16 ) ); // affiche : 7 trace( sept.toString( 16 ) ); // affiche : 8 trace( huit.toString( 16 ) ); // affiche : 9 trace( neuf.toString( 16 ) ); // affiche : a trace( dix.toString( 16 ) ); // affiche : b trace( onze.toString( 16 ) ); // affiche : c trace( douze.toString( 16 ) ); // affiche : d trace( treize.toString( 16 ) ); Chapitre 20 ? ByteArray ? version 0.1.1 10 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : e trace( quatorze.toString( 16 ) ); // affiche : f trace( quinze.toString( 16 ) ); Pour des raisons pratiques, les ingénieurs décidèrent alors de grouper les bits par paquets, c?est ainsi que naquit la notion d?octet. A retenir ? On parle de poids du bit pour exprimer sa puissance. ? Le bit de poids le plus fort est toujours positionné à l?extrême gauche. ? Le bit de poids le plus faible est toujours positionné à l?extrême droite. ? La méthode toString de la classe Number permet d?exprimer un nombre dans une base différente. Octet L?octet permet d?exprimer une quantité de données. Nous l?utilisons tous les jours dans le monde de l?informatique pour indiquer par exemple le poids d?un fichier. Bien entendu, nous ne dirons pas qu?un fichier MP3 pèse 3145728 octets mais plutôt 3 méga-octets. Nous ajoutons donc généralement un préfixe comme kilo ou méga permettant d?exprimer un volume d?octets : ? 1 kilooctet (ko) = 10³ octets (1 000 octets) ? 1 mégaoctet (Mo) = 106 octets = 1 000 ko (1 000 000 octets) Attention à ne pas confondre le terme de bit et byte : 1 octet = 8 bit Un octet est donc composé de 8 bit : 11111111 Ce dernier peut contenir un entier naturel compris entre 0 et 255 lorsque celui-ci est dit non-signé (non négatif). Afin de convertir cette notation binaire sous forme décimale, nous utilisons la technique abordée précédemment. Nous multiplions chaque bit par son poids et additionnons le résultat : 1*27 + 1*26 + 1*25 + 1*24 + 1*23 + 1*22 + 1*21 + 1*20 Ce qui nous donne le résultat suivant : Chapitre 20 ? ByteArray ? version 0.1.1 11 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255 Nous verrons qu?il est aussi possible d?exprimer au sein d?un octet une valeur oscillant entre -128 et 127 à l?aide d?un octet dit signé (négatif). Il est important de noter que dans un contexte de manipulation de données binaire la base 16 est très souvent utilisée au sein de logiciels tels les éditeurs hexadécimaux afin d?optimiser la représentation d?un octet. Voici une liste non exhaustive de quelques éditeurs hexadécimaux gratuits ou payants : ? Free Hex Editor Neo (gratuit) http://www.hhdsoftware.com/Family/hex-editor.html ? HxD (gratuit) http://mh-nexus.de/hxd/ ? Hex Workshop (payant) http://www.hexworkshop.com ? Hexprobe (payant) http://www.hexprobe.com/hexprobe Il est plus facile de lire la valeur d?un octet sous la notation hexadécimale suivante : 4E Que sous une notation binaire : 1001110 Un octet non signé d?une valeur de 255 peut donc être exprimé par deux symboles seulement en notation hexadécimale : FF En additionnant le poids de chaque symbole et en additionnant le résultat nous obtenons la valeur 255 : 15*161 + 15*160 = 240 + 15 = 255 Afin de mettre cette notion en pratique, nous avons ouvert une image au format PNG au sein d?un éditeur hexadécimal. La figure 20-9 illustre une partie des données : Chapitre 20 ? ByteArray ? version 0.1.1 12 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 20-9. Editeur hexadécimal. Nous pouvons remarquer que chaque colonne est composée de deux symboles représentant un octet. Comme son nom l?indique, l?éditeur hexadécimal représente chaque octet en base 16 à l?aide de deux symboles. Pourquoi un tel choix ? Pour des raisons pratiques, car le couple de symboles FF permet de représenter la valeur maximale d?un octet, ce qui est optimisé en termes d?espaces et moins pénible à lire et mémoriser. Comme nous le verrons plus tard, chaque fichier peut être identifié par son entête. En lisant la spécification du format PNG disponible à l?adresse suivante : http://www.w3.org/TR/PNG/ Nous voyons que tout fichier PNG doit obligatoirement commencer par une signature composée de cette série de valeurs décimales : 137 80 78 71 13 10 26 10 En convertissant en notation hexadécimale chacune de ces groupes, nous retrouvons les mêmes valeurs dans notre fichier PNG : var premierOctet:int = 137; var deuxiemeOctet:int = 80; var troisiemeOctet:int = 78; var quatriemeOctet:int = 71; var cinquiemeOctet:int = 13; var sixiemeOctet:int = 10; var septiemeOctet:int = 26; var huitiemeOctet:int = 10; // affiche : 89 trace( premierOctet.toString ( 16 ) ); // affiche : 50 trace( deuxiemeOctet.toString ( 16 ) ); // affiche : 4e Chapitre 20 ? ByteArray ? version 0.1.1 13 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org trace( troisiemeOctet.toString ( 16 ) ); // affiche : 47 trace( quatriemeOctet.toString ( 16 ) ); // affiche : d trace( cinquiemeOctet.toString ( 16 ) ); // affiche : a trace( sixiemeOctet.toString ( 16 ) ); // affiche : 1a trace( septiemeOctet.toString ( 16 ) ); // affiche : a trace( huitiemeOctet.toString ( 16 ) ); La figure 20-10 illustre la correspondance entre chaque octet : Figure 20-10 Signature d?un fichier PNG. Nous allons nous intéresser à présent à l?ordre des octets. A retenir ? Attention à ne pas confondre 1 octet (byte) et 1 bit. ? Un octet représente 8 bits. ? Un octet peut contenir une valeur maximale de 255 soit 27 + 26 + 25 + 24 + 23 + 22 + 21 + 20 . ? La notation hexadécimale est couramment utilisée pour représenter les octets. Ordre des octets Lorsque nous devons utiliser plusieurs octets afin d?exprimer un nombre, il est important de prendre en considération l?ordre de stockage. Cette notion est appelée en Anglais byte ordering ou endian nature. Chapitre 20 ? ByteArray ? version 0.1.1 14 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Souvenez-vous, dans la partie intitulée Position et poids du bit nous avons vu que le bit le plus à gauche était considéré comme celui de poids le plus fort, et inversement le bit le plus à droite était celui de poids le plus faible. Le même concept s?applique au sein d?un groupe d?octets. Nous parlons alors d?octet le plus fort (most significant byte ou MSB) et d?octet le plus faible (less significant byte ou LSB). Attention, notez que nous utilisons l?acronyme MSB et LSB en majuscule pour exprimer l?ordre des octets. A l?inverse nous utilisons des minuscules dans les acronymes msb et lsb pour exprimer le poids d?un bit. Imaginons que nous devions stocker le nombre entier suivant : 550000000 Ce nombre est un entier non négatif 32 bits et nécessite 4 octets afin d?être stocké, sa valeur en hexadécimale est la suivante : 20 C8 55 80 Les processeurs tels les Motorola 68000 ou les processeurs SPARC équipant les plateformes Sun Microsystems utilise cet ordre de stockage et sont considérés comme gros-boutiste ou big-endian en Anglais. Nous utilisons ce terme car l?octet de poids le plus fort (le plus gros) est à gauche de l?expression : Gros-boutiste (big-endian) 0 1 2 3 20 C8 55 80 Tableau 3. Stockage des octets gros-boutiste. D?autres processeurs comme le fameux 5602 de Motorola ou x86 d?Intel sont petit-boutiste ou little-endian et stockent les octets dans le sens inverse : Petit-boutiste (little-endian) 0 1 2 3 Chapitre 20 ? ByteArray ? version 0.1.1 15 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org 80 55 C8 20 Tableau 4. Stockage des octets petit-boutiste. Lors du développement d?applications ActionScript 3 utilisant la classe ByteArray nous devrons prendre en considération cet ordre, surtout dans un contexte de communication externe. La notion d?ordre des octets n?a pas de réelle conséquence dans le cas de lecture d?un seul octet à la fois. A l?inverse, lorsque plusieurs octets sont interprétés, nous devons absolument prendre l?ordre des octets en considération. A retenir ? Certaines architectures utilisent l?ordre petit-boutiste, d?autres l?ordre gros-boutiste. ? L?ordre des octets est important dans un contexte de communication entre plusieurs machines. ? Nous verrons que les classes ByteArray, Socket et URLStream possèdent une propriété endian permettant de spécifier l?ordre dans lequel stocker les octets. La classe ByteArray Il est temps de mettre en application toutes les notions abordées précédemment grâce à la classe flash.utils.ByteArray. Même si la classe ByteArray figure parmi l?une des classes les plus puissantes du lecteur Flash, une des questions les plus courantes conçerne l?intérêt de pouvoir écrire un flux binaire. L?intérêt de la classe ByteArray réside dans l?accès bas niveau au niveau des données. En d?autres termes, nous allons pouvoir manipuler des types de données non existants et travailler sur des octets. Cela peut nous permettre par exemple d?interpréter ou de générer n?importe quel type de fichiers en ActionScript 3. Comme son nom l?indique, la classe ByteArray représente un tableau d?octets. Pour exprimer 1 kilo-octet nous utiliserons donc 1000 index du tableau. La figure 20-11 illustre le fonctionnement d?un tableau d?octets : Chapitre 20 ? ByteArray ? version 0.1.1 16 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 20-11. Tableau d?octets. Comme son nom l?indique, la classe ByteArray possède quelques similitudes avec la classe Array. Les deux objets demeurent des tableaux et possèdent donc une longueur, mais chaque index composant un tableau d?octet ne peut être codé que sur 8 bits. Voici une liste non exhaustive de quelques projets utilisant la classe ByteArray : ? FC64 : Emulateur Commodore 64. http://codeazur.com.br/stuff/fc64_final/ ? AlivePDF : Librairie de génération de PDF. http://www.alivepdf.org ? FZip : Librairie de compression et décompression d?archives ZIP. http://codeazur.com.br/lab/fzip/ ? GIF Player : Librairie de lecture de GIF animés. http://www.bytearray.org/?p=95 ? GIF Player : Librairie d?encodage de GIF animés. http://www.bytearray.org/?p=93 ? WiiFlash : Librairie de gestion de la manette Nintendo Wiimote. www.wiiflash.org ? Encodeurs d?images : Deux classes issues de la librairie corelib fournie par Adobe permettent l?encodage PNG et JPEG. http://code.google.com/p/as3corelib/ ? Popforge Audio : Librairie de génération de sons. http://www.popforge.de/ Attention, les mêmes capacités que la classe ByteArray sont aussi présentes dans la classe flash.net.Socket. Grâce à celle-ci nous pouvons dialoguer avec l?extérieur au format brut binaire. Méthodes de lecture et d?écriture Afin de créer un flux binaire nous utilisons la classe flash.utils.ByteArray. Bien que nous puissions créer un tableau traditionnel à l?aide de l?écriture littérale suivante : var monTableau:Array = [ ]; Seul le mot clé new permet la création d?un tableau d?octets : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); Pour récupérer la longueur du tableau d?octets nous utilisons sa propriété length : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); Chapitre 20 ? ByteArray ? version 0.1.1 17 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : 0 trace ( fluxBinaire.length ); Lorsqu?un tableau d?octets est créé, celui-ci est vide, de la même manière qu?un tableau traditionnel. Notons qu?en ActionScript 3, la taille d?un tableau d?octets est dynamique et ne peut être fixe comme c?est le cas dans certains langages tels Java, C# ou autres. Comme nous l?avons abordé précédemment, l?ordre des octets peut varier selon chaque plateforme. La propriété endian définie par la classe ByteArray permet de spécifier l?ordre des octets. Par défaut, le tableau d?octets est gros-boutiste : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); // affiche : bigEndian trace ( fluxBinaire.endian ); Afin de modifier l?ordre d?écriture des octets nous utiliser les constantes de la classe flash.utils.Endian : ? Endian.BIG_ENDIAN : octet le plus fort en première position. ? Endian.LITTLE_ENDIAN : octet le plus faible en première position. Dans le code suivant, nous testons si l?ordre des octets est gros- boutiste : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); // affiche : true trace( fluxBinaire.endian == Endian.BIG_ENDIAN ); Nous pouvons modifier l?ordre : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); // écriture dans l'ordre petit-boutiste fluxBinaire.endian == Endian.LITTLE_ENDIAN; Pour stocker l?entier non signé 5 au sein d?une instance de la classe Array nous pouvons écrire le code suivant : var donnees:Array = new Array(); var nombre:uint = 5; donnees[0] = nombre; // affiche : 1 trace( donnees.length ); Chapitre 20 ? ByteArray ? version 0.1.1 18 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : 5 trace( donnees[0] ); Pour écrire le même nombre au sein d?un tableau d?octets, le nombre doit être séparé en groupe d?octets. Ainsi, afin d?écrire un nombre entier non signé 32 bits comme c?est le cas pour le type uint nous devons séparer l?entier en 4 groupes de 8 bits : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture manuelle en représentation gros-boutiste fluxBinaire[0] = (nombre & 0xFF000000) >> 24; fluxBinaire[1] = (nombre & 0x00FF0000) >> 16; fluxBinaire[2] = (nombre & 0x0000FF00) >> 8; fluxBinaire[3] = nombre & 0xFF; Notez que nous venons de stocker l?entier en représentation gros- boutiste. Une fois l?entier séparé, nous pouvons le reconstituer à l?aide des opérateurs de manipulation de bits : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture manuelle en représentation gros-boutiste fluxBinaire[0] = (nombre & 0xFF000000) >> 24; fluxBinaire[1] = (nombre & 0x00FF0000) >> 16; fluxBinaire[2] = (nombre & 0x0000FF00) >> 8; fluxBinaire[3] = nombre & 0xFF; var nombreStocke:uint = fluxBinaire[0] << 24 | fluxBinaire[1] << 16 | fluxBinaire[2] << 8 | fluxBinaire[3]; // affiche : 5 trace( nombreStocke ); Si nous devions stocker les données au format petit-boutiste nous devrions inverser l?ordre d?écriture : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture manuelle en représentation petit-boutiste fluxBinaire[3] = (nombre & 0xFF000000) >> 24; fluxBinaire[2] = (nombre & 0x00FF0000) >> 16; fluxBinaire[1] = (nombre & 0x0000FF00) >> 8; fluxBinaire[0] = nombre & 0xFF; var nombreStocke:uint = fluxBinaire[3] << 24 | fluxBinaire[2] << 16 | fluxBinaire[1] << 8 | fluxBinaire[0]; Chapitre 20 ? ByteArray ? version 0.1.1 19 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : 5 trace( nombreStocke ); Il s?avère extrêmement fastidieux d?écrire des données au sein d?un tableau d?octets à l?aide du code précédent. Rassurez-vous, afin de nous faciliter la tâche le lecteur Flash va automatiquement gérer le découpage des octets ainsi que la reconstitution à l?aide de méthodes de lecture et d?écriture. Ainsi, pour écrire un entier non signé au sein du tableau, nous utilisons la méthode writeUnsignedInt dont voici la signature : public function writeUnsignedInt(value:int):void Voici le détail du paramètre attendu : ? value : un entier non signé de 32 bits. Le code fastidieux précédent peut donc être réécrit de la manière suivante : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); Un entier non signé nécessite 32 bits, en testant la longueur du tableau binaire, nous voyons que 4 octets sont inscrits dans le tableau : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); // affiche : 4 trace( fluxBinaire.length ); Afin de lire l?entier non signé écrit, nous utilisons simplement la méthode readUnsignedInt : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); // lève une erreur à l'exécution var nombreStocke:uint = fluxBinaire.readUnsignedInt(); En testant le code précédent, l?erreur à l?exécution suivante est levée : Chapitre 20 ? ByteArray ? version 0.1.1 20 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Error: Error #2030: Fin de fichier détectée. Lors de l?écriture du flux de données, un pointeur interne défini par la propriété position de l?objet ByteArray se déplace automatiquement auprès de l?octet suivant : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // affiche : 0 trace( fluxBinaire.position ); // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); // affiche : 4 trace( fluxBinaire.position ); La propriété position nous indique que le pointeur est à l?index 4 du tableau d?octets. Ainsi, la prochaine méthode d?écriture commencera à écrire les données à partir de cet index. La figure 20-12 illustre le principe : Figure 20-12. Positionnement du pointeur interne. Afin de gérer les éventuelles erreurs de lecture du flux, nous pouvons placer les appels aux différentes méthodes de lecture au sein d?un bloc try catch : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); try { // lève une erreur à l?exécution trace( fluxBinaire.readUnsignedInt() ); } catch ( pErreur:Error ) { trace ("erreur de lecture"); } Chapitre 20 ? ByteArray ? version 0.1.1 21 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Pour pouvoir lire le flux, nous devons obligatoirement remettre à zéro le pointeur interne puis appeler la méthode de lecture : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); fluxBinaire.position = 0; // lecture de l?entier de 32 bit non signé var nombreStocke:uint = fluxBinaire.readUnsignedInt(); // affiche : 5 trace( nombreStocke ); Nous allons nous attarder sur un détail important. Dans le code précédent, nous avons à nouveau utilisé le type uint afin de stocker la variable nombre ainsi que le résultat de la méthode readUnsignedInt. Tout au long de l?ouvrage, nous avons préféré l?utilisation du type int qui s?avère dans la plupart des cas plus rapide que le type uint. Dans un contexte de manipulation de flux binaire, il convient de toujours utiliser le type lié à la méthode de lecture ou d?écriture. Nous utilisons donc le type uint lors de l?utilisation des méthodes writeUnsignedInt et readUnsignedInt. Si nous ne respectons pas cette précaution, nous risquons d?obtenir des valeurs erronées. Afin de confirmer cela, nous pouvons prendre l?exemple suivant. Dans le code suivant, nous inscrivons au sein du tableau d?octets un entier non signé d?une valeur de 3 000 000 000. En stockant le retour de la méthode readUnsignedInt au sein d?une variable de type int, nous obtenons un résultat erroné : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 3000000000; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); fluxBinaire.position = 0; // lecture de l?entier de 32 bit non signé var nombreStocke:int = fluxBinaire.readUnsignedInt(); // la machine virtuelle conserve le type à l'exécution Chapitre 20 ? ByteArray ? version 0.1.1 22 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : -1294967296 trace( nombreStocke ); L?entier retourné par la méthode readUnsignedInt n?a pu être stocké correctement car le type int ne peut accueillir un entier supérieur à 2 147 483 647. En utilisant le type approprié, nous récupérons correctement l?entier stocké : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 3000000000; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); fluxBinaire.position = 0; // lecture de l?entier de 32 bit non signé var nombreStocke:uint = fluxBinaire.readUnsignedInt(); // la machine virtuelle conserve le type à l'exécution // affiche : 3000000000 trace( nombreStocke ); En analysant la figure 20-13 ci dessous, nous voyons que le nombre entier non signé d?une valeur de 3 000 000 000 occupe les 4 octets (32 bit) alloués par la machine virtuelle pour le type uint : Figure 20-13. Entier non signé au sein du tableau d?octets. Avez-vous remarqué que la figure précédente utilisait la notation hexadécimale afin de simplifier la représentation d?un tel nombre ? Nous utiliserons désormais la notation hexadécimale afin de simplifier la représentation des données. Nous pourrions donc exprimer une telle valeur à l?aide de la notation hexadécimale : var nombre:uint = 0xB2D05E00; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); A l?inverse, si nous stockons un entier non signé d?une valeur de 5 à l?aide de la méthode writeUnsignedInt : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); Chapitre 20 ? ByteArray ? version 0.1.1 23 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org var nombre:uint = 5; // écriture d?un entier de 32 bit non signé fluxBinaire.writeUnsignedInt(nombre); fluxBinaire.position = 0; // lecture de l?entier de 32 bit non signé var nombreStocke:uint = fluxBinaire.readUnsignedInt(); // affiche : 5 trace( nombreStocke ); Comme l?illustre la figure 20-14, seuls les 8 bits de poids le plus faible suffisent : Figure 20-14. Le nombre 5 au sein du tableau d?octets. Nous pouvons donc stocker l?entier non signé au sein d?un seul octet à l?aide de la méthode writeByte : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un octet fluxBinaire.writeByte(nombre); L?octet est inscrit à l?index 0 du tableau, la figure 20-15 illustre l?octet sous notation hexadécimale: Figure 20-15. Un octet stocké à l?index 0. Une fois l?octet écrit, nous pouvons le lire au sein du tableau sous la forme d?un entier non signé à l?aide la méthode readUnsignedByte : // création d'un tableau d?octets var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 5; // écriture d?un octet fluxBinaire.writeByte(nombre); fluxBinaire.position = 0; // lecture de l'octet non signé var nombreStocke:uint = fluxBinaire.readUnsignedByte(); // affiche : 5 trace( nombreStocke ); Chapitre 20 ? ByteArray ? version 0.1.1 24 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous tentons d?écrire une valeur dépassant un octet en stockage, seuls les bits inférieurs sont écrits au sein du flux. Dans le code suivant nous évaluons la valeur 260 en binaire : var nombre:uint = 260; // affiche : 100000100 trace ( nombre.toString( 2 ) ); Le nombre entier non signé 260 occupe 9 bits, dont voici la représentation : 1 0 0 0 0 0 1 0 0 |_ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _| 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Comme nous l?avons vu précédemment, chaque index d?un tableau d?octets ne peut contenir qu?un seul octet, soit 8 bits. Ainsi, lorsque nous tentons d?écrire une valeur nécessitant plus de 8 bits, seuls les bits de poids le plus faible sont inscrits. Rappelez-vous, les bits dits de poids le plus faible sont à droite. Les bits dits de poids le plus fort sont ceux situés à gauche. Un octet ne pouvant contenir que 8 bit, le 9ème bit débordant est ignoré : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:uint = 260; // écriture d'un entier non signé fluxBinaire.writeByte(nombre); fluxBinaire.position = 0; // lecture de l'entier non-signé // affiche : 4 trace ( fluxBinaire.readUnsignedByte() ); En convertissant l?entier non signé 4 en notation binaire : // lecture de l'entier non-signé au format binaire // affiche : 100 trace ( fluxBinaire.readUnsignedByte().toString(2) ); Nous retrouvons les 3 bits 6, 7 et 8 de l?octet écrit : 1 0 0 0 0 0 1 0 0 |_ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _| 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Chapitre 20 ? ByteArray ? version 0.1.1 25 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Comme nous l?avons vu précédemment, un octet peut contenir une valeur oscillant 0 et 255 lorsque celui-ci est dit non signé. A l?inverse un octet signé, peut contenir un entier relatif allant de -128 à 127. Ainsi si nous inscrivons un entier signé d?une valeur de -80, nous devons lire l?octet avec la méthode readByte : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:int = -80; // écriture d'un entier signé fluxBinaire.writeByte(nombre); fluxBinaire.position = 0; // lecture de l'octet signé // affiche : -80 trace( fluxBinaire.readByte() ); Nous pouvons alors nous poser la question du type à utiliser lorsque nous utilisons les méthodes writeByte et readByte et readUnsignedByte. Nous avons vu précédemment qu?il était recommandé d?utiliser le type uint lors de l?utilisation des méthodes writeUnsignedInt et readUnsignedInt. Il n?existe pas, à l?heure d?aujourd?hui de type byte ou ubyte en ActionScript 3. Nous utilisons donc le type int en remplacement qui permet de stocker toutes les valeurs possibles d?un octet signé ou non signé. Afin de stocker une valeur supérieure à 255 nous devons travailler sur deux octets. Prenons le cas du nombre entier 1200 sous sa forme binaire : 1 0 0 1 0 1 1 0 0 0 0 |_ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _| 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Deux octets sont nécessaires à l?écriture de ce nombre, pour cela nous utilisons la méthode writeShort dont voici la signature : public function writeShort(value:int):void Voici le détail du paramètre attendu : ? value : Un entier de 32 bits. Seuls les 16 bits inférieurs sont écrits dans le flux d'octets. Un couple de deux octets est généralement appelé mot ou word en Anglais, celui-ci pouvant contenir une valeur allant de -32768 à 32767 ou 0 à 65 535 si celui est dit non signé. Chapitre 20 ? ByteArray ? version 0.1.1 26 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org L?intérêt de la méthode writeShort réside dans l?écriture d?un entier de 16 bit au lieu de 32 prévus par les types int et uint. Comme pour le type byte ou ubyte, il n?existe pas à l?heure d?aujourd?hui de type short ou ushort en ActionScript 3. Nous utilisons donc à nouveau le type int en remplacement. Dans le code suivant nous inscrivons un entier de 16 bits non signé : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:int = 1200; // écriture d'un entier de 16 bit non signé fluxBinaire.writeShort(nombre); // nombre d'octets stockés // affiche : 2 trace( fluxBinaire.length ); En testant le code précédent nous remarquons que deux octets sont écrits dans le flux. Afin de lire la valeur stockée nous utilisons la méthode readShort : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:int = 1200; // écriture d'un entier de 16 bit non signé fluxBinaire.writeShort(nombre); fluxBinaire.position = 0; // lecture de l'entier de 16 bit signé // affiche : 1200 trace( fluxBinaire.readShort() ); Si nous analysons la représentation binaire de l?entier 1200 nous obtenons la représentation suivante : 1 0 0 1 0 1 1 0 0 0 0 |_ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _| 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 L?entier 1200 est donc codé sur 16 bits, si nous appelons successivement la méthode readUnsignedByte nous évaluons l?entier octet par octet : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:int = 1200; // écriture d'un entier de 16 bit non signé fluxBinaire.writeShort(1200); Chapitre 20 ? ByteArray ? version 0.1.1 27 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org fluxBinaire.position = 0; // lecture du premier octet var premierOctet:int = fluxBinaire.readUnsignedByte(); // lecture du deuxième octet var secondOctet:int = fluxBinaire.readUnsignedByte(); // affiche : 4 trace( premierOctet ); // affiche : 176 trace( secondOctet ); Bien entendu, nous pouvons stocker un nombre à virgule flottante au sein du tableau. ActionScript 3 utilise la norme IEEE 754 afin de traiter les nombres à virgule flottante. Pour cela nous utilisons la méthode writeFloat : public function writeFloat(value:Number):void Voici le détail du paramètre attendu : ? value : un nombre à virgule flottante de 32 bits. Dans le code suivant, nous inscrivons un flottant de 32 bits : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:Number = 12.5; // écriture d'un flottant de 32 bits fluxBinaire.writeFloat(nombre); // réinitialisation du pointeur fluxBinaire.position = 0; // lecture du flottant var flottant:Number = fluxBinaire.readFloat(); // affiche : 12.5 trace( flottant ); // affiche : 1100 trace( flottant.toString(2) ); La méthode writeFloat écrit un nombre au format 32 bits, 4 octets sont donc nécessaire au stockage d?un nombre flottant : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); var nombre:Number = 12.5; // écriture d'un flottant de 32 bits fluxBinaire.writeFloat(nombre); // affiche : 4 trace( fluxBinaire.length ); Chapitre 20 ? ByteArray ? version 0.1.1 28 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Il n?existe pas en ActionScript 3 de type float, nous utilisons donc le type Number en remplacement. Si nous souhaitons stocker un nombre à virgule flottante plus grand, équivalent au type Number codé sur 64 bits nous pouvons utiliser la méthode writeDouble : // création d'un flux binaire var fluxBinaire:ByteArray = new ByteArray(); // écriture d'un nombre à virgule flottante fluxBinaire.writeDouble(12.5); // affiche : 8 trace( fluxBinaire.length ); Notons qu?un double est l?équivalent du type Number. ECMAScript 4 définit un type double, nous pourrions donc voir ce type apparaître un jour en ActionScript 3. A retenir ? La classe ByteArray définit un ensemble de méthodes permettant d?écrire différents types de données. ? Certains types de données écrits au sein du tableau d?octets n?ont pas d?équivalent en ActionScript 3. C?est le cas des types float, byte, et short. ? Ces méthodes découpent automatiquement les valeurs passées en paramètres en groupes d?octets. Copier des objets La classe ByteArray intègre un sérialiseur et désérialiseur AMF permettant l?écriture de types au format AMF. Nous pouvons utiliser la méthode writeObject de manière détournée en passant un objet, celui-ci est alors inscrit au sein du flux : var fluxBinaire:ByteArray = new ByteArray(); // définition d'un objet var parametres:Object = { age : 25, nom : "Bob" }; // écriture de l'objet au sein du flux fluxBinaire.writeObject( parametres ); Afin de créer une copie de ce dernier nous appelons la méthode readObject : var fluxBinaire:ByteArray = new ByteArray(); // définition d'un objet var parametres:Object = { age : 25, nom : "Bob" }; Chapitre 20 ? ByteArray ? version 0.1.1 29 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // écriture de l'objet au sein du flux fluxBinaire.writeObject( parametres ); fluxBinaire.position = 0; // copie de l'objet parametres var copieParametres:Object = fluxBinaire.readObject(); Nous pouvons ainsi créer une fonction personnalisée réalisant en interne tout ce processus : function copieObjet ( pObjet:* ):* { var fluxBinaire:ByteArray = new ByteArray(); fluxBinaire.writeObject( pObjet ); fluxBinaire.position = 0; return fluxBinaire.readObject(); } Afin de copier un objet, nous devons simplement appeler la fonction copieObjet : // définition d'un objet var parametres:Object = { age : 25, nom : "Bob" }; var copieParametres:Object = copieObjet ( parametres ); /* affiche : nom : Bob age : 25 */ for ( var p:String in copieParametres ) trace( p, " : ", copieParametres[p] ); En modifiant les données de l?objet d?origine, nous voyons que l?objet copieParametres est bien dupliqué : var copieParametres:Object = copieObjet ( parametres ); // modification du nom dans l'objet d'origine parametres.nom = "Stevie"; /* affiche : nom : Bob age : 25 */ for ( var p:String in copieParametres ) trace( p, " : ", copieParametres[p] ); Notez que cette technique ne fonctionne que pour les objets littéraux et non les instances de classes. A retenir Chapitre 20 ? ByteArray ? version 0.1.1 30 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode writeObject permet d?écrire un objet composite au sein du tableau d?octets. ? Grace à la méthode readObject nous pouvons créer une copie de l?objet écrit. Ecrire des données au format texte Afin de stocker des données au format texte nous pouvons utiliser la méthode writeUTFBytes dont voici la signature : public function writeUTFBytes(value:String):void Chaque caractère composant la chaîne est encodé sur une suite d?un à quatre octets. Dans le code suivant, nous écrivons un texte simple : var fluxBinaire:ByteArray = new ByteArray(); var chaine:String = "Bonjour Bob"; // affiche : 11 trace( chaine.length ); fluxBinaire.writeUTFBytes(chaine); // affiche : 11 trace( fluxBinaire.length ); Si nous utilisons les 127 premiers caractères de l?alphabet, nous voyons que chaque caractère est codé sur un octet. A l?aide de la méthode readByte, nous pouvons lire récupérer le caractère de chaque octet : var fluxBinaire:ByteArray = new ByteArray(); var chaine:String = "Bonjour Bob"; // affiche : 19 trace( chaine.length ); fluxBinaire.writeUTFBytes(chaine); // affiche : 20 trace( fluxBinaire.length ); fluxBinaire.position = 0; // affiche : 66 trace( fluxBinaire.readByte() ); // affiche : 111 trace( fluxBinaire.readByte() ); A l?inverse, si nous utilisons des caractères spéciaux, ils seront alors codés sur plusieurs octets : var fluxBinaire:ByteArray = new ByteArray(); var chaine:String = "Bonjour Bob ça va ?"; Chapitre 20 ? ByteArray ? version 0.1.1 31 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : 19 trace( chaine.length ); fluxBinaire.writeUTFBytes(chaine); // affiche : 20 trace( fluxBinaire.length ); Dans le code précédent, le caractère ç est codé sur deux octets. Lire des données au format texte Comme nous l?avons vu précédemment, il est primordial de ne pas tenter sortir du flux. Souvenez-vous, précédemment nous avions tenté de lire un octet non disponible au sein du flux, l?erreur suivante fut levée à l?exécution : Error: Error #2030: Fin de fichier détectée. Afin de garantir de ne jamais sortir du flux, nous utilisons la propriété bytesAvailable. Celle-ci renvoie le nombre d?octets disponible, autrement dit la soustraction de la longueur totale du flux et de la position du pointeur interne. Dans le code suivant nous inscrivons des données texte au sein d?un tableau d?octets et calculons manuellement le nombre d?octets disponibles à la lecture : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Bob Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; // calcul manuel du nombre d'octets disponibles à la lecture // affiche : 10 trace( fluxBinaire.length - fluxBinaire.position ); En réinitialisant le pointeur à l?aide de la propriété position, nous obtenons 10 octets disponibles. Dans l?exemple suivant, nous utilisons la propriété bytesAvailable, qui permet de renvoyer automatiquement le nombre d?octets disponibles à la lecture : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Bob Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; // affiche : 10 Chapitre 20 ? ByteArray ? version 0.1.1 32 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org trace( fluxBinaire.bytesAvailable ); Nous utilisons très souvent cette propriété afin d?être sur de ne pas aller trop loin dans la lecture des octets. Afin d?extraire le texte stocké au sein du flux, nous utilisons la méthode readUTFBytes ayant la signature suivante : public function readUTFBytes(length:uint):String En passant le nombre d?octets à lire, celle-ci décode automatiquement chaque octet en caractère UTF-8. Ainsi dans le code suivant, nous lisons uniquement les 3 premiers caractères du flux : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Bob Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; // extrait les 3 premiers caractères du flux // affiche : Bob trace( fluxBinaire.readUTFBytes( 3 ) ); Nous pouvons donc utiliser la propriété bytesAvailable afin d?être sur de lire la totalité du texte stockée dans le flux : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Bob Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; // extrait la totalité de la chaîne de caractères // affiche : Bob Groove trace( fluxBinaire.readUTFBytes( fluxBinaire.bytesAvailable ) ); Nous utilisons généralement la propriété bytesAvailable avec une boucle while afin d?extraire chaque caractère : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte fluxBinaire.writeUTFBytes("Bob Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; while ( fluxBinaire.bytesAvailable > 0 ) { /* affiche : B o b Chapitre 20 ? ByteArray ? version 0.1.1 33 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org G r o o v e */ trace( String.fromCharCode( fluxBinaire.readByte()) ); } Nous lisons chaque octet, tant qu?il en reste à lire. Afin de trouver le caractère correspondant au nombre, nous utilisons la méthode fromCharCode de la classe String. Si nous insérons des caractères spéciaux, l?appel de la méthode readUTFBytes décode correctement les caractères encodés sur plusieurs octets : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte avec des caractères spéciaux fluxBinaire.writeUTFBytes("Bob ça Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; // extrait la totalité de la chaîne de caractères // affiche : Bob ça Groove trace( fluxBinaire.readUTFBytes( fluxBinaire.bytesAvailable ) ); A l?inverse, si nous utilisons la méthode readByte en évaluant chaque caractère, nous ne pourrons interpréter correctement le caractère ç encodé sur deux octets : var fluxBinaire:ByteArray = new ByteArray(); // écriture de données texte avec des caractères spéciaux fluxBinaire.writeUTFBytes("Bob ça Groove"); // réinitialisation du pointeur fluxBinaire.position = 0; while ( fluxBinaire.bytesAvailable > 0 ) { /* affiche : B o b ? ? a G r o Chapitre 20 ? ByteArray ? version 0.1.1 34 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org o v e */ trace( String.fromCharCode( fluxBinaire.readByte()) ); } La méthode readByte n?est pas adaptée au décodage de chaînes encodées. A retenir ? Afin de décoder sous forme de chaîne de caractères UTF-8 un ensemble d?octets, nous utilisons la méthode readUTFBytes. ? Celle-ci accepte en paramètre, le nombre d?octets à lire au sein du flux. Compresser des données La classe ByteArray dispose d?une méthode de compression de données utilisant l?algorithme zlib dont la spécification est disponible à l?adresse suivante : http://www.ietf.org/rfc/rfc1950.txt Il peut être intéressant d?utiliser cette fonctionnalité afin de compresser des données devant être sauvegardées. Prenons le cas d?application suivant : Nous devons sauver sur le poste de l?utilisateur un volume de données important. Afin de faciliter la représentation de ces données, un objet XML est utilisé. Pour stocker des données sur le poste client, nous utilisons la classe flash.net.SharedObject permettant de créer des cookies permanents. Dans le code suivant, nous chargeons un fichier XML d?une taille de 1,5Mo au format binaire afin d?obtenir directement un objet ByteArray contenant le flux XML : var chargeur:URLLoader = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.BINARY; chargeur.load( new URLRequest ("donnees.xml") ); chargeur.addEventListener( Event.COMPLETE, chargementTermine ); function chargementTermine ( pEvt:Event ):void { Chapitre 20 ? ByteArray ? version 0.1.1 35 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org // accès au flux binaire XML var fluxXML:ByteArray = pEvt.target.data; // affiche : 1547.358 trace( fluxXML.length / 1024 ); } Puis nous sauvegardons le flux XML au sein d?un cookie permanent : var chargeur:URLLoader = new URLLoader(); chargeur.dataFormat = URLLoaderDataFormat.BINARY; chargeur.load( new URLRequest ("donnees.xml") ); chargeur.addEventListener( Event.COMPLETE, chargementTermine ); // crée un cookie permanent du nom de "cookie" var monCookie:SharedObject = SharedObject.getLocal("cookie"); function chargementTermine ( pEvt:Event ):void { // accès au flux binaire XML var fluxXML:ByteArray = pEvt.target.data; // affiche : 1547.358 trace( fluxXML.length / 1000 ); // écriture du flux XML dans le cookie monCookie.data.donneesXML = fluxXML; // sauvegarde du cookie sur le disque dur monCookie.flush(); } A l?appel de la méthode flush, le lecteur Flash tente de sauvegarder les données sur le disque. Le flux XML de large poids nécessite plus de 1 Mo d?espace disque et provoque l?ouverture du panneau Enregistrement local afin que l?utilisateur accepte la sauvegarde. La figure 20-16 illustre le panneau : Figure 20-16. Panneau Enregistrement local. Chapitre 20 ? ByteArray ? version 0.1.1 36 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Nous remarquons que le lecteur Flash nécessite alors une autorisation de 10 mo afin de sauver le cookie. 1548 Ko sont nécessaire à la sauvegarde du cookie : La figure 20-17 illustre l?espace actuellement utilisé par le cookie : Figure 20-17. Espace utilisé par le cookie permanent. En compressant simplement le flux XML à l?aide de la méthode compress, nous réduisons la taille du flux de près de 700% : function chargementTermine ( pEvt:Event ):void { // accès au flux binaire XML var fluxXML:ByteArray = pEvt.target.data; // affiche : 1547.358 trace( fluxXML.length / 1024 ); // compression du flux XML fluxXML.compress(); // affiche : 212.24 trace( fluxXML.length / 1024 ); // écriture du flux XML dans le cookie monCookie.data.donneesXML = fluxXML; // sauvegarde du cookie sur le disque dur monCookie.flush(); } En testant le code précédent, si nous affichons le panneau Enregistrement local, nous voyons que le cookie ne nécessite que 213 Ko d?espace disque : Chapitre 20 ? ByteArray ? version 0.1.1 37 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 20-18. Un octet stocké à l?index 0. Afin de lire le flux sauvegardé, nous devons appeler la méthode uncompress, puis extraire la chaîne compressée à l?aide de la méthode readUTFBytes : function chargementTermine ( pEvt:Event ):void { // accès au flux binaire XML var fluxXML:ByteArray = pEvt.target.data; // affiche : 1547.358 trace( fluxXML.length / 1024 ); fluxXML.compress(); // affiche : 212.24 trace( fluxXML.length / 1024 ); // ecriture du flux XML dans le cookie monCookie.data.donneesXML = fluxXML; // sauvegarde du cookie sur le disque dur monCookie.flush(); var fluxBinaireXML:ByteArray = monCookie.data.donneesXML; // décompression du flux XML binaire fluxXML.uncompress(); // lecture de la chaîne XML var chaineXML:String = fluxBinaireXML.readUTFBytes ( fluxBinaireXML.bytesAvailable ); // reconstitution d'un objet XML var donneesXML:XML = new XML ( chaineXML ); } Grâce à la chaîne extraite du tableau d?octets, nous récréons un objet XML utilisable. La méthode compress nous a permis de réduire le poids du fichier XML de près de 1335 Ko. A retenir Chapitre 20 ? ByteArray ? version 0.1.1 38 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode compress de la classe ByteArray permet la compression des données binaires à l?aide de l?algorithme zlib. ? La méthode uncompress permet de décompresser le flux. Sauvegarder un flux binaire Le lecteur Flash 9 n?a pas la capacité d?exporter un flux généré en ActionScript 3 par la classe FileReference. Il serait intéressant de pouvoir passer à la méthode download de l?objet FileReference un tableau d?octets afin que le lecteur nous propose de sauver le flux, mais une telle fonctionnalité n?est pas présente dans le lecteur Flash 9. Souvenez-vous, au cours du précédent chapitre nous avons exporté une image du même type grâce à AMFPHP. Dans l?exemple suivant nous allons exporter le tableau d?octets sans utiliser AMFPHP. La première étape consiste à utiliser une classe générant un fichier spécifique telle une image, une archive zip, ou n?importe quel type de fichier. Nous allons réutiliser la classe d?encodage EncodeurPNG que nous avons utilisé au cours du précédent chapitre. Rappelez-vous, la classe EncodeurPNG nécessite en paramètre, une image de type BitmapData afin d?encoder les pixels dans une enveloppe PNG. Nous créons dans le code suivant, une image d?une dimension de 320 par 240 pixels : // import de la classe EncodeurPNG import org.bytearray.encodage.images.EncodeurPNG ; // création d'une image var donneesBitmap:BitmapData = new BitmapData ( 320, 240, false, 0x990000 ); // encodage au format PNG var fluxBinairePNG:ByteArray = EncodeurPNG.encode ( donneesBitmap ); // affiche : 690 trace( fluxBinairePNG.length ); Nous obtenons une longueur de 690 octets. Souvenez, vous en début de chapitre, nous avions abordé la structure d?un fichier PNG. Pour exporter le flux nous devons transmettre par connexion HTTP le flux binaire, puis prévoir un script serveur afin de rendre le flux disponible en téléchargement par une fenêtre appropriée. Pour permettre cela, nous créons un fichier export.php contenant le code PHP suivant : <?php Chapitre 20 ? ByteArray ? version 0.1.1 39 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org if ( isset ( $GLOBALS["HTTP_RAW_POST_DATA"] )) { $flux = $GLOBALS["HTTP_RAW_POST_DATA"]; header('Content-Type: image/png'); header("Content-Disposition: attachment; filename=".$_GET['nom']); echo $flux; } else echo 'An error occured.'; ?> Nous sauvons le fichier export.php sur notre serveur local, puis nous transmettons le flux binaire au script distant en passant le tableau d?octets à la propriété data de l?objet URLRequest : // import de la classe EncodeurPNG import org.bytearray.encodage.images.EncodeurPNG ; // création d'une image var donneesBitmap:BitmapData = new BitmapData ( 320, 240, false, 0x990000 ); // encodage au format PNG var fluxBinairePNG:ByteArray = EncodeurPNG.encode ( donneesBitmap ); // entête HTTP au format brut var enteteHTTP:URLRequestHeader = new URLRequestHeader ("Content-type", "application/octet-stream"); var requete:URLRequest = new URLRequest("http://localhost/export_image/export.php?nom=sketch.jpg"); requete.requestHeaders.push(enteteHTTP); requete.method = URLRequestMethod.POST; requete.data = fluxBinairePNG; navigateToURL(requete, "_blank"); Notons que grâce à la classe à la classe URLRequestHeader, nous indiquons au lecteur Flash de ne pas traiter les données à transmettre en HTTP en tant que chaîne mais en tant que flux binaire. Souvenez-vous, lors du chapitre 14 intitulé Charger et envoyer des données, nous avions découvert qu?il était impossible depuis l?environnement de développement de Flash, d?utiliser la méthode POST en utilisant la fonction navigateToURL. Il est donc obligatoire de tester le code précédent au sein d?une page navigateur. Dans le cas contraire, le flux binaire sera transmis par la méthode GET et sera donc traité telle une chaîne de caractères. Chapitre 20 ? ByteArray ? version 0.1.1 40 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous avions voulu sauver l?image sur le serveur, nous aurions utilisé le code PHP suivant au sein du fichier export.php : <?php if ( isset ( $GLOBALS["HTTP_RAW_POST_DATA"] )) { $flux = $GLOBALS["HTTP_RAW_POST_DATA"]; $fp = fopen($_GET['nom'], 'wb'); fwrite($fp, $im); fclose($fp); } ?> Bien entendu, le code précédent peut être modifié afin de sauver le flux binaire dans un répertoire spécifique. A retenir ? Le lecteur Flash est incapable d?exporter un flux binaire sans un script serveur. ? Lors de l?envoi de données binaire depuis le lecteur Flash, nous devons obligatoirement utiliser la classe URLRequestHeader afin de préciser que les données transmises sont de type binaire. ? Les données binaires arrivent en PHP au sein du la propriété HTTP_RAW_POST_DATA du tableau $GLOBALS : $GLOBALS["HTTP_RAW_POST_DATA"]. Générer un PDF Afin de démontrer l?intérêt de la classe ByteArray dans un projet réel, nous allons générer un fichier PDF dynamiquement en ActionScript 3. Pour cela, nous allons utiliser la librairie AlivePDF qui s?appuie sur la classe ByteArray pour générer le fichier PDF. En réalité il est aussi possible de générer un fichier PDF en ActionScript 1 et 2 sans avoir recours à la classe ByteArray, car le format PDF est basé sur une simple chaîne de caractères. En revanche l?intégration d?images ou autres fichiers nécessite une manipulation des données binaire ce qui est réservé à ActionScript 3. Voici le contenu d?un fichier PDF ouvert avec le bloc notes : %PDF ?1.4 1 0 obj << /Type /Catalog /Outlines 2 0 R /Pages 3 0 R >> Chapitre 20 ? ByteArray ? version 0.1.1 41 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org endobj 2 0 obj << /Type Outlines /Count 0 >> endobj 3 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 4 0 obj << /Type /Page /Parent 3 0 R /MediaBox [0 0 612 792] /Contents 5 0 R /Resources << /ProcSet 6 0 R >> >> endobj 5 0 obj << /Length 35 >> stream ?Page-marking operators? endstream endobj 6 0 obj [/PDF] endobj xref 0 7 0000000000 65535 f 0000000009 00000 n 0000000074 00000 n 0000000120 00000 n 0000000179 00000 n 0000000300 00000 n 0000000384 00000 n trailer << /Size 7 /Root 1 0 R >> startxref 408 %%EOF Afin de générer un PDF en ActionScript nous utilisons la librairie AlivePDF disponible à l?adresse suivante : http://code.google.com/p/alivepdf/downloads/list Nous allons utiliser la version 0.1.4 afin de créer un PDF de deux pages contenant du texte sur la première page, puis une image sur la deuxième page. Une fois les sources de la librairie téléchargées. Nous plaçons le répertoire org contenant les classes à côté d?un nouveau document FLA, puis nous importons la classe PDF : Chapitre 20 ? ByteArray ? version 0.1.1 42 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org import org.alivepdf.pdf.PDF; import org.alivepdf.layout.Orientation; import org.alivepdf.layout.Unit; import org.alivepdf.layout.Size; import org.alivepdf.layout.Layout; import org.alivepdf.display.Display; import org.alivepdf.saving.Download; import org.alivepdf.saving.Method; var myPDF:PDF = new PDF ( Orientation.PORTRAIT, Unit.MM, Size.A4 ); Nous définissons le mode d?affichage du PDF : myPDF.setDisplayMode( Display.FULL_PAGE, Layout.SINGLE_PAGE ); Puis nous ajoutons une nouvelle page à l?aide de la méthode addPage : myPDF.addPage(); Enfin nous sauvons le PDF : myPDF.savePDF ( Method.REMOTE, 'http://localhost/pdf/create.php', Download.ATTACHMENT, 'monPDF.pdf' ); Nous devons passer en deuxième paramètre à la méthode savePDF, l?adresse du script create.php présent dans les sources du projet AlivePDF. A l?exécution, le SWF ouvre la fenêtre illustrée en figure 20-19 : Figure 20-19. Sauvegarde du document PDF. A l?ouverture le PDF contient une seule page blanche. Afin d?enrichir notre document PDF, nous allons ajouter du texte, en important deux nouvelles classes : Chapitre 20 ? ByteArray ? version 0.1.1 43 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org import org.alivepdf.fonts.Style; import org.alivepdf.fonts.FontFamily; import org.alivepdf.colors.RGBColor; Puis nous utilisons les différentes méthodes d?écriture de texte : myPDF.textStyle ( new RGBColor ( 255, 100, 0 ) ); myPDF.setFont( FontFamily.HELVETICA, Style.BOLD ); myPDF.setFontSize ( 20 ); myPDF.addText ('Voilà du texte !', 70, 12); myPDF.addLink ( 70, 4, 52, 16, "http://alivepdf.bytearray.org"); En testant le code précédent, un document PDF est généré, à l?ouverture, le texte est affiché et redirige sur le site du projet AlivePDF lors du clic. La figure 20-20 illustre le résultat : Figure 20-20. Texte intégré au PDF. Afin d?intégrer une image, nous importons une image dans la bibliothèque, puis nous l?associons à une classe nommée Logo. Grâce à la méthode addImage, nous intégrons l?image bitmap en deuxième page du PDF : myPDF.addPage(); var donneesBitmap:Logo = new Logo(0,0); var imageLogo:Bitmap = new Bitmap ( donneesBitmap ); myPDF.addImage ( imageLogo ); La figure 20-21 illustre le résultat : Figure 20-21. Image intégrée au PDF. Chapitre 20 ? ByteArray ? version 0.1.1 44 / 44 Thibault Imbert pratiqueactionscript3.bytearray.org Ainsi se termine notre aventure binaire. Au cours du prochain chapitre, nous mettrons en application la plupart des concepts abordés durant l?ensemble de l?ouvrage. Chapitre 21 ? Application finale ? version 0.1 1 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org 21 Application finale DECOMPOSER L?APPLICATION ...................................................................... 1 GERER LES CHEMINS D?ACCES ...................................................................... 3 CREATION D?UN FICHIER XML DE CONFIGURATION ............................................... 3 UTILISER DES LIBRAIRIES PARTAGEES ...................................................... 6 GENERER ET CHARGER LES LIBRAIRIES ................................................................... 8 UTILISER LES LIBRAIRIES PARTAGEES ................................................................... 15 Décomposer l?application Au cours de ce chapitre, nous allons nous intéresser aux différents points essentiels à chaque application ActionScript 3. Afin de permettre à chacun d?apprécier ce chapitre, nous ne traiterons pas de cas particulier mais nous nous attarderons sur différents concepts réutilisables pour chaque type d?application. Le premier point que nous allons aborder concerne la décomposition de notre application. La première approche consiste à ce que l?application se précharge elle-même, cette technique s?avère limitée car nous devons nous assurer qu?un minimum d?éléments soient exporté sur la première image de notre application, au risque de voir notre barre de préchargement s?afficher à partir de 50%. Nous allons donc opter pour une deuxième technique en utilisant un premier SWF qui se chargera de précharger notre application En utilisant cette approche, nous devons nous assurer que l?application préchargeant notre application soit la plus légère possible afin de ne pas nécessiter un préchargement elle aussi. Chapitre 21 ? Application finale ? version 0.1 2 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 21-1 illustre le principe : Figure 21-1. Chargement de l?application. Chaque SWF possède une classe de document associée la figure 21-2 illustre l?idée : Figure 21-2. Classe de document. Nous allons définir une première classe de document nommée Prechargeur pour le SWF de préchargement. Celle-ci va se charger de charger tous les éléments nécessaires à notre application. A retenir Chapitre 21 ? Application finale ? version 0.1 3 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org ? Il est préférable d?utiliser un SWF dédié au préchargement de notre application. ? Ce SWF se charge de charger tous les éléments nécessaires à notre application. Gérer les chemins d?accès Dans la plupart des applications ActionScript, un problème se pose éternellement lié aux chemins d?accès aux fichiers. Afin de simplifier cela nous allons définir au sein d?un fichier XML de configuration les chemins d?accès. La première chose que fera le SWF de préchargement sera de charger ce fichier XML de configuration afin de récupérer les chemins d?accès. Création d?un fichier XML de configuration Au sein d?un répertoire init nous créons un fichier XML nommé initialisation.xml contenant l?arbre XML suivant : <CONFIG> <CHEMIN_XML chemin="xml/"/> <CHEMIN_IMAGES chemin="images/"/> <CHEMIN_SWF chemin="swf/"/> <CHEMIN_POLICES chemin="polices/"/> </CONFIG> Chaque n?ud définit un chemin associé à un type de médias. Nous spécifions sont emplacement par FlashVars au sein de la page HTML : <script language="javascript"> if (AC_FL_RunContent == 0) { alert("Cette page nécessite le fichier AC_RunActiveContent.js."); } else { AC_FL_RunContent( 'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9, 0,0,0', 'width', '550', 'height', '400', 'src', 'chap-21-prechargeur', 'flashvars', 'initChemin=init/', 'quality', 'high', 'pluginspage', 'http://www.macromedia.com/go/getflashplayer', 'align', 'middle', 'play', 'true', 'loop', 'true', 'scale', 'showall', 'wmode', 'window', 'devicefont', 'false', 'id', 'chap-21-prechargeur', 'bgcolor', '#ffffff', 'name', 'chap-21-prechargeur', Chapitre 21 ? Application finale ? version 0.1 4 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org 'menu', 'true', 'allowFullScreen', 'false', 'allowScriptAccess','sameDomain', 'movie', 'chap-21-prechargeur', 'salign', '' ); //end AC code } </script> Nous récupérons ce chemin afin d?indiquer au SWF de préchargement où se situe le XML de configuration. Nous associons la classe de document suivante à notre SWF de préchargement, nous réutilisons la classe ChargeurXML créée au cours du chapitre 14 intitulé Charger et envoyer des données : package org.bytearray.document { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.net.URLRequest; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.xml.ChargeurXML; public class Prechargeur extends ApplicationDefaut { // adresse du fichier de configuration private const INITIALISATION:String = "initialisation.xml"; // chemins private var cheminXML:String; private var cheminImages:String; private var cheminSWF:String; private var cheminPolices:String; // chargement private var chargeurInitialisation:ChargeurXML; public function Prechargeur () { chargeurInitialisation = new ChargeurXML (); chargeurInitialisation.addEventListener ( Event.COMPLETE, chargementInitTermine ); chargeurInitialisation.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeurInitialisation.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, erreurChargement ); var chemin:String = loaderInfo.parameters.initChemin; if ( chemin == null ) chemin = "init/"; Chapitre 21 ? Application finale ? version 0.1 5 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org chargeurInitialisation.charge ( new URLRequest ( chemin + INITIALISATION ) ); } private function chargementInitTermine ( pEvt:Event ):void { var initXML:XML = pEvt.target.donnees; cheminXML = initXML.CHEMIN_XML.@chemin; cheminImages = initXML.CHEMIN_IMAGES.@chemin; cheminSWF = initXML.CHEMIN_SWF.@chemin; cheminPolices = initXML.CHEMIN_POLICES.@chemin; // affiche : xml/ images/ swf/ polices/ trace( cheminXML, cheminImages, cheminSWF, cheminPolices ); } private function erreurChargement ( pEvt:Event ):void { trace ( pEvt ); } } } Le fichier de configuration est automatiquement chargé depuis son dossier. Grâce à la variable initChemin, nous pouvons passer dynamiquement le chemin d?accès au fichier de configuration. Afin de pouvoir tester notre application au sein de l?environnement auteur, nous avons ajouté le test suivant : if ( chemin == null ) chemin = "init/"; Ce test permet simplement de définir la variable chemin lorsque nous testons l?application lors du développement au sein de l?environnement auteur et que l?utilisation des FlashVars est impossible. Ainsi, il nous est facile de spécifier l?emplacement de ce fichier de configuration. Si lors du déploiement de notre application, le chargement d?éléments échoue, nous pouvons modifier les chemins grâce au fichier de configuration XML. A retenir Chapitre 21 ? Application finale ? version 0.1 6 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?utilisation d?un fichier XML de configuration est recommandée afin de pouvoir facilement spécifier les chemins d?accès aux fichiers. ? Le chemin d?accès au fichier de configuration XML est spécifié par FlashVars. ? Une fois l?application mise en production, la modification des chemins d?accès aux fichiers est simplifiée. Utiliser des librairies partagées Lors du développement d?une application ActionScript 3, celle-ci est généralement divisée en plusieurs modules. Nous devons donc prendre en considération plusieurs points. Le premier concerne l?optimisation du poids de l?application globale. Afin d?éviter de dupliquer les éléments graphiques et classes au sein des différents SWF nous allons utiliser le concept de librairie partagée abordée au cours du chapitre 13 intitulé Chargement de contenu. Souvenez-vous, en plaçant les définitions de classes au sein d?un SWF nous avions extrait dynamiquement la définition associée et utilisée la classe au sein de notre application. Nous allons utiliser ce mécanisme de librairie partagée pour les éléments graphiques ainsi que les polices utilisées. Les éléments graphiques seront chargés par le SWF de préchargement et rendues disponibles à tous les SWF constituant l?application. Nous économiserons ainsi du poids et gagnerons en facilité de mise à jour. En mettant à jour la librairie partagée, toute l?application sera mise à jour sans la nécessite d?être recompilée car tout sera extrait dynamiquement. Imaginons que nous souhaitions utiliser le logo d?une société au sein de l?application Si nous n?optimisons pas celle-ci, voici comment celle-ci serait organisée : Chapitre 21 ? Application finale ? version 0.1 7 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-3. Duplication du poids de l?image. Notre application est composée de nombreux SWF contenant chacun l?image, ce qui augmente sensiblement le poids total de l?application. En utilisant une librairie partagée, le SWF de préchargement va extraire les classes nécessaires et permettre aux différents SWF constituant l?application leur utilisation. Comme la figure 21-3 l?illustre, une image bitmap est utilisée par chacun des SWF. En dupliquant la classe BitmapData dans chaque SWF nous dupliquons et augmentons le poids de l?application globale ainsi que la bande passant utilisée. Nous allons générer un SWF contenant l?image à réutiliser puis la réutiliser au sein des différents SWF, la figure 21-4 illustre le résultat : Chapitre 21 ? Application finale ? version 0.1 8 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-4. Chargement de l?image à l?aide d?une librairie partagée. Grâce à ce procédé nous optimisons le poids de l?application mais bénéficions d?un autre avantage lié à la mise à jour de notre application. En centralisant les éléments chargés et en les réutilisant, nous facilitons la mise à jour de l?application. Nous allons nous intéresser à cette partie dans cette nouvelle partie. A retenir ? Afin d?optimiser la bande passante utilisée il est recommandé d?utiliser les librairies partagées. ? Leur utilisation permet de faciliter la mise à jour de l?application. ? Toutes les définitions de classes sont extraites dynamiquement, la mise à jour de l?application ne nécessite donc pas de recompilation. Générer et charger les librairies Afin de générer une librairie utilisable, nous créons un nouveau document Flash CS3 et importons une image dans la bibliothèque que nous associons à une classe nommée Logo. Une fois définie, nous exportons un SWF sous le nom de libraire.swf. Chapitre 21 ? Application finale ? version 0.1 9 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Afin d?utiliser les définitions de classes issues de la librairie, le SWF gérant le chargement doit d?abord charger la librairie partagée afin de rendre les classes disponibles par tous les SWF de l?application. Pour cela, nous devons charger la librairie dans le domaine d?application en cours. Souvenez-vous nous avions utilisé un objet LoaderContext lors du chapitre 13 afin de spécifier le domaine d?application dans lequel placer les classes chargées. Nous modifions la classe de document Prechargeur afin de charger en premier temps la librairie partagée, puis le SWF principal application.swf. Nous devons donc charger notre librairie dans un premier temps, puis charger le module application.swf. Afin de charger ce contenu de manière séquentielle, nous utilisons la classe de chargement séquentielle suivante : package org.bytearray.chargement { import flash.display.Loader; import flash.display.LoaderInfo; import flash.display.Sprite; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.net.URLRequest; import flash.system.ApplicationDomain; import flash.system.LoaderContext; public class ChargeurSequentiel extends Sprite { private var liste:Array; private var chargeur:Loader; private var contexte:LoaderContext; private var domaine:ApplicationDomain; public var contentLoaderInfo:LoaderInfo; public function ChargeurSequentiel ( pDomaine:ApplicationDomain ) { liste = new Array(); domaine = pDomaine; chargeur = new Loader(); contentLoaderInfo = chargeur.contentLoaderInfo; addChild ( chargeur ); contexte = new LoaderContext ( false, domaine ); Chapitre 21 ? Application finale ? version 0.1 10 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org chargeur.contentLoaderInfo.addEventListener ( Event.INIT, redirigeEvenement ); chargeur.contentLoaderInfo.addEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.contentLoaderInfo.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); chargeur.contentLoaderInfo.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, redirigeEvenement ); } private function chargementTermine ( pEvt:Event ):void { suivant(); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } public function ajoute ( pFichier:String ):void { liste.push ( new URLRequest ( pFichier ) ); } public function demarre ( ):void { suivant(); } private function suivant ( ):void { if ( liste.length ) chargeur.load ( liste.shift(), contexte ); else dispatchEvent ( new Event ( Event.COMPLETE ) ); } } } Chapitre 21 ? Application finale ? version 0.1 11 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Cette classe possède une méthode ajoute qui place chaque média en file d?attente, puis nous lançons le chargement des éléments à l?aide de la méthode demarre. Nous mettons à jour le code de la classe Prechargeur afin de charger la librairie partagée : package org.bytearray.document { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.net.URLRequest; import flash.system.ApplicationDomain; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.xml.ChargeurXML; import org.bytearray.chargement.ChargeurSequentiel; public class Prechargeur extends ApplicationDefaut { private const INITIALISATION:String = "initialisation.xml"; // chemins private var cheminXML:String; private var cheminImages:String; private var cheminSWF:String; private var cheminPolices:String; // chargement private var chargeurInitialisation:ChargeurXML; private var chargeur:ChargeurSequentiel; public function Prechargeur () { chargeur = new ChargeurSequentiel ( ApplicationDomain.currentDomain ); chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, erreurChargement ); addChild ( chargeur ); chargeurInitialisation = new ChargeurXML (); chargeurInitialisation.addEventListener ( Event.COMPLETE, chargementInitTermine ); chargeurInitialisation.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); Chapitre 21 ? Application finale ? version 0.1 12 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org chargeurInitialisation.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, erreurChargement ); var chemin:String = loaderInfo.parameters.initChemin; if ( chemin == null ) chemin = "init/"; chargeurInitialisation.charge ( new URLRequest ( chemin + INITIALISATION ) ); } private function chargementTermine ( pEvt:Event ):void { trace("chargement des libs et swf terminé !"); } private function chargementEnCours ( pEvt:ProgressEvent ):void { trace("chargement en cours"); } private function chargementInitTermine ( pEvt:Event ):void { var initXML:XML = pEvt.target.donnees; cheminXML = initXML.CHEMIN_XML.@chemin; cheminImages = initXML.CHEMIN_IMAGES.@chemin; cheminSWF = initXML.CHEMIN_SWF.@chemin; cheminPolices = initXML.CHEMIN_POLICES.@chemin; chargeur.ajoute ( cheminSWF + "librairie.swf" ); chargeur.demarre(); } private function erreurChargement ( pEvt:Event ):void { trace ( pEvt ); } } } Nous passons au constructeur de la classe ChargeurSequentiel le domaine d?application en cours. Ainsi les classes issues des SWF chargés dynamiquement sont placées au sein du domaine d?application en cours. Chapitre 21 ? Application finale ? version 0.1 13 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Afin d?indiquer l?état de chargement, nous ajoutons une barre de préchargement : package org.bytearray.document { import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.net.URLRequest; import flash.system.ApplicationDomain; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.evenements.EvenementInfos; import org.bytearray.xml.ChargeurXML; import org.bytearray.chargement.ChargeurSequentiel; public class Prechargeur extends ApplicationDefaut { private const INITIALISATION:String = "initialisation.xml"; // chemins private var cheminXML:String; private var cheminImages:String; private var cheminSWF:String; private var cheminPolices:String; // chargement private var chargeurInitialisation:ChargeurXML; private var chargeur:ChargeurSequentiel; // barre de préchargement private var jauge:Jauge; public function Prechargeur () { jauge = new Jauge(); jauge.x = int((stage.stageWidth - jauge.width) / 2); jauge.y = int((stage.stageHeight - jauge.height) / 2); chargeur = new ChargeurSequentiel ( ApplicationDomain.currentDomain ); chargeur.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); chargeur.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeur.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, erreurChargement ); addChild ( chargeur ); chargeurInitialisation = new ChargeurXML (); Chapitre 21 ? Application finale ? version 0.1 14 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org chargeurInitialisation.addEventListener ( Event.COMPLETE, chargementInitTermine ); chargeurInitialisation.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); chargeurInitialisation.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, erreurChargement ); var chemin:String = loaderInfo.parameters.initChemin; if ( chemin == null ) chemin = "init/"; chargeurInitialisation.charge ( new URLRequest ( chemin + INITIALISATION ) ); } private function chargementTermine ( pEvt:Event ):void { stage.removeChild ( jauge ); } private function chargementEnCours ( pEvt:ProgressEvent ):void { jauge.scaleX = pEvt.bytesLoaded / pEvt.bytesTotal; } private function chargementInitTermine ( pEvt:Event ):void { var initXML:XML = pEvt.target.donnees; cheminXML = initXML.CHEMIN_XML.@chemin; cheminImages = initXML.CHEMIN_IMAGES.@chemin; cheminSWF = initXML.CHEMIN_SWF.@chemin; cheminPolices = initXML.CHEMIN_POLICES.@chemin; chargeur.ajoute ( cheminSWF + "librairie.swf" ); chargeur.demarre(); stage.addChild ( jauge ); } private function erreurChargement ( pEvt:Event ):void { trace ( pEvt ); } } } Chapitre 21 ? Application finale ? version 0.1 15 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Le SWF de préchargement charge dans un premier temps la libraire contenant les définitions de classes : Figure 21-5. Préchargement des définitions de classes. Une fois les définitions chargées, celles-ci pourront être utilisées par les SWF constituant l?application. A retenir ? En chargeant la librairie partagée dans le domaine d?application en cours. Chaque SWF constituant l?application peut extraire les définitions de classe nécessaires. Utiliser les librairies partagées Au sein du SWF application.swf, nous associons la classe de document suivante : package org.bytearray.document { import flash.display.BitmapData; import flash.display.Bitmap; import flash.system.ApplicationDomain; import org.bytearray.abstrait.ApplicationDefaut; public class Application extends ApplicationDefaut { Chapitre 21 ? Application finale ? version 0.1 16 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org private var logo:BitmapData; private var domaineEnCours:ApplicationDomain; private const NOM_LOGO:String = "Logo"; public function Application () { domaineEnCours = ApplicationDomain.currentDomain; if ( domaineEnCours.hasDefinition ( NOM_LOGO ) ) { var defLogo:Class = Class ( domaineEnCours.getDefinition ( NOM_LOGO ) ); logo = new defLogo (0,0); addChild ( new Bitmap ( logo ) ); } } } } Nous extrayons dynamiquement la définition de classe Logo à l?aide de la méthode getDefinition puis nous l?instancions. Puis le SWF application.swf est chargé en modifiant la classe de document du SWF de préchargement : private function chargementInitTermine ( pEvt:Event ):void { var initXML:XML = pEvt.target.donnees; cheminXML = initXML.CHEMIN_XML.@chemin; cheminImages = initXML.CHEMIN_IMAGES.@chemin; cheminSWF = initXML.CHEMIN_SWF.@chemin; cheminPolices = initXML.CHEMIN_POLICES.@chemin; chargeur.ajoute ( cheminSWF + "librairie.swf" ); chargeur.ajoute ( cheminSWF + "application.swf" ); chargeur.demarre(); stage.addChild ( jauge ); } En testant notre application, le SWF de préchargement charge la librairie partagée puis charge le SWF application.swf comme l?illustre la figure 21-6 : Chapitre 21 ? Application finale ? version 0.1 17 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-6. Utilisation de la définition de classe Logo. Afin de nous rendre compte de la facilité de mise à jour de notre application nous ouvrons le fichier librairie.fla et modifions l?image Logo dans la bibliothèque. Nous exportons à nouveau la librairie partagée puis nous rechargeons notre site sans aucune recompilation, la figure 21-7 illustre le résultat : Chapitre 21 ? Application finale ? version 0.1 18 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-7. Remplacement de l?image à travers une librairie partagée. Sans recompiler notre application nous avons mis à jour le logo utilisé au sein du module application.swf. Bien que cette pratique ait un avantage certain en matière de poids et de mise à jour, celle-ci possède néanmoins un désavantage. Nous sommes obligés de passer par le SWF de préchargement afin de tester notre module SWF car les définitions de classes utilisées par ce dernier sont chargées par le module de préchargement principal. En testant notre application nous remarquons que la définition de classe Logo est correctement extraire et utilisée au sein du SWF application.swf. Nous allons à présent développer un menu au sein de notre module application.swf. Ce menu aura pour but de charger les autres modules portfolio.swf, infos.swf et contact.swf. Nous allons utiliser la même technique afin de charger dynamiquement les polices de notre menu. Pour cela, nous intégrons au sein de la librairie une police au sein de la bibliothèque. Nous obtenons donc deux éléments dans notre librairie : ? Une image bitmap représentant notre logo Chapitre 21 ? Application finale ? version 0.1 19 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org ? Une police utilisée par notre menu La figure 21-8 illustre la bibliothèque : Figure 21-8. Bibliothèque de la librairie partagée. Comme nous l?avons vu lors du chapitre 16 intitulé Le texte, les polices peuvent être embarquées à l?exécution. De la même manière que notre image bitmap, nous allons extraire la définition de classe de police et ajouter celle-ci dans la liste des polices disponibles. Chapitre 21 ? Application finale ? version 0.1 20 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 21-9 illustre l?idée : Figure 21-9. Chargement d?une police à l?aide d?une librairie partagée. Nous modifions la classe Application afin d?extraire la police et l?ajouter : package org.bytearray.document { import flash.display.BitmapData; import flash.display.Bitmap; import flash.system.ApplicationDomain; import flash.text.Font; import flash.text.TextField; import flash.text.TextFormat; import org.bytearray.abstrait.ApplicationDefaut; public class Application extends ApplicationDefaut { private var logo:BitmapData; private var police:Font; private const NOM_LOGO:String = "Logo"; private const NOM_POLICE:String = "PoliceMenu"; public function Application () { Chapitre 21 ? Application finale ? version 0.1 21 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org if ( ApplicationDomain.currentDomain.hasDefinition ( NOM_LOGO ) ) { var defLogo:Class = Class ( ApplicationDomain.currentDomain.getDefinition ( NOM_LOGO ) ); logo = new defLogo (0,0); addChild ( new Bitmap ( logo ) ); } if ( ApplicationDomain.currentDomain.hasDefinition ( NOM_POLICE )) { var defPolice:Class = Class ( ApplicationDomain.currentDomain.getDefinition ( NOM_POLICE ) ); police = new defPolice(); Font.registerFont ( defPolice ); } } } } Afin d?utiliser la police extraite, nous créons un menu dynamique. Nous avons donc besoin de charger un fichier XML contenant les chemins d?accès au XML. Pour cela, nous allons diffuser un événement depuis le SWF de préchargement, cela va nous permettre de passer au module application.swf les chemins d?accès aux fichiers. Nous définissons un fichier XML nommé donnees.xml : <MENU> <BOUTON legende="Accueil" url="accueil.swf"/> <BOUTON legende="Photos" url="photos.swf"/> <BOUTON legende="Blog" url="blog.swf"/> </MENU> Puis nous créons une classe événementielle EvenementInfos contenant les différentes propriétés indiquant les chemins : package org.bytearray.evenements { import flash.events.Event; public class EvenementInfos extends Event { Chapitre 21 ? Application finale ? version 0.1 22 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org public static const CHEMINS:String = "chemins"; public var images:String; public var polices:String; public var swf:String; public var xml:String; public function EvenementInfos ( pType:String, pCheminImages:String, pCheminPolices:String, pCheminSWF:String, pCheminXML:String ) { super ( pType ); images = pCheminImages; polices = pCheminPolices; swf = pCheminSWF; xml = pCheminXML; } } } Le SWF de préchargement doit donc indiquer au SWF principal les chemins d?accès aux fichiers, pour cela nous diffusons un événement au module application.swf une fois les fichiers chargés : private function chargementTermine ( pEvt:Event ):void { stage.removeChild ( jauge ); pEvt.target.contentLoaderInfo.sharedEvents.dispatchEvent ( new EvenementInfos ( EvenementInfos.CHEMINS , cheminImages, cheminPolices, cheminSWF, cheminXML ) ); } Notre SWF application.swf n?a plus qu?à écouter cet événement et enregistrer les chemins puis charger le XML afin de créer le menu : package org.bytearray.document { import flash.display.BitmapData; import flash.display.Bitmap; import flash.display.Loader; import flash.display.Sprite; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.MouseEvent; import flash.events.SecurityErrorEvent; import flash.net.URLRequest; import flash.system.ApplicationDomain; import flash.text.Font; import flash.text.TextFormat; import org.bytearray.evenements.EvenementInfos; Chapitre 21 ? Application finale ? version 0.1 23 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org import org.bytearray.xml.ChargeurXML; import org.bytearray.abstrait.ApplicationDefaut; public class Application extends ApplicationDefaut { private var logo:BitmapData; private var police:Font; private var donneesXML:ChargeurXML; private var domaineEnCours:ApplicationDomain; private var conteneurMenu:Sprite; private var chargeur:Loader; private const NOM_LOGO:String = "Logo"; private const NOM_POLICE:String = "PoliceMenu"; private const MENU_XML:String = "donnees.xml"; private var cheminXML:String; private var cheminImages:String; private var cheminSWF:String; private var cheminPolices:String; public function Application () { loaderInfo.sharedEvents.addEventListener( EvenementInfos.CHEMINS, initialisation ); chargeur = new Loader(); addChild ( chargeur ); conteneurMenu = new Sprite(); conteneurMenu.addEventListener ( MouseEvent.CLICK, clicBouton, true ); conteneurMenu.x = 110; conteneurMenu.y = 25; addChildAt ( conteneurMenu, 0 ); donneesXML = new ChargeurXML ( true ); donneesXML.addEventListener ( Event.COMPLETE, chargementTermine ); donneesXML.addEventListener ( IOErrorEvent.IO_ERROR, erreurChargement ); donneesXML.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, erreurChargement ); domaineEnCours = ApplicationDomain.currentDomain; if ( domaineEnCours.hasDefinition ( NOM_LOGO ) ) { var defLogo:Class = Class ( domaineEnCours.getDefinition ( NOM_LOGO ) ); Chapitre 21 ? Application finale ? version 0.1 24 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org logo = new defLogo (0,0); addChild ( new Bitmap ( logo ) ); } } private function initialisation ( pEvt:EvenementInfos ):void { cheminXML = pEvt.xml; cheminImages = pEvt.images; cheminSWF = pEvt.swf; cheminPolices = pEvt.polices; if ( domaineEnCours.hasDefinition ( NOM_POLICE )) { var defPolice:Class = Class ( domaineEnCours.getDefinition ( NOM_POLICE ) ); police = new defPolice(); Font.registerFont ( defPolice ); donneesXML.charge ( new URLRequest ( cheminXML + MENU_XML ) ); } } private function chargementTermine ( pEvt:Event ):void { var donnees:XML = pEvt.target.donnees; var boutonMenu:Bouton; var i:int = 0; var formatage:TextFormat = new TextFormat ( police.fontName, 8 ); for each ( var noeud:XML in donnees.* ) { boutonMenu = new Bouton(); boutonMenu.buttonMode = true; boutonMenu.mouseChildren = false; boutonMenu.lien = noeud.@url; boutonMenu.legende.embedFonts = true; boutonMenu.legende.defaultTextFormat = formatage; boutonMenu.legende.text = noeud.@legende; Chapitre 21 ? Application finale ? version 0.1 25 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org boutonMenu.x = (boutonMenu.width + 5) * i; conteneurMenu.addChild ( boutonMenu ); i++; } } private function clicBouton ( pEvt:MouseEvent ):void { chargeur.load ( new URLRequest ( cheminSWF + pEvt.target.lien ) ); } private function erreurChargement ( pEvt:Event ):void { trace(pEvt); } } } En utilisant un nom de police générique, nous pouvons ainsi remplacer plus tard la police utilisée pour le menu par une autre sans créer des erreurs de logique Ainsi une police Verdana, Times ou autre pourra être utilisée de manière transparente pour désigner la police du menu. Lorsque nous testons le module application.swf, nous ne voyons pas le menu affiché. A l?inverse, en testant le SWF par le module principal nous obtenons le résultat suivant : Chapitre 21 ? Application finale ? version 0.1 26 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-10. Utilisation de la police au sein du menu dynamique. La police est ainsi intégrée au sein du site et chargée une seule et unique fois. Nous économisons bande passante et gagnons en facilité de mise à jour. Afin de mettre à jour la police, nous modifions la police présente au sein de la bibliothèque et régénérons le fichier librairie.swf. La figure 21-11 illustre l?application relancée en ayant modifié la police présente au sein de la librairie partagée : Chapitre 21 ? Application finale ? version 0.1 27 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-11. Remplacement de la police à travers la librairie partagée. La police extraite est désormais disponible au sein de tous les SWF de l?application. En cliquant sur chacun des boutons nous chargeons de nouveaux SWF ayant recours à cette police. Nous ajoutons au sein de la librairie partagée une nouvelle police destinée à être utilisée par le contenu. Celle-ci est associée à une classe PoliceContenu. La figure 21-12 illustre la bibliothèque de la librairie partagée : Chapitre 21 ? Application finale ? version 0.1 28 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 21-12. Bibliothèque de la librairie partagée. Chaque bouton du menu charge un SWF représentant une partie du site, ici encore chaque SWF va utiliser les définitions de polices chargées le SWF de préchargement. Nous évitons ainsi d?intégrer des polices au sein de chaque SWF et optimisons le poids du site. En cliquant sur le bouton Accueil nous chargeons le SWF accueil.swf. Nous ajoutons au sein de ce dernier le code suivant afin de créer un champ texte et d?utiliser la définition de police PoliceContenu : var domaineEnCours:ApplicationDomain = ApplicationDomain.currentDomain; var policeContenu:String = "PoliceContenu"; if ( domaineEnCours.hasDefinition ( policeContenu ) ) { var defPolice:Class = Class ( domaineEnCours.getDefinition ( policeContenu ) ); var police:Font = new defPolice(); var formatage:TextFormat = new TextFormat ( police.fontName, 8 ); Chapitre 21 ? Application finale ? version 0.1 29 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Font.registerFont ( defPolice ); var monContenu:TextField = new TextField() addChild ( monContenu ); monContenu.x = 40; monContenu.y = 110; monContenu.embedFonts = true; monContenu.defaultTextFormat = formatage; monContenu.autoSize = TextFieldAutoSize.LEFT; monContenu.wordWrap = true; monContenu.width = 450; monContenu.text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis consectetuer enim fringilla ligula. Donec facilisis scelerisque sem. Nunc lobortis ante eget turpis. Donec auctor ullamcorper diam. Phasellus sed enim non urna aliquam consectetuer. Morbi sit amet purus. Suspendisse eros leo, volutpat eu, eleifend sit amet, bibendum ac, lacus. Suspendisse elit. In egestas lorem in nisi. Integer imperdiet felis et ligula. Quisque semper dapibus pede. Mauris ac neque vel erat porttitor hendrerit. Duis justo augue, scelerisque a, rutrum nec, rutrum ut, justo. Maecenas luctus. Aenean a leo et eros blandit sollicitudin. Sed bibendum placerat ligula. Integer varius. Aliquam in felis in felis convallis laoreet. Vivamus nulla. Quisque rutrum massa id nunc." } Nous extrayons dynamiquement la police PoliceContenu, la figure 21-13 illustre le résultat : Figure 21-13. Utilisation de la police au sein du texte de présentation. Chapitre 21 ? Application finale ? version 0.1 30 / 30 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de tester notre mécanisme, nous modifions le fichier de police puis nous relançons notre site sans recompiler les autres SWF constituant l?application. Nous obtenons le résultat illustré par la figure suivante : Figure 21-14. Remplacement de la police à travers la librairie partagée. De cette manière nous centralisons nos images ainsi que les polices par le biais de la librairie partagée. La mise à jour de l?application est ainsi simplifiée et les temps de chargement optimisés. A retenir ? Afin d?extraire dynamiquement une classe nous utilisons la méthode getDefinition de l?objet ApplicationDomain. Ainsi s?achève notre aventure au c?ur de l?ActionScript 3. Rendez- vous sur le forum dédié à l?ouvrage pour en discuter et ainsi répondre à d?éventuelles questions. A très vite !

PARTAGER SUR

Envoyer le lien par email
2733
READS
10
DOWN
7
FOLLOW
7
EMBED
DOCUMENT # TAGS
#flash  #action script  #développement flash 

licence non indiquée


DOCUMENT # INDEX
Divers 
img

Partagé par  mistigri

 Suivre

Auteur:T.Imbert
Source:Non communiquée