Retour d'expérience sur la création d'une app flash/html5 avec le plugin OpenFL pour FlashCC

📅 February 15, 2016


🇬🇧 If you want to read this in english. Checkout the openfl forum.


Introduction

Il ne s'agit pas ici d'un tutoriel mais d'un retour d'expérience sur la création d'une application multi-plateforme avec un seul code source via le langage Haxe :

  • html5 pour tablettes tactiles
  • flash1 pour navigateurs de bureau sous IE8

En effet, n'attendez pas de pouvoir faire du html5 pour IE8, et faire le grand écart entre plusieurs versions de html, js et css n'est pas une bonne idée. Flash est encore la solution aux disparités d'implémentation des normes web.

Au programme, quelques problèmes rencontrés et leurs solutions basées sur les librairies openfl 3.3.2 et swf 1.9.1. Il est cependant possible que depuis, les librairies aient été améliorées alors ne prenez pas cela pour définitif.

Plugin OpenFL pour flashCC

Le plugin est téléchargeable sur le site d'Adobe gratuitement2. Il faudra cependant s'abonner à FlashCC (je n'ai pas testé le plugin sur AnimateCC) le temps du projet (29.99EUR HT/mois pour la formule mensuelle). Une fois le plugin installé, un nouveau type de projet apparaitra. Cela permettra d'exporter les graphismes et animations dans un format compatible avec OpenFL.

new openfl document on FlashCC

Support des options dans FlashCC

Tout d'abord, il faut savoir qu'à l'heure actuelle, beaucoup de fonctions sont désactivées dans FlashCC pour que le plugin fonctionne. C'est le cas notamment des BlendModes et des filtres.

no support of blendModes with OpenFL plugin for FlashCC no support of effects with OpenFL plugin for FlashCC

Ainsi, quand on importe dans notre projet OpenFL/FlashCC des graphismes venant d'Illustrator par exemple, ces options sont supprimées. L'image de gauche comporte des options (blendMode, alpha, ombre portée) tandis que l'image de droite est le résultat obtenu après import dans notre projet OpenFL/FlashCC.

example of MovieClip with blendMode and filter result of MovieClip with blendMode and filter when importing in OpenFL/FlashCC

La liste des incompatibilités est affichée lors de l'import. incompatibility warning when importing on openfl/flashCC

Ainsi, pour produire ce type d'image dans notre application flash/html5, nous avons 2 solutions :

  • Recréer les effets en code. Attention les BlendModes ne fonctionnent pas en html5 (sauf webgl)
  • Importer en tant qu'image bitmap Import as bitmap

Choix du type de rendu html5

Ayant d'abord écarté le Webgl à cause de son manque de support sur tablette. J'ai ensuite écarté le Canvas car diffuser une vidéo à l'intérieur d'un Canvas sur tablette semblait ne pas fonctionner. Finalement, après quelques essais en Dom, les performances globales de mon application n'étaient pas satisfaisantes. Je suis donc reparti pour le Canvas mais en sortant la vidéo du Canvas (cf partie vidéo).

Vectoriel

L'utilisation de FlashCC pour un projet openFL permet d'utiliser des images vectorielles facilement. Cependant, il arrive parfois qu'on obtienne un résultat quelque peu différent quand on compile pour html5. Les formes apparaissent souvent "fermées" sur cette plateforme (à gauche la forme souhaitée, à droite la forme obtenue en html5).

example of vector shapes example of vector shapes bug in html5

Encore une fois, 2 solutions s'imposent à nous :

  • Convertir les images vectorielles posant problème en Bitmap. Evidemment, on oublie alors le bénéfice du vectoriel.
  • Utiliser ces correctifs.

Les polices de caractères

L'affichage de champs textes créés à partir de FlashCC posent problème en flash. En effet, les champs textes flash n'utilisent pas les polices incorporées. Bizarrement, la propriété embedFonts est paramétrée à false. Forcer son paramétrage à true fait disparaître le texte. La seule solution que j'ai trouvé est de copier les propriétés du champs texte dans un nouveau TextField en mettant bien embedFonts à true (cf bug).

J'ai fait un script si jamais vous rencontrez le même problème :

package;
import openfl.display.DisplayObjectContainer;
import openfl.text.TextField;
/**
* ...
* @author loudo
*/
class FixText
{
public static var fixTxt:TextField;
public static var parent:DisplayObjectContainer;
public function new()
{
}
/**
* Re-create the textfield in flash to fix font embedding issue on textfield made with FlashCC (https://github.com/openfl/swf/issues/56)
* @param txt textfield
* @param text text of the textfield
* @param html use htmlText
*/
public static function fixFont(txt:TextField, text:String, html:Bool = false):TextField
{
#if flash
if (!txt.embedFonts)
{
fixTxt = new TextField();
fixTxt.name = txt.name;
fixTxt.defaultTextFormat = txt.defaultTextFormat;
fixTxt.embedFonts = true;
fixTxt.x = txt.x;
fixTxt.y = txt.y + Math.round(fixTxt.defaultTextFormat.size/5);//fix because new textfield seems not be placed in the same position
fixTxt.width = txt.width;
fixTxt.height = txt.height;
fixTxt.multiline = txt.multiline;
fixTxt.wordWrap = txt.wordWrap;
fixTxt.rotation = txt.rotation;
fixTxt.selectable = txt.selectable;
parent = txt.parent;
parent.removeChild(txt);
parent.addChild(fixTxt);
if(!html)
fixTxt.text = text;
else
fixTxt.htmlText = text;
return fixTxt;
}else {
if(!html)
txt.text = text;
else
txt.htmlText = text;
return txt;
}
#else
if(!html)
txt.text = text;
else
txt.htmlText = text;
return txt;
#end
}
/*
*
*/
inline public static function fixReturn(text:String):String
{
return StringTools.replace(text, '\n', '');
}
}
view raw FixText.hx hosted with ❤ by GitHub

Rotation négative

Il semblerait que placer certains clips avec des rotations négatives posent problème. Certaines positions sont incorrectes. A éviter donc.

MovieClip

Dans de nombreux cas en html5, un simple clip à 2 états créé avec FlashCC ne fonctionne pas. Seule la 1ère frame semble utilisable. not working example of simple MovieClip with 2 states

Une solution simple quand cela arrive semble de garder l'image de la 1ère frame sur la seconde. working second example of simple MovieClip with 2 states

A noter aussi, que certaines animations réalisées sur FlashCC ne se lancent pas en html5. Il vaudra alors peut-être mieux les créer via la librairie Actuate. FlashCC servant alors seulement à prototyper ses animations.

La vidéo

La gestion de la vidéo avec OpenFL est légèrement différente selon la plateforme. La manière de récupérer la durée et les statuts de la vidéo est différente. Flash utilise onMetaData et l'évènement NetStatusEvent.NET_STATUS tandis que html5 utilise onPlayStatus qui agit comme NetStatusEvent.NET_STATUS avec des noms de statuts parfois différents. Le snippet suivant montre la manière dont j'ai créé ma vidéo.

/**
* OpenFL Video help file for flash/html5
* openfl=3.5.3
* lime=2.8.3
*/
//vars
var duration:Int;
var ns:NetStream;
var vid:Video;
//create video
vid = new Video(800,600);
var c:NetConnection = new NetConnection();
c.connect(null);
ns = new NetStream(c);
ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
ns.addEventListener(NetStatusEvent.NET_STATUS, onStatus);//seems to only works in flash
vid.attachNetStream(ns);
var meta:Dynamic = { };
//flash uses onMetaData and NetStatusEvent.NET_STATUS, html5 uses onPlayStatus.
#if flash
meta.onMetaData = function(meta:Object):Void
{
duration = meta.duration;
}
#elseif html5
meta.onPlayStatus = function(meta:Object):Void
{
handleVideoStatus(meta.code, meta);//NetStatusEvent.NET_STATUS doesn't work on html5, so we have to call this here
}
#end
ns.client = meta;
function asyncErrorHandler(e:AsyncErrorEvent):Void
{
trace('error '+e);
}
function onStatus(e:NetStatusEvent):Void
{
handleVideoStatus(e.info.code);
}
function handleVideoStatus(status:String, ?meta:Object):Void
{
switch(status)
{
case "NetStream.Play.canplay"://html5
duration = meta.duration;
case "NetStream.Play.timeupdate"://html5
handleProgression(meta.position);
case "NetStream.Play.Complete", "NetStream.Play.Stop"://html5 then flash
trace('video stopped')
#if flash
if(hasEventListener(Event.ENTER_FRAME))
removeEventListener(Event.ENTER_FRAME, onUpdate);
#end
}
}
function play():Void
{
ns.play(videoName);
#if flash
if(!hasEventListener(Event.ENTER_FRAME))
addEventListener(Event.ENTER_FRAME, onUpdate);
#end
}
#if flash
function onUpdate(e:Event):Void
{
handleProgression(Std.int(ns.time));
}
#end
function handleProgression(time:Int):Void
{
var progress:Float = time / duration;
}

Suite à quelques problèmes de performance dans la version html5 Dom, je suis finalement passé au Canvas. Les vidéos Canvas passant très mal sur mobile, il m'a fallu trouver comment afficher une vidéo en dehors du Canvas. J'ai donc créé une balise en pur javascript pour ensuite le manipuler depuis Haxe. Il faut pour cela modifier le template html (cf Partie template).

lime.embed ("openfl-content", ::WIN_WIDTH::, ::WIN_HEIGHT::, "::WIN_FLASHBACKGROUND::");
//create the video			
var video = document.createElement('video');
video.id = "videoDisplay";
video.autoPlay = false;
document.body.appendChild(video);

Puis j'ai légèrement modifié mon code Haxe. A noter que cela implique de masquer/afficher la vidéo au moment opportun, placer la vidéo à la bonne position et utiliser l'évènement Event.ENTER_FRAME qui est cette fois indispensable dans la version html5.

#if html5
var htmlVideo:VideoElement;
htmlVideo = cast(Browser.document.getElementById('videoDisplay'), VideoElement);	
htmlVideo.src = videoName;
htmlVideo.style.top = videoY + "px";
htmlVideo.style.left = videoX + "px";
htmlVideo.style.visibility = "hidden";
#end

function play():Void
{
	#if html5
	duration = Std.int(htmlVideo.duration);
	htmlVideo.play();
	htmlVideo.style.visibility = "visible";
	#end
}
function onUpdate(e:Event):Void
{
	#if html5
	handleProgression(Std.int(htmlVideo.currentTime) / duration);
	#end
}

Template Html et fichiers dépendants

Pour avoir un seul fichier html qui choisit la version de l'application à présenter (flash ou html5), il faudra modifier le template html pour qu'il corresponde à nos besoins. Celui se trouve dans le répertoire haxelib openfl\x,y,z\templates\html5\template. Ensuite, il suffit de l'indiquer dans le fichier project.xml avec ses fichiers dépendants.

<section if="html5">
	<template path="Assets/template/template.html" rename="index.html"  />
	<dependency path="Assets/dependencies/swfobject.js"  />
	<assets path="Assets/exportflash" rename="" embed="false" />
</section>

Paramètres

Pour gérer des paramètres d'application depuis le template html, il faudra utiliser une manière différente pour les deux plateformes :

Pour flash, il suffit d'utiliser les flashvars à l'aide de swfobject.

so = new SWFObject("flash.swf", "flash-content", "100%", "100%", "11", "#FFFFFF");
so.addVariable("myParameter", data);

En revanche, pour html5, il faut utiliser les métadonnées suivantes pour informer le compilateur de rendre cette fonction accessible en javascript.

//.hx
#if html5
 ("parameters.sendParameters")
public static function sendParameters(data:String):Void
{
	trace('parameters : ', data);
}
#end
//.html
lime.embed ("openfl-content", ::WIN\_WIDTH::, ::WIN\_HEIGHT::, "::WIN\_FLASHBACKGROUND::");
parameters.sendParameters(data);

Conclusion

Au final, mon application a été un succès et je suis très satisfait de mon expérience avec OpenFL. Mon avis est cependant mitigé concernant l'utilisation de FlashCC compte tenu de son coût et de ses limitations actuelles. Mais il est tout de même possible que je tente l'expérience une nouvelle fois puisque la création de template graphique avec FlashCC permet quand même un gain de temps, à condition de savoir où l'on met les pieds.

addendum 2023

Si 7 ans après la rédaction de cet article, il n'y a plus de raison de faire une application html5 ET flash à la fois, les vieux navigateurs ayant tous disparus, la technique s'est bien améliorée : les effets dans Animate sont mieux intégrés à Openfl et le vectoriel est parfaitement pris en charge. J'utilise encore parfois Animate pour intégrer rapidement des interfaces difficiles à faire en css, comme des cartes interactives, surtout quand les sources graphiques proviennent d'Illustrator car une bonne partie du travail d'intégration peut être fait d'un simple copié collé 😜.


  1. La technologie flash est encore utilisée pour le logiciel desktop et mobile. C'est désormais Harman, une filiale de Samsung qui gère le SDK AIR
  2. Il n'y a aujourd'hui plus besoin de plugin, qui n'existe plus, soit dit en passant. Il suffit de compiler un swf depuis l'application Adobe Animate