Let’s explore the new Flex 4 features building a full screen XML-driven VideoPlayer gallery
In this article we’ll build an XML-driven video gallery exploring a lot of new Flex 4 features (out in spring 2010): skinning spark components, spark containers, new states and transitions; but we’ll also use some “old” concepts like itemRenderers, custom events dispatch, load data from an xml file (using the Singleton design pattern), value objects, embedding fonts, css and a lot of other useful stuff when you develop Flex applications.
This article is also available in italian language on the Adobe italian UserGroup website actionscript.it
Here you can test a live demo
(right-click to get source code)
The application logic is very simple:
a list of videos will be loaded from an XML file and displayed in the List using a thumbnail.
When user mouse is over an image the video description will be displayed.
When user clicks the image the related video is loaded into the videoplayer.
Even if this application is so little, before to start to write this tutorial I created a mockup using Balsamiq .
I will write something about this software in the next weeks. It’s great to create your Flex / Air mockups.
The main application file
First of all, we need to create the application UI using a List and a VideoPlayer FX 4 components, and defining an HorizontalLayout. This is a new Flex 4 feature that allow to define the layout type (just like the layout property was on FX3).
We also call the VideoLoader#loadData() method after the applicationComplete event is fired.
This is a Singleton class that loads data from an XML file and dispatch the event XMLEvent.LOADED when data are ready, passing as parameter the whole xml content as an ArrayCollection.
At last, I would like you notice that the List component handle a custom event called selected, dispatched every time the user selects a new item from the List and used to load a new video on the videoplayer component.
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:list="com.fabiobiondi.videoplayer.views.list.*" minWidth="950" minHeight="400" applicationComplete="init()" viewSourceURL="http://path/to/source" > <fx:Style source="css/styles.css" /> <fx:Script> <![CDATA[ import com.fabiobiondi.videoplayer.events.ItemEvent; import com.fabiobiondi.videoplayer.events.XMLEvent; import com.fabiobiondi.videoplayer.managers.VideoLoader; import com.fabiobiondi.videoplayer.skins.VerticalScrollbar; import spark.skins.spark.VideoPlayerSkin; /** * Init application loading assets from the XML * */ private function init():void { VideoLoader.getInstance().loadData("data/video_electronics.xml") VideoLoader.getInstance().addEventListener(XMLEvent.LOADED, onLoadData); } /** * XML loading completed * The XMLEvent 'LOADED' is dispatched after all xml data are loaded */ private function onLoadData(e:XMLEvent):void { videolist.dataProvider = e.videos; } /** * Load video on VideoPlayer * The ItemEvent is dispatched after an user list selection * * @param e ItemEvent */ private function loadvideo(e:ItemEvent):void { player.source = e.video.videourl as String; } ]]> </fx:Script> <s:layout> <s:HorizontalLayout/> </s:layout> <!--video list--> <list:VideoList id="videolist" bottom="0" top="0" width="300" selected="loadvideo(event)"/> <!--player video--> <s:VideoPlayer id="player" width="100%" height="100%" minWidth="200" minHeight="200" /> </s:Application>
The XML file
<list> <video id="1"> <title>Wii NunChuck controller, Arduino and servos</title> <thumb>assets/electronics/wiinunchukservo.jpg</thumb> <description>The Wii nunchuck controller contains a 3 axis accelerometer, one joystick ...</description> <videourl>http://www.fabiobiondi.com/blog/wp-content/uploads/2009/12/09122009093.flv</videourl> <blogurl>http://www.fabiobiondi.com/blog/2009/12/wii-nunchuck-controller-and-arduino/</blogurl> </video> ... </list>
VideoLoader.as Class
This class loads data from an external XML file using the Singleton design pattern (since we need only one instance of this class).
When data are loaded we loop through the received xml object creating a new array where each element is a VideoItem value object.
This is useful (in FX3 too), but not required, to avoid the bothering warning message in the flex console panel when you use an itemRenderer.
So, we dispatch the XMLEvent.LOADED custom event passing, as parameter, the content in ArrayCollection format, so we can automatically populate the video List dataProvider when we’ll receive it in the main.mxml file.
This class must implements the IEventDispatcher interface to allow dispatching events.
package com.fabiobiondi.videoplayer.managers { import com.fabiobiondi.videoplayer.events.XMLEvent; import com.fabiobiondi.videoplayer.model.entities.VideoItem; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.net.URLLoader; import flash.net.URLRequest; import mx.collections.ArrayCollection; public class VideoLoader implements IEventDispatcher { private var _data:XML; private var _urlLoader:URLLoader; private var dispatcher:EventDispatcher; private static var _instance:VideoLoader; public static const LOADED:String = "loaded"; public function VideoLoader(singleton:SingleTon) { dispatcher = new EventDispatcher(this); } /** * Return a messageLoader istance * @return MessageLoader */ public static function getInstance():VideoLoader { if (VideoLoader._instance == null) { VideoLoader._instance = new VideoLoader(new SingleTon()); } return VideoLoader._instance; } public function loadData(xmlurl:String):void { var urlRequest:URLRequest = new URLRequest(xmlurl) _urlLoader = new URLLoader(); _urlLoader.addEventListener(Event.COMPLETE, onXMLLoaded) _urlLoader.load(urlRequest); } private function onXMLLoaded(e:Event):void { _data = XML(_urlLoader.data); var _videos:Array = new Array(); // Loop over the received XML object for (var i:uint = 0; i < _data.video.length(); i++) { var xmlNode:XML = _data.video[i]; // Create an object for each node (using the value object class "VideoItem" var videoVO:VideoItem = new VideoItem(); videoVO.id = xmlNode.@id; videoVO.title = xmlNode.title; videoVO.thumb = xmlNode.thumb; videoVO.description = xmlNode.description; videoVO.videourl = xmlNode.videourl; videoVO.blogurl = xmlNode.blogurl; _videos.push(videoVO) } dispatchEvent(new XMLEvent(XMLEvent.LOADED, new ArrayCollection(_videos))); } public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void{ dispatcher.addEventListener(type, listener, useCapture, priority); } public function dispatchEvent(evt:Event):Boolean{ return dispatcher.dispatchEvent(evt); } public function hasEventListener(type:String):Boolean{ return dispatcher.hasEventListener(type); } public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void{ dispatcher.removeEventListener(type, listener, useCapture); } public function willTrigger(type:String):Boolean { return dispatcher.willTrigger(type); } } } class SingleTon{}
VideoItem.as class
This is the Value Object class used to contains each video node info.
package com.fabiobiondi.videoplayer.model.entities { [Bindable] public class VideoItem { private var _id:Number; private var _thumb:String; private var _title:String; private var _description:String; private var _videourl:String; private var _blogurl:String; public function VideoItem() { } public function get id():Number { return _id; } public function set id(value:Number):void { _id = value; } public function get title():String { return _title; } public function set title(value:String):void { _title = value; } public function get description():String { return _description; } public function set description(value:String):void { _description = value; } public function get thumb():String { return _thumb; } public function set thumb(value:String):void { _thumb = value; } public function get videourl():String { return _videourl; } public function set videourl(value:String):void { _videourl = value; } public function get blogurl():String { return _blogurl; } public function set blogurl(value:String):void { _blogurl = value; } } }
XMLEVENT.as
Following the XMLEvent custom class
package com.fabiobiondi.videoplayer.events { import flash.events.Event; import mx.collections.ArrayCollection; public class XMLEvent extends Event { //public static const COMING_MESSAGE:String = "update" public static const LOADED:String = "loaded" public var videos:ArrayCollection; public function XMLEvent( _type:String, videolist:ArrayCollection):void { super(_type); this.videos = videolist; } override public function clone():Event { return new XMLEvent(type, videos); } } }
LIST COMPONENT
Here, the custom List component used in the main application file.
After the user selects an item we dispatch the custom event ItemEvent.SELECTED (I won’t show you it because it’s very similar to the XMLEvent class but you can get it downloading the source code) passing as parameter the selected node value object.
To display data on the list we’ll use an itemrenderer, called Videoitem and explained in the next paragraph.
[Event(name="selected", type="com.fabiobiondi.videoplayer.events.ItemEvent")] <!--[CDATA[ import com.fabiobiondi.videoplayer.events.ItemEvent; import com.fabiobiondi.videoplayer.model.entities.VideoItem; import com.fabiobiondi.videoplayer.views.list.itemRenderer.Videoitem; import spark.events.IndexChangeEvent; private function myChangedHandler(event:IndexChangeEvent):void { var videoVO:VideoItem = new VideoItem(); videoVO.id = event.currentTarget.selectedItem.id; videoVO.title = event.currentTarget.selectedItem.title; videoVO.thumb = event.currentTarget.selectedItem.thumb; videoVO.description = event.currentTarget.selectedItem.description; videoVO.videourl = event.currentTarget.selectedItem.videourl; videoVO.blogurl = event.currentTarget.selectedItem.blogurl; dispatchEvent(new ItemEvent(ItemEvent.SELECTED, videoVO )); } ]]> <!-- Place non-visual elements (e.g., services, value objects) here -->
THE LIST ITEM RENDERER
The itemRenderer concept it’s identical to Flex 3 but it’s implemented in a different way because of the new states architecture.
1) we can manage the normal, hovered and selected states, automatically invoked when the related event is fired.
2) we define some states transitions (similar to FX3). This task is not required, but it’s done just to improve the user experience.
3) we define which elements display in each state assigning the “state” property to the components (this is a new FX4 features) as follow:
property.stateName = value
<!--item renderer states--> <!-- State Transitions --> <!--background--> <!--Thumbnail--> <!--Title Container--> <!--Description-->
SKINNING
As you will see below, in Flash Builder 4, CSS are very similar to Flex 3, but a lot of style names are changed (so check the Api reference for details).
First of all, we set an embed font for whole the application (feature available in FX3 too) and we also defined the skin custom classes for the list and the video player components (available in FX3 too but now implemented in a new way).
We must also define the spark and the mx namespaces, to use both style libraries.
/* CSS file */ @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; @font-face { src: url("assets/fonts/ARIAL.TTF"); fontFamily: myFontFamily; advancedAntiAliasing: true; embedAsCFF: true; } s|Application { fontFamily: myFontFamily; fontSize: 15; color: #ffffff; background-color: #000000; } s|List s|Scroller { horizontalScrollPolicy: off; verticalScrollPolicy: auto; } s|List s|VScrollBar { /* This skin class is taken from http://theflashblog.com/?p=1063*/ skin-class: ClassReference("com.fabiobiondi.videoplayer.skins.VerticalScrollbar"); } s|VideoPlayer { color: #ffffff; baseColor: haloOrange; skin-class: ClassReference("com.fabiobiondi.videoplayer.skins.VideoPlayerCustomSkin"); }
SCROLLBAR SKIN CLASSES
To skin the scrollbars we need to implement 3 classes in order to modify the layout of the scrollbar itself, the thumb and the track.
Honestly I found a nice skin on FlashBlog, so I have used it:
http://theflashblog.com/?p=1063
I have copied the three requested classes on my /skin folder, changed the packaging and used them on this application.
VerticalScrollbar.mxml
[HostComponent("spark.components.Scroller")]
VScrollThumb.mxml
[HostComponent("spark.components.Button")]
VScrollTrack.mxml
[HostComponent("spark.components.Button")]
VIDEOPLAYER SKIN
The default video player skin is white and since I created this demo with a black background I didn’t like it.
Using google I found nothing useful so the only way to understand how to skin this component has been look inside the flex sdk.
I found that the default video player skin is called VideoPlayerSkin.mxml, so I have copied all the content in a new MXML file (skins/VideoPlayerCustomSkin.mxml) modifying it to properly skin my component.
This class allow to easly modify the ScrubBar (the centered part) color but to update to black the Buttons (play, fullscreen,…) and the VolumeBar styles the only ways was update all the related classes.
But i’m soooo lazy and I found a nice trick.
Infact, when the video is in fullscreen mode I noticed that the videoplayer control bar was already black…. coool… it was exatly what I needed.
Now the question is: how to reuse this black skin when the player is in normal mode too?
Looking the VideoPlayerSkin I saw that it defines two different classes for each state (skinClass and skinClass.fullScreenStates) so I just updated all the “normal” states to be the same of “fullScreenStates” and it perflectly works. Now my videoplayer skin is always black.
As you can see below we just need to change the first line of each component to be the same of the second one.
before:
skinClass="spark.skins.spark.mediaClasses.normal.VolumeBarSkin" skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"
after:
skinClass="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin" skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"
So, this is the new custom video player skin class (it seems complex but it’s done from Adobe, I only updated some values ; )
<!-- ADOBE SYSTEMS INCORPORATED Copyright 2008 Adobe Systems Incorporated All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the license agreement accompanying it. --> <!--- The default skin class for the Spark VideoPlayer component. @langversion 3.0 @playerversion Flash 10 @playerversion AIR 1.5 @productversion Flex 4 --> <!-- chrome color of undefined in the normal states means we inherit the chromeColor property, and a chrome color of 0xCCCCCC in the fullScreenStates means we ignore the chromeColor property all together as 0xCCCCCC is essentially just a no-op color transform --> <!-- host component --> /** * @copy spark.skins.spark.ApplicationSkin#hostComponent */ [HostComponent("spark.components.VideoPlayer")] <!--[CDATA[ /* Define the skin elements that should not be colorized. */ static private const exclusions:Array = ["videoDisplay", "playPauseButton", "scrubBar", "currentTimeDisplay", "timeDivider", "durationDisplay", "volumeBar", "fullScreenButton"]--> <!-- states --> <!-- drop shadow --> <!--- @private --> <!--- Video and player controls are clipped if they exceed the size of the component, but the drop shadow above is not clipped and sizes to the component. We also set verticalScrollPosition so that when we do clip, rather than clipping off the bottom first, we clip off the top fist. This is so the player controls are still visible when we start clipping. --> <!-- There's a minimum size for the video and controls. If we go below that we are clipped. --> <!-- background when the videoDisplay doesn't fill its whole spot --> <!--- @copy spark.components.VideoPlayer#videoDisplay --> <!-- video player controls --> <!-- actual controls with a maxWidth in non-fullScreen mode --> <!--- @copy spark.components.VideoPlayer#playerControls --> <!--- @copy spark.components.VideoPlayer#playPauseButton --> <!-- scrubbar + the currentTime/duration labels --> <!-- background for scrubbar + the currentTime/duration --> <!-- fill highlight (exclude in fullScreen) --> <!-- one pixel border --> <!-- border for the scrubbar/time label controls --> <!-- scrub bar + currentTime/duration in a HorizontalLayout --> <!-- spacer --> <!--- @copy spark.components.VideoPlayer#scrubBar --> <!-- spacer --> <!--- @copy spark.components.VideoPlayer#currentTimeDisplay --> <!--- @copy spark.components.VideoPlayer#durationDisplay --> <!-- spacer --> <!--- @copy spark.components.VideoPlayer#volumeBar --> <!--- @copy spark.components.VideoPlayer#fullScreenButton --> <!-- border -->
CONCLUSION
Flex 3 was great. Flex 4 seems amazing.
There is a lot of new stuff to study about the new architecture but it seems it will solve a lot of previous bothering issues.
Furthermore, I still need to explore in details the new Flash Catalyst integration (even if I’m so afraid it will be too much crude in its first version) but I think that we can forget all the skinning problems of the previous release.
I hope to post something about it in the next weeks.
That’s all. I hope you enjoy it : )













Leave your response!