How to Use Flash Video (FLV) Cue Points

ActionScript 2.0 ActionScript 3.0 Flash

Video cue points can be used for all sorts of things in Flash.  Typical uses involve triggering other activity, such as peripheral movie clips whose animations enhance the video content, or triggering text, such as closed captions.  I’ve seen some developers in the Adobe forums even use a cue point to signal that a video clip has reached its end.  Strictly speaking, cue points aren’t needed for that last goal (see “How to Determine the Completion of a Flash Video (FLV) File”), but it’s certainly a possible way to go.

If you’re interested in cue points yourself, but don’t know where to begin, let’s dive in. 

Getting cue point into a video file

You can either define cue points in a way that “bakes” them into the FLV (Flash video) file itself, or you can associate cue points with a video programmatically.  If they’re baked in, you have to re-encode the FLV in order to change them.  There are pros and cons to either approach, of course.  I generally prefer to have my cue points embedded into the FLV file (the baked-in approach), because the Import Video wizard in Flash allows me to determine cue points visually, rather than having to sit there with a timer, noting cue point times for later use.  Here’s a quick look at both techniques.

Embedded cue points

Selecting File > Import > Import Video opens the Import Video wizard.  Step through each screen until you get to the Encoding screen.  In Flash CS3, you’ll see a Cue Points tab along with the other tabs.  In Flash 8, you’ll have to click the Show Advanced Settings button first in order to see the tabs.  In both versions, the actual cue points interface is the same.  On the left side, you’ll see plus (+) and minus (-) buttons.  Click the plus button to add a cue point — but before you do, make sure to first specify where it should appear!  Here’s how to do that.

You’ll see a preview of the video itself with a slider knob above a horizontal track.  You’ll see a split slider knob beneath the horizontal track.  The split knob represents the in and out points of the original video file.  Drag the left (in) knob toward the right to trim off excess beginning footage; drag the right (out) knob toward the left to trim off excess ending footage.  The span between those split knobs dictates the portion of the video that Flash will actually encode as an FLV file.  The knob above the horizontal track can be dragged back and forth to determine where cue points should appear.

Choose a position, then click the plus button.  You’ll be able to supply a name in the Name heading.  For sake of illustration, let’s name our cue point “caption,” (without quotes) because we’re going to use this cue point as a closed caption.  The Time heading is determined by the knob you just dragged.  Leave the Type heading as the default “Event.”  On the right, you’ll see a second set of plus (+) and minus (-) buttons.  These are for optionally adding parameters to any given cue point.  Since this is a makeshift caption cue point, let’s provide some text to display when this position is reached.  Click plus, then provide a name and value in their respective headings.  For Name, let’s choose “text” (without quotes) and for Value, let’s supply “Lorem ipsum dolor sit amet” (again, without quotes, even though the value of this parameter is a string).

Import Video wizard's Cue Points interface

When we use ActionScript a bit later to listen for cue points, we’re going to actually reach in to the first cue point and pull out the custom text parameter we just made.  We’re going to use that to set a dynamic text field with the parameter’s value, Lorem ipsum dolor sit amet.  To clear out this text field a second later, we’ll need to add another cue point and set its text parameter to an empty string.  (Note, we could have called this parameter oogabooga:  it’s not the name that matters.  Obviously, text is a good, descriptive name, but keep in mind that much of what we’re doing is arbitrary.)

Drag the top slider knob rightward along the horizontal track.  At a position that represents one second later (or whatever time span you like), follow the same steps to add a second cue point whose text parameter’s value is an empty field.  You’ll see a default value of “value” when you click the right-hand plus button; just remove that word and replace it with a pair of empty double quotes ("").

Note: Unfortunately, a tricky thing happens when this second cue point is encountered.  When ActionScript sees that empty string (""), it should set the dynamic text field to the same value — that is, an empty string — but in actual practice, it ends up setting the dynamic text field to a pair of visible quotation marks.  If you leave the original default value instead, you get the word “value”.  Neither of those is desirable, but at least the pair of quotes is shorter, and this quirky behavior will be solved with a bit of extra code later in the tutorial.  (Special thanks for Grant for bringing this to my attention!)

Repeat the above steps for as many additional cue points as you like.  If you’re not using cue points for closed captions, you won’t necessarily have to pair up each text-filled cue point with an empty string partner.  Totally up to you.  You could easily use the parameters area to specify which addition movie clip to trigger, or which sound to play … you get the idea.

Flash CS3 features an additional button that allows you to draw from pre-notated cue points in an XML document.  You can either adhere to an arbitrary Adobe format or a subset of the TT (Timed Text) format specified by the W3C.  Tom Green and I cover both of these approaches in Foundation Flash CS3 for Designers.

Assigned cue points

It may be that you’re simply given a ready-made FLV file.  If that’s the case, and if that FLV doesn’t already contain cue points, you can still add them at runtime with ActionScript.  The approach you use depends on how your FLV is played.  If you’re using the FLVPlayback component, which is pretty likely with recent versions of Flash, you’ll drag an instance of FLVPlayback to the Stage and give it an instance name — we’ll use videoPlayer — then enter the following ActionScript into the Actions panel for each cue point you wish to add:

videoPlayer.addASCuePoint(
  1.345,
  "caption",
  {text:"Lorem ipsum dolor sit amet"}
);
videoPlayer.addASCuePoint(
  2.345,
  "caption",
  {text:""}
);

This invokes the FLVPlayback.addASCuePoint() method on the chosen FLVPlayback instance.  The two cue points shown match the ones previously added by way of the Import Video wizard:  the first parameter is the time (in seconds, with up to three decimal places), the second is the name “caption” (this time with quotes), and the third parameter is the parameters property, which is passed in as an object (the {} brackets, with each parameter its own name:value pair).  Note:  Quotation marks used with the addASCuePoint() method work the way they should:  actual string content should be wrapped in quotes, and so should empty strings.  The curly brackets are a shorthand for the expression new Object(), so you certainly could use the following syntax:

videoPlayer.addASCuePoint(
  1.345,
  "caption",
  new Object(
    text:"Lorem ipsum dolor sit amet"
  )
);

… but why not use the shorthand?  Note that in the object, as before, our specification of “text” appears without quotes, because it’s an object property, while the value appears in quotes, because it’s that property’s string value.  Both are connected by a colon.  If you wanted to include more than one parameter, you’d separate them with commas:

videoPlayer.addASCuePoint(
  1.345,
  "caption",
  {
    text:"Lorem ipsum dolor sit amet",
    color:0xFF0000,
    someOtherFlag:true
  }
);

Note:  Syntax for the FLVPlayback.addASCuePoint() method is the same in ActionScript 2.0 and 3.0.  If you’re going the route of non-component video, such as the approach described in “How to Load External Video (FLV),” you’ll find that, for better or worse, the NetStream class does not provide a way to add cue points at runtime.  You can listen for cue points, but not add them programmatically.

If you happen to be using the old Media components designed for Flash Player 6 and 7, you’ll drag an instance of MediaDisplay or MediaPlayback to the Stage, give it an instance name — again, we’ll use videoPlayer — then enter the following ActionScript for each cue point you wish to add:

videoPlayer.addCuePoint("cue point A", 10);
videoPlayer.addCuePoint("cue point B", 12);

This invokes the Media.addCuePoint() method on the instance name of the Media component chosen.  Note, with these older components, that only two properties may be provided:  name and time (in seconds).  Thereis noparameters property, which means captioning could get a bit dicey.  Theoretically, you could use the name property (the first parameter, above) to store your captions text.

The Media components also provide another way to add cue points, which is handled through the Component Inspector panel.  Select your Media instance, open the Component Inspector panel, and scroll down.  You’ll see plus (+) and minus (-) buttons that allow you to add Name and Position parameters, which relate to the name and time properties discussed earlier.  Cue points added in this manner are still assigned dynamically, rather than baked in.

Listening for cue points

Regardless how cue points are associated with your FLV — dynamic assignment or actually embedded — they’re retrieved in the same way.  The mechanism depends, again, on how the video is played back in Flash Player.  As it turns out, the ActionScript 2.0 syntax, both for FLVPlayback and the older Media components, is almost identical.  ActionScript 3.0 is only available for FLVPlayback, since the Media components don’t run in AS3.  Let’s look at AS2 first.

Component-based, ActionScript 2.0 and 3.0

Here’s the basic structure, right out of the Help docs.  I’m using my own personal convention for variable names.

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  // instructions here
}
videoPlayer.addEventListener("cuePoint", listener);

Again, this works for FLVPlayback and the Media components, with the caveat that the Media.cuePoint event is only available in Flash Player 7 or higher.  FLVPlayback requires Flash Player 8 or higher.

In the first line, an arbitrarily named variable, listener, is declared and set to an instance of the Object class.  The :Object part after the word listener indicates to Flash that this variable is of type Object; and in fact, boom, there it is:  new Object().  In truth, everything in ActionScript is an object — arrays, dates, movie clips, you name it — but the Object class gives you a basic, generic starter kit … an Object object.  Instances of the Object class typically used for holding custom data, but in this case, the listener object is going to act as a liaison between the FLVPlayback or Media component and the cuePoint events it’s listening for.

The second line assigns a function to the cuePoint property of our listener object.  Because it’s just a generic object, it has no built-in cuePoint property.  We’re creating it with this line, and setting it to a function that receives a single parameter, here referenced by the arbitrarily named variable evt (short for “event”).  Whatever we want triggered by the cue point is set in the part that currently reads “instructions here” — more on that in a moment.

Finally, the FLVPlayback or Media component is subscribed to the “cuePoint” event by way of the listener object.  This happens via the instance name given to the component, which happens to be videoPlayer.

The difference between FLVPlayback’s and Media’s implementation of this outline happens with the incoming parameter, here named evt.  With FLVPlayback, the parameter features an info property that points to the data carried by the cue point.  What data are we talking about?  Pretty simple:  they’re the name, time, type, and optional parameters properties specified earlier.  To access these, you’ll reference the incoming parameter’s info property, then reference the desired property beneath that.  To populate a text field with our caption string, for example, the following would suffice:

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  tfCaptions.text = evt.info.parameters.text;
}
videoPlayer.addEventListener("cuePoint", listener);

This assumes a dynamic text field with the instance name tfCaptions.  The evt parameter (which carries with it the cue point’s data) is referenced by way of its variable name, then the info property, and finally the text property of the parameters property.  To grab the cue point’s name (these were all named “caption,” remember), it would look like this:

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  tfCaptions.text = evt.info.name;
}
videoPlayer.addEventListener("cuePoint", listener);

… but that would be silly, because the text field would always read “caption” from the first cue point onward.  In the example that references parameter.text, the first cue point would put “Lorem ipsum dolor sit amet” into the text field, and the second text field would replace that sentence with “” (an empty string).  To grab the cue point’s time, you’d use the expression evt.info.time.  Pretty straightforward.

Now, remember that quirky behavior described earlier? — with the double quotes, when added via the Import Video wizard?  To avoid seeing actual quotes in your tfCaptions text field, use an if statement to check if the value of evt.info.parameters.text is literally a pair of quotes.  If so, set the value of tfCaptions to a blank string by hand.  To do that, your if statement will have to check for a quoted pair of escaped quotes.  Here’s what that could look like:

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  if (evt.info.parameters.text == "\\"\\"") {
    tfCaptions.text = "";
  } else {
    tfCaptions.text = evt.info.parameters.text;
  }
}
videoPlayer.addEventListener("cuePoint", listener);

For the Media components, you still reference the incoming parameter (here, evt), but there is no info property.  The cue point’s name is simply cuePointName and the time is cuePointTime.

e.g.

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  tfCaptions.text = evt.cuePointName;
}
videoPlayer.addEventListener("cuePoint", listener);

Remember, Media components don’t have the parameters property, with its optional sub-properties.  You could give each cue point its own name and have that be the string used for captioning, but I haven’t tested how long a string such a property would accept.  It works for short phrases.

If you don’t want to populate a text field, you could fill the event handler function with whatever ActionScript makes sense to you.

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  evt.target._parent.play();
}
videoPlayer.addEventListener("cuePoint", listener);

Interesting.  What’s that?  What’s this target property?  Both the FLVPlayback and Media versions feature a target property.  In this context, target points to the component that dispatched the event in the first place.  Since both FLVPlayback and Media are ultimately based on movie clips in ActinScript 2.0, the expression evt.target here ultimately points to a movie clip.  The MovieClip class features a _parent property, which points to the timeline that contains a given movie clip.  So if your incoming parameter is named evt (your choice), then the expression evt.target._parent points to the timeline that contains the FLVPlayback or Media component.  You might have other ActionScript that pauses the timeline (stop()), and the above code, every dispatched cue point would set the timeline rolling again.

Maybe you’ve got a reason to categorize cue points into groups.  Depending on the group to which the cue point belongs, you’d like to tell a particular movie clip to go to and play certain frame label.  Assuming that movie clip has the instance name additionalContent:

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  if (evt.info.parameters.category == "A") {
    additionalContent.gotoAndPlay("the A label");
  }
  if (evt.info.parameters.category == "B") {
    additionalContent.gotoAndPlay("the B label");
  }
  if (evt.info.parameters.category == "C") {
    additionalContent.gotoAndPlay("the C label");
  }
}
videoPlayer.addEventListener("cuePoint", listener);

In this case, a switch statement would do just as well:

var listener:Object = new Object();
listener.cuePoint = function(evt:Object):Void {
  switch (evt.info.parameters.category) {
    case "A":
      additionalContent.gotoAndPlay("the A label");
      break;
    case "B":
      additionalContent.gotoAndPlay("the B label");
      break;
    case "C":
      additionalContent.gotoAndPlay("the C label");
      break;
}
videoPlayer.addEventListener("cuePoint", listener);

Sky’s the limit.

If you’re using ActionScript 3.0, the basic structure goes like this:

import fl.video.MetadataEvent;

videoPlayer.addEventListener(
  MetadataEvent.CUE_POINT,
  function(evt:MetadataEvent):void {
    // instructions here
  }
);

AS3 doesn’t require a liaison object.  Just invoke the EventDispatcher.addEventListener() method on the FLVPlayback instance (again named videoPlayer).  The FLVPlayback class inherits from the EventDispatcher class, which makes all its own class members available to FLVPlayback.  You’re listening for the MetadataEvent.CUE_POINT event, which makes you’ll have to import the fl.video.MetadataEvent class.  This is one of the relatively rare times when the import statement must be used in timeline code (in AS3, it’s always required in code that appears in external text files).  The stated event is your first parameter of the addEventListener() method; the second parameter is a function, which lets you perform whatever you’d like.  The info property, as before, provides access to your cue point data:

evt.target points to the FLVPlayback instance
evt.info.name points to the cue point’s name
evt.info.time points to the cue point’s time
evt.info.parameters points to the cue point’s optional parameters

Non-Component-based, ActionScript 2.0 and 3.0

If you’re using the NetStream class and a Video object, the object that listens for cue points becomes your NetStream instance.  No liaison listener object is required, and the incoming parameter has no info or target properties.  This is all very much more direct:

var nc:NetConnection = new NetConnection();
nc.connect(null);
var ns:NetStream = new NetStream(nc);
videoPlayer.attachVideo(ns);

ns.onCuePoint = function(evt:Object):Void {
  trace(evt.name);
  trace(evt.time);
  trace(evt.parameters);
}

ns.play("videoFile.flv");

The first several lines are taken directly from “How to Load External Video (FLV),” and the final bit attaches a function directly to the NetStream.onCuePoint event of the NetStream instance, here named ns.  You have as much leeway inside the function as you had with the FLVPlayback/Media approach, except there is no target property to refer to the Video object — because, in this case, it’s the NetStream instance that raises the event.

In ActionScript 3.0, the first block of code is slightly different from the one shown for AS2.  For the most part, it should look familiar.  Read carefully and you’ll see that much of it overlaps.  A full explanation merits its own article, but the following sample code should at least get you started:

var nc:NetConnection = new NetConnection();
nc.connect(null);
var ns:NetStream = new NetStream(nc);
videoPlayer.attachNetStream(ns);

var listener:Object = new Object();
listener.onMetaData = function(evt:Object):void {};
listener.onCuePoint = function(evt:Object):void {
  // instructions here
  trace(evt.name);
  trace(evt.time);
  trace(evt.parameters);
};
ns.client = listener;

ns.play("videoFile.flv");

Ironically, the ActionScript 3.0 version reverts back to a liaison object scenario.  This is one of the very few times AS3 strays from its overhauled event handler system, which is otherwise 99.9% consistently dependent on EventDispatcher.addEventListener(), as already seen.  (I made up the 99.9% figure, of course, but we’re talking darned near.)

After the initial few lines, which are practically the same as their AS2 counterpart, a listener variable is declared and set to an Object instance.  There are two event handlers defined.  The first is for the NetStream.onMetatData event, and the function doesn’t do anything.  This may seem odd, but it’s just a quick-and-dirty way to work around FLVs that happen to have metadata embedded in them.  If you don’t handle the event, you’ll see an error message, so this “handles” the event without actually doing anything.  The key is the NetStream.onCuePoint function, which (as always) does whatever you program it to do.  The incoming parameter, again named evt, points directly to name, time, and parameters properties, which work as illustrated earlier.

Finally, the NetStream instance is subscribed to the listener via the NetStream.client property, and the video file is instructed to play.

Leave a Reply