By Adobe
 
Created
26 July 2010
 

Requirements

 
Prerequisite knowledge

You should have a good understanding of Adobe Flash Professional and ActionScript 3.0.
 

 
User level

Intermediate
 

 
Required products

 
Sample files

  • timelinewatcher.zip (22 KB)
In many SWF applications, developers need a way to programmatically monitor a timeline as it is played back so the code can take some action when a frame label or the end of the timeline is reached. Techniques for achieving this, however, have often conflicted with the design goal of separating timelines from actions.
 
In the past decade Adobe Flash has grown from a simple animation tool into a comprehensive authoring environment for creating rich content. Nowadays many rich media agencies have implemented a workflow where multiple people in different roles—including Flash designers and developers—can simultaneously work together on one project, and projects are split into many different FLA and ActionScript files that all fit a specific framework or conceptual structure.
 
As a result, it has become a key best practice to use FLA files primarily as a collection of multimedia elements and timeline animations, and to embody all logic in external ActionScript files. To enable this workflow, Flash CS3 Professional introduced the Document class as a default mechanism to link a FLA file to external ActionScript code.
 
Overall it has become pretty easy to separate timelines from actions or code. However, there is unfortunately one exception to the rule.
 
Consider the following scenario: A developer would like to run a piece of code after a certain timeline has played to a specific point, which is marked with a label. How does he know exactly when this point has been reached? To solve this problem, the developer might place the following action on a timeline inside a FLA file:
 
dispatchEvent(new Event("animationHasFinished"));
Although there is nothing invalid about this approach, it does have drawbacks. For developers, timeline actions are harder to author, adjust, and locate than code inside external ActionScript files. Many designers will also find this approach too technical. Lastly, it simply violates the principle of keeping your actions and timelines separated (yes, we "Flashers" are proud people).
 
At Refunk, where I work, we have developed a small utility that elegantly solves this problem. The TimelineWatcher class enables a developer's code to watch a timeline as it is played back and be notified when a frame label or the end of the timeline has been reached. By using frame labels as hooks—cue points, as in web video—timelines and actions can now be truly separated.
 

 
The TimelineWatcher and TimelineEvent classes

The TimelineWatcher class is a small, simple utility class. Here is the full documentation:
 

  • Package: com.refunk.timeline
  • Class: public class TimelineWatcher
  • Inheritance: TimelineWatcher > EventDispatcher > Object
  • Language version: ActionScript 3.0
 
Public methods
  • TimelineWatcher(timeline:MovieClip): Creates a TimelineWatcher object to watch a single timeline
  • dispose():void: Removes the TimelineWatcher object's internal listeners and references, so it can be garbage collected without any memory leaks
 
Events
  • labelReached: Dispatched when a new timeline label has been reached
Event Object Type: com.refunk.events.TimelineEvent
Event.type property: com.refunk.events.TimelineEvent.LABEL_REACHED
Custom property/value: currentFrame: The current frame (int)
currentLabel: The current timeline label (String)
  • endReached: Dispatched when the end of a timeline has been reached
Event Object Type: com.refunk.events.TimelineEvent
Event.type property: com.refunk.events.TimelineEvent.END_REACHED
Custom property/value: currentFrame: The current frame (int)
currentLabel: The current timeline label (String)

Because the TimelineWatcher class extends the built-in EventDispatcher class you can add event listeners using the standard addEventListener() method. It goes hand-in-hand with the TimelineEvent class, which is a custom event class that explicitly types the events used by TimelineWatcher. These events additionally store the current frame number and label, so they can be retrieved via the event object.
 
You can find the TimelineWatcher and TimelineEvent classes in the sample file for this article (timelinewatcher.zip). Both classes are made available as free code by Refunk and are distributed under the GNU General Public License.
 

 
Setting up the TimelineWatcher example

To see a working example of TimelineWatcher, start by unzipping the timelinewatcher.zip file and opening index.html in your desktop web browser (make sure you have Flash Player 9 or later installed). You will see a simple timeline animation of a red ball that animates from the left to the right and back. It loops three times before the animation stops. A text field in the top left corner displays the timeline label that is currently playing and the number of loops the animation has made (see Figure 1).
 
Simple TimelineWatcher example
Figure 1. Simple TimelineWatcher example
Open the test.fla file in your Flash authoring environment, and you will see a white canvas with a MovieClip of a red ball (see Figure 2).
 
Canvas at frame 1 showing a red ball
Figure 2. Canvas at frame 1 showing a red ball
The main Timeline contains two classic tweens, one that moves the ball to the right and one that moves it back to the left, with two frame (or timeline) labels—moveRight and moveLeft—indicating the direction of the animation (see Figure 3).
 
Timeline showing two simple tweens with corresponding frame labels
Figure 3. Timeline showing two simple tweens with corresponding frame labels
That's all the design work that went into the example. When you test this movie—without the attached Document class, as I will discuss next—you will see an endlessly looping animation of a red ball that moves back and forth.
 
The next step is to add an external ActionScript file that will do the following:
 
  • Display the dynamic text field on the top left of the canvas that indicates the current Timeline label and the amount of loops that have been completed
  • Stop the animation after three loops
To link the external ActionScript file named Test.as to the test.fla file, type Test as the Class in the Publish settings of the Properties panel (see Figure 4). At compile-time this Document class will be associated with the main Timeline, and will be able to provide functionality much like actions on the main Timeline.
 
Document class field in the Properties panel
Figure 4. Document class field in the Properties panel
Before examining the external ActionScript file, there is one additional setting you need to be aware of. Choose File > Publish Settings and click the Flash tab. Make sure ActionScript 3.0 is specified in the Script menu and click Settings to open the Advanced ActionScript 3.0 dialog box (see Figure 5).
 
Automatically Declare Stage Instances check box in the Advanced ActionScript 3.0 Settings dialog box
Figure 5. Automatically Declare Stage Instances check box in the Advanced ActionScript 3.0 Settings dialog box
By default the Automatically Declare Stage Instances check box is selected, which means that Flash will automatically declare any element with an instance name that resides on the main Timeline at compile time. Although this is a handy feature for designers and developers who use the Flash authoring environment for ActionScript development, it also can cause problems for developers who use external code editors, because these editors will complain that these declarations have not been created. (Flash acts as if they have been created—it kind of creates them for you in memory—so it's not really a matter of visibility; they are required but don't exist, and Flash creates them for you automagically.)
 
To make your code editable in multiple development environments, you are better off unchecking this option and explicitly declaring every element with an instance name that resides on the main Timeline as a public class variable in your Document class. Without these declarations, Flash will throw a compile-time error. In the example there is just one MovieClip on the main Timeline with the instance name ball, so the following declaration is required:
 
public var ball:MovieClip;
From this point onwards you automatically have a reference to the ball instance on the main Timeline.
 

 
Using external ActionScript to tie everything together

Here is the basic scaffolding for the Document class named Test.as:
 
package { import flash.display.MovieClip; public class Test extends MovieClip { public var ball:MovieClip; public function Test() { super(); stop(); } } }
The code above declares the ball MovieClip on the main Timeline and stops its playback. Although Flash implicitly calls the super() method in the constructor, I usually add it for all extended classes as a best practice; this reminds me to make the method call when additional arguments are required. Starting with the basic scaffolding above, I add the text field for the current timeline label to the top left of the screen (highlighted text indicates the newly added code):
 
package { import flash.display.MovieClip; import flash.text.TextField; public class Test extends MovieClip { public var ball:MovieClip; private var output:TextField; public function Test() { super(); stop(); output = new TextField(); addChild(output); output.text = "testing: 1, 2, 3"; // just for testing purposes } } }
At this point it starts to get more interesting. I add the TimelineWatcher and TimelineEvent classes as well as the necessary logic:
 
package { import flash.display.MovieClip; import flash.text.TextField; import com.refunk.events.TimelineEvent; import com.refunk.timeline.TimelineWatcher; public class Test extends MovieClip { public var ball:MovieClip; private var output:TextField; private static const MOVE_LEFT:String = "moveLeft"; private static const MOVE_RIGHT:String = "moveRight"; private var timelineWatcher:TimelineWatcher; public function Test() { super(); stop(); output = new TextField(); addChild(output); timelineWatcher = new TimelineWatcher(this); timelineWatcher.addEventListener(TimelineEvent.LABEL_REACHED, handleTimelineEvent); gotoAndPlay(1); } private function handleTimelineEvent(e:TimelineEvent):void { if (e.currentLabel === MOVE_LEFT || e.currentLabel === MOVE_RIGHT) { output.text = "label: " + e.currentLabel; } } } }
First I import both classes so the Flash compiler knows where to find them. Next I add two constants named MOVE_LEFT and MOVE_RIGHT that match the timeline labels. This is a good practice to avoid errors due to typing mistakes.
 
Next I declare the TimelineWatcher instance named timelineWatcher as a private class variable. In the main constructor named Test(), I create the actual TimelineWatcher instance and pass it the timeline that I want it to watch as a parameter. In this case I can use the keyword this, because it reflects the main Timeline.
 
I also add an event listener to listen for the TimelineEvent.LABEL_REACHED event, which triggers the custom handleTimelineEvent() method. Next, I start playing the main Timeline again.
 
The handleTimelineEvent() method uses the returned event object named e to retrieve the current label and checks if it corresponds with the predefined labels I am monitoring. If so, it updates the timeline label on the screen.
 
When you run this example you will see the red ball continuously moving back and forth with the current timeline label in the top left of the canvas. After adding only a few lines of external ActionScript code I now know exactly what's going on with the main Timeline. And, look mum, I used no timeline actions at all!
 
The edits to the code below stop the timeline after it has played three loops:
 
package { import flash.display.MovieClip; import flash.text.TextField; import com.refunk.events.TimelineEvent; import com.refunk.timeline.TimelineWatcher; public class Test extends MovieClip { public var ball:MovieClip; private var output:TextField; private static const MOVE_LEFT:String = "moveLeft"; private static const MOVE_RIGHT:String = "moveRight"; private var timelineWatcher:TimelineWatcher; private var loops:uint = 1; public function Test() { super(); stop(); output = new TextField(); addChild(output); timelineWatcher = new TimelineWatcher(this); timelineWatcher.addEventListener(TimelineEvent.LABEL_REACHED, handleTimelineEvent); timelineWatcher.addEventListener(TimelineEvent.END_REACHED, handleTimelineEvent); gotoAndPlay(1); } private function handleTimelineEvent(e:TimelineEvent):void { switch (e.type) { case TimelineEvent.LABEL_REACHED: if (e.currentLabel === MOVE_LEFT || e.currentLabel === MOVE_RIGHT) { output.text = "label: " + e.currentLabel + " loops: " + loops; } break; case TimelineEvent.END_REACHED: loops++; if (loops > 3) { stop(); timelineWatcher.removeEventListener(TimelineEvent.LABEL_REACHED, handleTimelineEvent) timelineWatcher.removeEventListener(TimelineEvent.END_REACHED, handleTimelineEvent); timelineWatcher.dispose(); timelineWatcher = null; } break;} } } }
I have added a second listener to listen for the TimelineEvent.END_REACHED event to count each timeline loop, and I stop the timeline playback after three loops.
 
Finally, I clean up by removing the two listeners, disposing the TimelineWatcher instance, and setting its reference to null.
 
That completes all the code for the example! When you test this code you should see the example as displayed in Figure 1.
 

 
Where to go from here

In this article you've seen how to use the TimelineWatcher class to dispatch an event when a frame label or the end of a timeline is reached, while keeping timelines and actions separate.
 
Thibault Imbert from ByteArray.org recently blogged about the FrameLabel event, a new feature request for the next version of Adobe Flash Player.
 
For more information about ActionScript, visit the ActionScript Technology Center.