2 July 2007
This article assumes that you have an intermediate knowledge of ActionScript 3.0 and have some prior experience working with XML files.
Intermediate
Introduced with ActionScript 3.0 and Adobe Flash Player 9, ECMAScript for XML (E4X) offers new ways to retrieve data from XML in Adobe Flash applications. In this article I examine how you can use E4X to fetch data out of an XML object within an application in real time—processing only the information required to run the application. This strategy improves the performance of Flash applications by reducing unnecessary overhead and data loading.
The release of Flash Player 9 includes a new virtual machine called ActionScript Virtual Machine 2 (AVM2), which increases player performance. In addition, improvements to the ActionScript language in ActionScript 3.0 offer Flash developers new functionality when creating Flash applications.
Note: Read the AVM2 overview (PDF, 401k) for more information about the instructions, associated data structures, and file format supported by ActionScript Virtual Machine 2.
One of those improvements was the adoption of ECMAScript for XML (E4X), a part of ECMAScript—the standardized scripting language popularly implemented as JavaScript. ActionScript is based on ECMAScript and Adobe is one of the contributors to the ECMAScript standard.
E4X is an extension to ECMAScript that adds XML as a native data type, adding operators like the dot (.) and the attribute identifier (@) to search and filter data. For ActionScript developers, this means XML is now a native data type in ActionScript 3.0. XML now comes with its own set of classes to manage and access data, making it much easier for Flash to access data within XML structures.
E4X offers faster and more logical access to XML data consumed by ActionScript 3.0 applications. You can use it at runtime to pull data directly from an XML object, which eliminates the need to write a specialized parser. Previously XML data had to be transformed from an XML packet into structured native data types, such as arrays of objects, in order for the application to use the data. This process is now much more intuitive and XML data is easier to consume using ActionScript 3.0.
If you are familiar with the basics of accessing data within an array of objects using either square brackets ([]) or dot notation (.), you'll immediately recognize some of the syntax used when working with E4X. In addition to these operators, the attribute identifier (@) is used when performing E4X operations.
In the example below, a string of characters is data typed as an XML object:
var myXML:XML =
<employees>
<employee id="1">
<fname>Frank</fname>
<lname>Bacon</lname>
<email>fbacon@company.com</email>
</employee>
<employee id="2">
<fname>Chris</fname>
<lname>Wren</lname>
<email>cwren@company.com</email>
</employee>
</employees>
In the code example below, the E4X operators I previously discussed are used to access data contained within the XML object:
trace(myXML.employee[0].fname); //
Output: Frank
trace(myXML.employee.(@id=="2").email);
// Output: cwren@company.com
trace(myXML.employee.(lname=="Bacon").email);
// Output: fbacon@company.com
The first trace statement displays the first name of the employee in the first node of XML.
The second trace statement filters data by the id attribute to find a case where the id value is equal to "2."
The third trace statement filters data by finding a node whose value is equal to "Bacon."
Note: These filters are case-sensitive. Comparing the string "bacon" would not result in the same output.
You can use a for loop to iterate through all the nodes of the XML file. In the code example below, the loop repeats and outputs each employee's first name, last name, and e-mail address until the data for both nodes has been accessed:
for each ( var property:XML in myXML.employee ) {
trace(property.fname + " " + property.lname);
trace(property.email);
trace("--------------------");
}
The rest of this article describes how to build an application that uses XML data and E4X to populate multilingual captions for a video display.
In this article, I discuss building an application that uses XML data and E4X to populate multilingual captions for a video display. This scenario is designed to give you insight into how to use E4X. A single XML packet is loaded into the application that will contain all the necessary captioning data. Rather than parse all the data into a complex data structure, you'll see how E4X can extract the data from the XML object and then use the data in different locations within the application (see Figure 1).
The layout of the video application is simple. First, an FLVPlayback component is placed in the middle of the Stage to render the FLV video that is displayed in the application. Centered vertically beneath the FLVPlayback component is a dynamic text field to display the caption. Directly below that is an instance of a ComboBox component that allows the user to select the preferred language to display the video caption text.
The first node of the XML packet contains information about the various languages. This information is stored by the rest of the packet. The language data information is used by the application to populate the ComboBox, offering the available language options for the video captions.
For the purposes of this article, the caption text and the timing of the captions have been "borrowed" from the Flash CS3 Professional documentation. When working with the FLVPlayback component, you can add captions to the video by using the FLVPlaybackCaptioning component. It uses a Timed Text (TT) XML file for timings and captions, which is an open standard used for captioning and is essentially a DTD (Data Type Definition).
While multilingual captioning can be implemented using the FLVPlaybackCaptioning component, this article illustrates the concept of multilingual captioning as an example to demonstrate how easy it is to access complex structured XML data using E4X.
The XML code below is a portion of the XML packet that is used in the application:
<?xml version="1.0" encoding="ISO-8859-1"?>
<movieCaptions>
<languages>
<language name="English" code="eng"/>
<language name="French" code="fre"/>
<language name="German" code="deu"/>
<language name="Italian" code="ita"/>
<language name="Japanese" code="jpn"/>
<language name="Spanish" code="spa"/>
</languages>
<caption cuepoint="0.500">
<eng>I had just joined Macromedia in 1996,</eng>
<deu>Ich hatte gerade Macromedia 1996 verbunden,</deu>
<fre>J'avais juste joint Macromedia en 1996,</fre>
<ita>Avevo
unito appena Macromedia in 1996,</ita>
<jpn>私はちょうど1996年にMacromediaを結合した、</jpn>
<spa>Acababa de ensamblar Macromedia en 1996,</spa>
</caption>
</movieCaptions>
Note: The various translations used in this example have been translated online using translation software; hopefully the translation results of the captions are somewhat accurate.
The original XML data consists of one <languages> node and multiple <caption> nodes. As you can see when analyzing the XML, the items in the <languages> node contain both name and code attributes. In the <caption> node, each translated caption is surrounded by tags that correspond to the code attribute of each available language.
The application uses instances of the XMLList class to store the language and caption data once the XML packet has been retrieved. This makes it possible to use the E4X operators to process the XML data. First, you'll declare two instances of the XMLList class, naming them captions and languages respectively, as shown below:
var captions:XMLList = new XMLList();
var languages:XMLList = new XMLList();
Next, you need to retrieve the XML. This is accomplished by creating an instance of the URLLoader class and calling its load method, which in turn uses an instance of the URLRequest class to fetch the XML packet:
var captionsXMLLoader:URLLoader =
new URLLoader();
captionsXMLLoader.load(new URLRequest("assets/translated_captions.xml"));
captionsXMLLoader.addEventListener(Event.COMPLETE,
captionsXMLLoadedHandler);
In the example above, the code is loading the XML from a local path. However, there's no reason why you couldn't load the XML using a URL to a file on the network instead.
The application needs to consume the XML data once the packet has completed loading; the addEventListener method initiates the function call once this has occurred. The listener is added in the last line of code in the example above.
Now it's time to write the function. In the code example below, you are creating a new function called captionsXMLLoadedHandler. First, the function creates a local variable named captionsXML that is assigned to the data retrieved by constructing a new XML object. Once the retrieved data is correctly typed as XML, you can use the methods included in the XML class to retrieve the data. In the next line, the captions XMLList is populated with the child nodes from the local variable.
As I mentioned previously, the first node of the XML packet retrieved is a listing of the languages used for the captions. Notice the path used to retrieve that data for the languages XMLList:
function captionsXMLLoadedHandler(eventObj:Event):void {
var captionsXML = new XML(eventObj.currentTarget.data);
captions = captionsXML.children();
languages = captionsXML.children()[0].children();
setCuePoints();
setLanguages();
}
The data stored in the captions and languages XMLLists is then used to set initial values for the application. The captions XMLList sets the cue points of the FLVPlayback component so that an event is fired to change the caption that is currently displayed. The languages XMLList populates the ComboBox component which offers the user the list of specific languages that can be displayed. These actions are handled by calling the appropriate functions from within the captionsXMLLoadedHandler function detailed above. The first function called is setCuePoints.
The code for the setCuePoints function is shown here:
function setCuePoints():void {
var i:int = 0;
for each(var prop:XML in captions) {
var captionData:XML = prop;
my_FLVPlybk.addASCuePoint({name:i.toString(),
time:Number(captionData.@cuepoint)});
i++;
}
}
The setCuePoint function uses a for loop to iterate over the captions using the FLVPlayback component's addASCuePoint method to add the cuepoint attribute of each node. The cue point's name attribute is assigned the current value of i, while E4X is used to extract the cue point attribute of the current item in the XMLList.
Next, construct a new DataProvider object and analyze the setLanguages function:
import fl.data.DataProvider;
var dp:DataProvider = new DataProvider();
language.dataProvider = dp;
var selectedLang:Object = new Object();
function setLanguages():void {
var i:int = 0;
for each(var prop:XML in languages) {
var languageData:XML = prop;
dp.addItem( {
label:languageData.@name, data:languageData.@code } );
i++;
}
selectedLang = language.dataProvider.getItemAt(0);
}
The setLanguages function relies on some additional code because it is adding structured data to the ComboBox component's dataProvider. The dataProvider is created and assigned to the ComboBox component in the first two lines in the code example above. Using E4X filtering, the code in the for loop adds the name and code attributes extracted from the languages XMLList to the dataProvider by using the addItem method in each iteration of the loop.
A variable named selectedLang is assigned the value of the first item of language's dataProvider. This variable is used to determine the current caption whenever the ComboBox component changes. It is important to set a default value for the ComboBox's initial value before any selection is made, in case the user doesn't select a different language from the ComboBox as the application runs.
This section shows you how to add the visual elements to the Stage of the application. First drag a FLVPlayback component to the Stage and delete it. This causes the FLVPlayback component to be added to the Library. We deleted the component from the Stage because in this article we'll use ActionScript 3.0 to control all aspects of this component.
Next, add a dynamic text field to the Stage. Be sure to give the field sufficient width and height to accommodate two lines of text at the font size that you've selected. Also, select the "Multiline" option so that text displayed in the field can word-wrap. Name the instance of the text field captionText.
Finally, drag a ComboBox component to the Stage. Position the ComboBox below the dynamic text field and name the ComboBox instance language. Use the alignment tools to center both of the elements vertically (see Figure 2).
Now add the code to the application that controls the FLVPlayback component:
import fl.video.*;
var my_FLVPlybk = new FLVPlayback();
my_FLVPlybk.x = 115;
my_FLVPlybk.y = 50;
addChild(my_FLVPlybk);
my_FLVPlybk.source = "assets/caption_video.flv";
my_FLVPlybk.addEventListener(MetadataEvent.CUE_POINT,
cp_listener);
This code example adds a new FLVPlayback component to the Stage and positions it above the text field. The code also loads the FLV file that the FLVPlayback component will display. Then it adds an event listener for the cue point events and initiates the function cp_listener to call as each of these events occurs.
The code for the cp_listener function is listed below. The variable currentCuePoint is set within the application to store the name of the current cue point; the value of this variable is used by a number of functions within the application:
var currentCuePoint:Number = 0;
function cp_listener(eventObject:MetadataEvent):void {
currentCuePoint =
eventObject.info.name;
setCaption(currentCuePoint);
}
The cp_listener function called by the FLVPlayback component's cue point event sets the value of currentCuePoint to the name of the current cue point. It also calls the setCaption function, passing the value of currentCuePoint.
Using bracket ([]) notation and an E4X filter, the setCaption function sets the text value of the text field to the language for the current cue point:
function setCaption(cp:Number):void {
captionText.text =
captions[cp][selectedLang.data];
}
The last function, setLanguage, is called by the ComboBox component when a selection is made. The setLanguage function sets the current caption language by calling the setCaption function, as shown below:
function setLanguage(evt:Object):void {
selectedLang=language.selectedItem
setCaption(currentCuePoint);
}
Variable declarations and import statements in this sample code have been moved around for the sake of clarity so that you can view the logical pieces together.
It is customary to place the import statements at the beginning of your ActionScript code, at the top where the variables are defined. Placing import statements at the beginning of the code ensures that the imported items will be available to the application when the functions are called.
Using E4X to filter data actively at runtime eliminates the need for unnecessary data transformation as the application starts up. This has many benefits, including better performance. As you analyze each function, you can see that there really isn't much code required to build this application, yet it is an elegant solution because it pulls in only the data as it is required. While the typical XML example revolves around parsing RSS feeds, I hope that the concepts presented in this article for presenting multilingual captions with Flash video will give you ideas to consider for future projects.
Remember that the FLVPlaybackCaptioning component is a perfect partner for the FLVPLayback component. If you set up the components in authoring mode within the Flash authoring environment, you can create video captions without using the code outlined in these examples. As with most operations, there are multiple ways to achieve a similar goal. By controlling the FLVPlayback component using ActionScript 3.0, you've learned a new trick that uses E4X to quickly retrieve XML data that you can integrate into your ActionScript 3.0 applications.
For more information, please see the resources below:
| 04/23/2012 | Auto-Save and Auto-Recovery |
|---|---|
| 04/23/2012 | Open hyperlinks in new window/tab/pop-up ? |
| 04/21/2012 | PNG transparencies glitched |
| 04/01/2010 | Workaround for JSFL shape selection bug? |
| 02/13/2012 | Randomize an array |
|---|---|
| 02/11/2012 | How to create a Facebook fan page with Flash |
| 02/08/2012 | Digital Clock |
| 01/18/2012 | Recording webcam video & audio in a flv file on local drive |