Díky haXe a již ozkoušenému swfmill si vytvoříme jednoduchý MP3 přehrávač, který čte data z playlistu v XML. Jedná se tedy o příklad složitější flashové aplikace, která pracuje s externími daty. Díky tomu může tento Flash reagovat na přání konečného uživatele a není nutné v praxi kompilovat pokaždé nový swf soubor, když si chceme přehrát jinou písničku. Stačí si v textové editoru přepsat playlist.
Využití takové funkčnosti je samozřejmě mnohem širší, například díky tomu mohou hry načítat různá externí data nebo si je ukládat (například žebříčky hráčů, ale i reklamu atd.)
MP3 přehrávač:
haXe
haXe vychází z MTASC (za jeho vývojem stojí také Nicolas Cannasse). MTASC se však omezil pouze na ActionScript 2 a s koncem tohoto jazyka jeho vývoj skončil. haXe na něj navazuje tím, že kromě jazyka ActionScript 2 nabízí i kompatibilitu s jazykem ActionScript 3 (byť ne tak docela, jak si dále uvedeme).
haXe je open-source jazyk, jehož kód lze kompilovat do swf souborů. Kromě toho lze kód kompilovat i do JavaScriptu nebo pro spuštění na serveru. Já ho ale používám výhradně pro kompilaci do swf, ostatní možnosti jsem nezkoušel. Přestože na úrovni kódu se haXe občas odlišuje od ActionSriptu, výsledná swf jsou kompatibilní na 100%. Proto se haXe nabízí jako další alternativa open source pro tvoření Flashe.
Příklady rozdílů v syntaxi kódu mezi haXe a ActionScriptem
Uvedu jen několik rozdílů.
Smyčka v AS3
for(var i=0;i<num;i++){ trace(i); }
Smyčka v haXe
for(i in 0...num){ trace(i); }
Typy dat
Rozdíly jsou v typech dat. ActionScript 2 má pro čísla jediný typ Number, ActionScript 3 má dva typy Int a Uint, v haXe těmto typům odpovídá Int a Float.
Pro převod mezi typy se používá haXe komponenta Std.
var x : Int = 456; var y : Float = 123.456; x = y; // nefunguje x = Std.int(y); // funguje
Důležitá změna, kterou si ukážeme také na našem příkladu, je v hierarchii objektů. Zatímco v ActionScriptu 2 začínáme u _root, v haXe je nejvyšší úroveň označena jako flash.Lib.current.
Seznam všech rozdílů najdete na stránkách projektu.
Ve srovnání sice chybí ActionScript 3, ale přesto tuto stránku doporučuji, protože vlastně shrnuje specifika jazyka haXe.
Po dlouhé praxi jsem si dokonce na zápis haXe natolik zvykl, že neznám kolikrát ekvivalenty pro ActionScript. Hlavní výhodou je, že zápis v haXe je standardní pro ActionScript 2 i 3, liší se pouze jednotlivé komponenty, které jsou pro oba jazyky jiné, a jejich metody (viz například uvedené datové typy, které jsou v AS2 a v AS3 odlišné). Díky tomu mohu snadněji vyvíjet aplikace i pro starší verze Flashe a případně je rozšiřovat o další funkcionalitu pro novější Flash. Do kódu mohu dokonce dávat podmínky pro kompilaci do různých verzí. Tady je příklad takové podmínky v kódu pro Flash 9, starší verze Flashe a JavaScriptu:
#if flash9 udelejNeco(); #elseif flash udelejNecojineho(); #elseif js neboSeNaToVykasli(); #end
V praxi mi úžasně pomáhají stránky s API haXe. Tam je pomocí stromu zobrazen kompletní přehled všech komponent haXe i s popisem detailního použití. Při programování mám tuto stránku otevřenou v prohlížeči a neustále ji konzultuji.
Spouští se:
$ haxe soubor.hxml
V soubor.hxml (který mám nejčastěji nazvaný jako compile.hxml) mám parametry kompilace:
-swf vyslednySoubor.swf //cesta k výslednému souboru -swf-version 8 //specifikuji číslo verze od 6 do 10 -swf-lib library.swf //cesta ke zdrojové knihovně, v našem případě ke knihovně, kterou jsme si vytvořili pomocí swfmill -main Main //cesta k hlavní třídě --flash-use-stage //pracuje s objekty na ploše, které máme umístěné ve zdrojové knihovně. Bez tohoto parametru by se nám nezobrazily objekty umístěné pomocí tagu <place> v knihovně utvořené pomocí SWFMILL
Další parametry pro kompilátor.
Dále si musím při nejmenším vytvořit textový soubor s hlavní třídou, kterou jsme si specifikovali v compile.hxml Ta bude obsahovat kód aplikace. Nazveme si ji tedy Main.hx:
class Main { static function main() { trace("Funguje."); } }
Na rozdíl od ActionScriptu mají všechny soubory v haXe koncovku .hx! ActionScript používá koncovku .as.
MP3 přehrávač s playlistem v XML
Grafický návrh aplikace
Nejdříve si v našem oblíbeném grafickém editoru vytvoříme základní návrh aplikace. Protože mým oblíbeným editorem je Inkscape, vytvořil jsem si základní návrh právě v něm. V návrhu jsou ovládací panely a textové pole. Ne všechno použiji v aplikaci, ale je dobré vše si předem nakreslit. Podle rozmístění objektů v návrhu, pak budu například určovat pozice jednotlivých prvků v swfmill.
Pro tlačítko Play jsem si vytvořil i variantu Pause. Chci totiž mít jediné tlačítko, které bude mít dvě pozice - pro přehrávání a pauzování.
Všechny prvky vyexportuji do PNG. (Export do PNG není nutný, nejnovější verze swfmill si dobře poradí s SVG soubory. Stačí pro každý prvek vytvořit samostatné SVG a to pak importovat přes swfmill stejným způsobem, jaký uvádíme pro PNG.)
SWFMILL
Další postup se liší od toho, co jsme si uvedli v předchozím dílu, kde jsme si nejdříve vytvořili program v ActionScriptu a ten pak vnutili přes swfmill do naší knihovny. Naopak. Nyní si nejdříve pomocí swfmill vytvoříme knihovnu s objekty a některé si už rovnou umístíme na plochu budoucího SWF souboru.
classes.xml
<?xml version="1.0" encoding="utf-8" ?> <movie width="250" height="20" framerate="20"> <background color="#FFFFFF"/> <frame> <library> <clip id="BtnPlay" import="assets/btnPlay.png"/> <clip id="BtnPause" import="assets/btnPause.png"/> <clip id="BtnStop" import="assets/btnStop.png"/> <clip id="BtnFw" import="assets/btnFw.png"/> <clip id="BtnBw" import="assets/btnBw.png"/> <clip id="pozadi" import="assets/pozadi.png"/> <font id="FontBitmap" import="assets/px_sans_nouveaux.ttf" glyphs="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ1234567890öüěščřňďžýáíéúůÖÜĚŠĎČŇŘŽÝÁÍÉÚŮ=-% .!?><&"" /> <textfield id="txt" width="150" height="20" size ="8" font="FontBitmap"/> <clip id="mcTxt"> <place id="txt" name="txt" depth="1" /> </clip> <clip id="BtnPlayPause"> <frame name="play"> <place id="BtnPlay" depth="1" /> </frame> <frame name="pause"> <place id="BtnPause" depth="1" /> </frame> </clip> </library> <place id="pozadi" depth="1" /> <place id="mcTxt" name="mcTxt" x="96" y="0" depth="2" /> </frame> </movie>
Zajímavé je, že se zde poprvé definuje jednoduchá pookénková animace pomocí tagu <frame>. Přeložím-li následující řádky do normálního jazyka, potom u BtnPlayPause (která rozšiřuje komponentu MovieClip) jsem vytvořil dvě okénka animace. Ty si mohu v ActionScriptu zpřístupnit pomocí metody .gotoAndStop (“název okénka”) :
<clip id="BtnPlayPause"> <frame name="play"> <place id="BtnPlay" depth="1" /> </frame> <frame name="pause"> <place id="BtnPause" depth="1" /> </frame> </clip>
Dále jsme na rozdíl od předchozího dílu umístili některé objekty na plochu pomocí tagu <place>. Dělám to proto, že se jedná o objekty, které nechci nějak složitě programovat. Je pro mě jednodušší je prostě umístit na scénu a pak u nich jen měnit hodnoty, nebo využívat jejich metody. Z toho důvodu jsem si například umístil pozadí a také textové pole txt, které je vloženo do MovieClipu mcTxt.
<place id="mcTxt" name="mcTxt" x="96" y="0" depth="2" />
Rada zkušeného
Při umísťování si dejte pozor, abyste objekty dávali vždy do jiné hloubky - parametr depth. Tady dělám nejčastější chyby a pak se divím, proč mé objekty nejsou vidět.
Zkompilujeme si XML soubor a tím si vytvoříme základní knihovnu pro další programování:
$ swfmill simple classes.xml classes.swf
haXe
Nyní se dostáváme k tomu nejzábavnějšímu - k programování v haXe, díky kterému si vytvoříme flashovou aplikaci schopnou zpracovat externí soubory.
compile.hxml
V tomto souboru si nastavíme parametry kompilace pro naši aplikaci. Protože jsme si už pomocí swfmill vytvořili zdrojovou knihovnu classes.swf, uvedeme na ni zde odkaz. Parametr --flash-use-stage se nám postará o to, abychom při kompilaci nepřišli o všechny objekty umístěné pomocí tagu <place>.
-swf build/MP3Player.swf -swf-lib classes.swf -main Main -swf-version 8 --flash-use-stage
Main.hx
Tohle je hlavní třída aplikace. Vytvořím v ní novou instanci našeho přehrávače MP3 - MPPlayer a tuto instanci si umístím na nejvyšší plochu - flash.Lib.current.
import MPPlayer; class Main { static function main() { flash.Lib.current.attachMovie("MPPlayer","_MPPlayer",flash.Lib.current.getNextHighestDepth()); } }
PlayerButton.hx
V této třídě si nastavíme obecné chování pro tlačítka MP3 přehrávače. Ta budou reagovat na pohyb myši tím, že se při přejetí zvýrazní. Dále si u nich nastavíme metody, které nám pohlídají, aby se na nich spouštěly události. Protože se mi v haXe nechtělo programovat vlastní knihovny pro události, stáhl jsem si pro tuto příležitost výborně nakódovanou knihovnu událostí z aplikace Mediator. Knihovny jsem jen mírně upravil a uložil do složky “events”. Rozbor jejich fungování je mimo záměr tohoto textu, mně jde prostě o to, abych si u každého tlačítka mohl volat metody addEventListener a dispatchEvent a tedy kontrolovat události na těchto tlačítkách.
Tento postup není úplně typický pro ActionScript 2. Teprve vyšší verze ActionScript 3 má plnou podporu spouštění událostí. Já jsem při programování Flashe začínal s vyšší verzí a natolik jsem si na události zvykl, že se je vždy snažím uplatnit i při programování v nižší verzi. Ve většině publikovaného kódu něco podobného ale nenajdete. Pokud vám na první pohled události připadnou složité, nelámejte si s tím hlavu. Jde to i bez nich.
Ještě jedna poznámka - v MTASC jsme pro stejný účel využili třídu GDispatcher.
import flash.MovieClip; import events.Event; import events.EventDispatcher; import events.EventListener; class PlayerButton extends MovieClip { public var id(default, null):String; private var dispatcher:EventDispatcher; public function new () { this._alpha = 60; dispatcher = new EventDispatcher(this); } override public function onRollOver ():Void { this._alpha = 100; } override public function onRollOut ():Void { this._alpha = 60; } //Následující část řeší spouštění událostí v této třídě. Jedná se o metody třídy events.EventDispatcher. public function getParent():PlayerButton { return cast(_parent, PlayerButton); } public function addEventListener(type:EventType, handler:Dynamic, context:Dynamic):Void { dispatcher.addEventListener(type, handler, context); } public function removeEventListener(type:EventType, handler:Dynamic, context:Dynamic):EventListener { return dispatcher.removeEventListener(type, handler, context); } public function dispatchEvent(event:Event):Bool { var dispatched:Bool = dispatcher.dispatchEvent(event); if(event.bubbles && dispatched && getParent() != null) { getParent().dispatchEvent(event); } return dispatched; } }
LibraryClasses.hx
V tomto souboru už pracuji s objekty z knihovny classes.swf, kterou jsem si vytvořil pomocí swfmill. Každý naimportovaný objekt v knihovně přes swfmill je přístupný jako třída pod názvem, který jsem si určil v parametru id. Nejčastěji si takový objekt zprovozním pomocí nejjednoduššího zápisu:
import flash.MovieClip; class Pic extends MovieClip { public function new() { super(); } }
To je ale jen příklad. Protože já potřebuji v tomto případě více logiky, musím předchozí zápis rozvinout. Asi nejhlavnější změnou je, že navazuji na již vytvořenou třídu PlayerButton a jen rozšiřuji její chování.
Používám tu už také okénkovou animaci, kterou jsem si vytvořil v swfmill. Například tlačítko BtnPlayPause se při spuštění přesune na okénko Play a tam se zastaví pomocí metody this.gotoAndStop("play");
import PlayerButton; import events.Event; //Importuji externí knihovnu, která mi umožní spouštět události class BtnStop extends PlayerButton { public function new () { super(); this._x = 23; this._y = 0; this.id = "stop"; } public override function onPress ():Void { //Při stisknutí spustím událost dispatchEvent(new Event(CLICK, true)); } } class BtnPlayPause extends PlayerButton { public function new () { super(); this._x = 1; this._y = 0; this.id = "play"; //Nastavím si základní frame animace a zastavím se na něm this.gotoAndStop("play"); } override function onPress ():Void { if (this._currentframe == 1 ) { this.gotoAndStop( "pause"); } else { this.gotoAndStop( "play"); } //Při stisknutí spustím událost dispatchEvent(new Event(CLICK, true)); } } class BtnBw extends PlayerButton { public function new () { super(); this._x = 45; this._y = 0; this.id = "backward"; } public override function onPress ():Void { //Při stisknutí spustím událost dispatchEvent(new Event(CLICK, true)); } } class BtnFw extends PlayerButton { public function new () { super(); this._x = 67; this._y = 0; this.id = "forward"; } public override function onPress ():Void { //Při stisknutí spustím událost dispatchEvent(new Event(CLICK, true)); } }
Song.hx
S jednotlivými MP3 písničkami budu pracovat jako se svébytnými objekty, u kterých mám tři základní proměnné: autora, titul a cestu ke zdroji.
class Song { public var source: String; public var artist: String; public var title: String; static function Song () { } public function new () { } }
MPPlayer.hx
Tohle je poslední součást naší aplikace - samotné jádro programu přehrávače MP3. Jednotlivé funkce jsou postupně popsány v komentářích. Nyní upozorním na základní rozdíly v syntaxi. Tohle je totiž program napsaný v haXe a snaží se pokud možno využít výhody tohoto jazyka oproti ActionScript.
Například pro traceování externího XML používám vlastní komponentu haXe haxe.xml.Fast. Její použití je podrobně popsáno na stránkách projektu. Jedná se tedy o něco specifického pro haXe, protože zde nepoužívám pro práci s XML komponenty Flashe. Výhodou je, že takto psaný kód pak můžu snadno migrovat na další platformy, které haXe podporuje a nejsem omezen tím, že jednou jsem napsal kód např. pro ActionScript 3, ale už ho nepoužiji ve starších Flashích, které podporují jen ActionScript 2. Obecně také předpokládám, že vlastní komponenty haXe pracují rychleji a jejich použití je intuitivnější.
Vlastní komponentou haXe haxe.Http také načítám externí soubor.
Zajímavé je, jak přistupuji k objektům, které jsem si umístil v swfmill. Tyto objekty jsou už totiž na ploše a já s nimi můžu přímo pracovat. V SWFMILL jsem pomocí tagu <place> umístil MovieClip mcTxt s textovým polem txt. Cesta k tomuto objektu je tedy:
flash.Lib.current.mcTxt.txt
U textového pole txt si mohu zadávat obsah textu pomocí proměnné text. V našem případě zadávám do obsahu této proměnné jména interpretů písní a názvy písní.
flash.Lib.current.mcTxt.txt.text = song.artist + ": " + song.title; import flash.MovieClip; import flash.Sound; import events.Event; import LibraryClasses; import Song; class MPPlayer extends MovieClip { static var sound : Sound; static var isPlaying : Bool; static var isLoaded : Bool; static var offset : Float; static var currentSong: Int; static var songs: Array<Song>; static function playSong(song:Song) { if (!isLoaded) { //Pokud není žádná MP3 nahraná, nahrajeme novou sound = new Sound( ); sound.onSoundComplete = function() { //Pokud MP3 dohrála, spustíme funkci pro přeskočení na další položku v playlistu isPlaying = !isPlaying; forward (); } flash.Lib.current.mcTxt.txt.text = song.artist + ": " + song.title; //V haXe přistupujeme k objektům nikoliv přes _root.atd, ale přes flash.Lib.current, v našem případě zadáme hodnotu pro proměnou text u textového objektu "txt" sound.loadSound( song.source, true ); isLoaded = true; } sound.start( offset ); isPlaying = !isPlaying; } static function pause() { if (isPlaying) { //Pauza = zaznamenám si pozici MP3 a zastavím ji offset = sound.position / 1000; sound.stop(); isPlaying = !isPlaying; } } static function stopSong() { if (isPlaying) { //Stop = vynuluji pozici a zastavím MP3 offset = 0; sound.stop(); isPlaying = !isPlaying; } } static function forward() { if (isPlaying) { sound.stop(); isPlaying = !isPlaying; } if (isLoaded) { isLoaded = !isLoaded; } //Posunu se dál na playlistu if (currentSong == (songs.length - 1)) { currentSong = 0; } else { ++currentSong ; } //Pustím další MP3 na playlistu playSong (songs[currentSong]); } static function backward () { if (isPlaying) { sound.stop(); isPlaying = !isPlaying; } //Posunu se o jednu položku nazpět v playlistu if (currentSong == 0) { currentSong = songs.length - 1; } else { currentSong = currentSong - 1 ; } if (isLoaded) { isLoaded = !isLoaded; } //Pustím předchozí MP3 na playlistu playSong (songs[currentSong]); } function initMPPlayer (importedSongs:Array<Song>) { songs = importedSongs; //Připojím ovládací prvky this.attachMovie("BtnStop","btnStop",this.getNextHighestDepth()); this.attachMovie("BtnPlayPause","btnPlayPause",this.getNextHighestDepth()); this.attachMovie("BtnFw","btnFw",this.getNextHighestDepth()); this.attachMovie("BtnBw","btnBw",this.getNextHighestDepth()); //Zaposlouchám se do událostí na ovládacích prvcích this.btnPlayPause.addEventListener(CLICK, eventHandler,this); this.btnPlayPause.addEventListener(CLICK, eventHandler,this); this.btnStop.addEventListener(CLICK, eventHandler,this); this.btnFw.addEventListener(CLICK, eventHandler,this); this.btnBw.addEventListener(CLICK, eventHandler,this); } function parseXml (str:String) { //Parsuji nahrané XML pomocí vlastní komponenty haxe.xml.Fast z haXe var _xml:Xml=Xml.parse(str); var fast = new haxe.xml.Fast( _xml.firstElement()); var songs = new Array(); for(item in fast.elements) { var song = new Song(); song.source = item.att.source; song.artist = item.att.artist; song.title = item.att.title; songs.push(song); } initMPPlayer(songs); } function eventHandler (event:Event) { switch (event.target.id) { case "play": if (!isPlaying) { playSong (songs[currentSong]); } else { pause(); } case "stop": if (isPlaying) { //Jednoduchá animace - přepnu na políčko "play" this.btnPlayPause.gotoAndStop("play"); } stopSong(); case "forward": if (!isPlaying) { //Jednoduchá animace - přepnu na políčko "pause" this.btnPlayPause.gotoAndStop("pause");} forward(); case "backward": if (!isPlaying) { //Jednoduchá animace - přepnu na políčko "pause" this.btnPlayPause.gotoAndStop("pause");} backward(); } } public function new () { //Nastavím základní proměnné isPlaying = false; isLoaded = false; //Startovací pozice je 0 a také první píseň má pořadí 0 offset = 0; currentSong = 0; //Nahrávám externí soubor pomocí komponenty haxe.Http var http:haxe.Http = new haxe.Http(flash.Lib.current.file); http.onStatus = function (n:Int) { trace("http status: " + n); } http.onError = function (msg:String) { trace(msg); } http.onData = parseXml; http.request(true); } }
Program MP3 přehrávače v haXe je hotov. Nyní si ho zkompilujeme pomocí příkazu:
$ haxe compile.hxml
Ouha! Výsledné swf nefunguje - jde sice spustit, ale přehrávač ohlásí chybu! Vytvořili jsme totiž skutečnou flashovou aplikaci, která pro své fungování potřebuje externí soubory. V našem případě XML soubor s playlistem. A konečně také MP3 s hudbou, kterou si přejeme na našich stránkách spouštět.
playlist.xml
Já jsem pro své potřeby stáhl volné MP3 z hudebního serveru last.fm. Vytvořil jsem si XML, kde jsem nadefinoval cestu k MP3, jména interpretů a tituly písní.
<?xml version="1.0" encoding="utf-8"?> <playlist> <song source="mp3/Bitter Tea For Breakfast - the invisible man (reprise).mp3" artist="Bitter Tea" title="the invisible man" /> <song source="mp3/single.mp3" artist="David Lanz" title="Things We Said Today" /> </playlist>
Flashvars
Nakonec zbývá vložit naše swf do html stránky. Cestu k XML souboru sdělím swf pomocí parametru Flashvars:
<param name="FlashVars" value="file=playlist.xml">
Celý html kód pro vložení swf souboru s aplikací MP3 přehrávače:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/ cabs/flash/swflash.cab#version=6,0,0,0" width="250" height="20" id="MP3Player.swf" align="middle"> <param name="allowScriptAccess" value="sameDomain"> <param name="movie" value="MP3Player.swf"> <param name="FlashVars" value="file=playlist.xml"> <param name="quality" value="high"> <param name="bgcolor" value="#ffffff"> <embed src="MP3Player.swf" quality="high" bgcolor="#ffffff" width="250" height="25" name="MP3Player.swf" align="middle" FlashVars="file=playlist.xml" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"> </object>
Výsledek - MP3 přehrávač s playlistem:
Výhody haXe
-
stejné jako u MTASC - vývoj flashových aplikací, při kterém nejsem svazován IDE Adobe Flashe. Vývoj Flashe mohu tedy oddělit od kreativní části. V kreativní části si pomocí jakéhokoliv editoru (který může být lepší než samotné IDE Adobe Flashe) nakreslím podklady a ty pak zpracuji pomocí kombinace swfmill a haXe;
-
rychlá kompilace;
-
vlastní knihovny a vlastní syntax. Díky tomu lze víceméně sjednotit vývoj v ActionScriptu 2 i ActionScriptu 3;
-
živé fórum a podpora komunity. Zatímco MTASC lze už bez nadsázky označit jako uzavřený projekt (ale stále dobře použitelný), v tomto případě se jedná o jazyk, na kterém se stále pracuje a kde vám autoři v případě těžkostí rádi poradí.
Shrnutí
V našem seriálu jsme si ukázali tři příklady vývoje Flashe pomocí nástrojů open-source. Uplatnění je skutečně široké - od jednoduchých animovaných bannerů, přes hry až po složitější aplikace, které dovedou interaktivně pracovat s externími soubory. Myslím, že tento výběr jednoznačně také ukázal, že Flash není mrtvá kapitola webového vývoje, ale naopak, že je živý mezi open-source komunitou a jeho pomocí lze vytvářet graficky pohledné aplikace pro webové prohlížeče napříč platformami. Tyto aplikace lze navíc vytvářet zdarma na Linuxu i jinde (bez počítače to ale nepůjde) a neomezovat se přitom na balík od Adobe Flash. Každý uživatel má svůj oblíbený grafický editor a textový editor. To jsou základní nástroje pro open-source tvorbu Flashe. Proč tuhle výzvu nevyužít?
Všechny materiály k tomuto článku si můžete stáhnout v archivu haXe.zip.