How to Play Flash Video Files (FLV) Sequentially

ActionScript 2.0 ActionScript 3.0

Half a year ago, I wrote a brief article on “How to Play Sound Files Sequentially” (AS2).  Recently, a reader was asking how to apply the same principle to video.  In his case, Paul had to play a commercial first, then follow it with a longer content video — and the user controls (buttons, scrubber, etc.) needed to be disabled wile the commercial was playing.  As it turns out, Paul worked out a solution of his own, partly based on some of the other articles here, but I thought it would be fun to do a quick version too.  That way, he can compare notes and anyone else can look on. 

An answer, short and sweet

To my thinking, the key is to reuse as much code as possible.  Even though we’re playing two (or any number) of videos, we only need a single instance of Video, NetConnection, and NetStream apiece.  That’s if we’re going with a non-component approach, like the one described in “How to Load External Video (FLV)” (AS2).  We’ll start with that one, to get the basic idea across, then I’ll also show how to accomplish the same thing with the FLVPlayback component.  The operative mechanism, in all cases, is to detect when the current video has ended, then automatically start playing the next one, until the list of videos is complete.

Let’s start with ActionScript 2.0.  Here’s the chunk of code:

ActionScript 2.0

var videos:Array = new Array("a.flv", "b.flv", "c.flv");
var currentVideo:Number = 0;
var duration:Number = 0;
var ready:Boolean = true;

var nc:NetConnection = new NetConnection();
nc.connect(null);
var ns:NetStream = new NetStream(nc);
videoPlayer.attachVideo(ns);
ns.play(videos[currentVideo]);

ns.onMetaData = function(evt:Object):Void {
  duration = evt.duration;
  ready = true;
};
ns.onStatus = function(evt:Object):Void {
  if (ready && this.time > 0 && this.time >= (duration - 0.5)) {
    ready = false;
    currentVideo++;
    if (currentVideo < videos.length) {
      ns.play(videos[currentVideo]);
    } else {
      delete this.onStatus;
    }
  }
}

How it works

The first line declares an arbitrarily named variable, videos, and sets it to an instance of the Array class.  This Array instance is populated with three strings, “a.flv”, “b.flv”, and “c.flv”.  Arrays are just lists, and this is a list of three FLV files.  The second line declares another variable, currentVideo, which is a number, and sets its value to zero.  Arrays start at zero, so in a moment, you’ll see this variable used to pull the first video from the list.

The third line declares a variable named duration, which will be updated later from its initial value of zero in response to the NetStream.onMetaData event, as described in “How to Determine the Completion of a Flash Video (FLV) File.”  Finally, a Boolean (true/false) variable named ready is declared and set to true.  This will be used to ensure that the onStatus event handler doesn’t perform its tasks too often in a row.

The next few lines — starting with var nc:NetConnection — use the approach suggested in “How to Load External Video (FLV)” to load the first video.  In fact, the videos array already makes its entrance:

ns.play(videos[currentVideo]);

Remember, currentVideo is 0, so the preceding line is effectively the same as saying videos[0], which pulls the first element from the videos array (the string “a.flv”, or whatever the name of your own video is; for Paul, this would be his commercial).  Ultimately, this expression resolves to ns.play("a.flv");.

The onMetaData event handler is explained in the “How to Determine” article, which leaves the NetStream.onStatus event handler.  Most of what it does is covered in the same onMetaData article, but in a nutshell, the onStatus event is dispatched whenever the video does something interesting, like start, download data, stop, etc.  It fires a couple times near the beginning and the end of FLVs, so the if statement checks if its current position (NetStream.time, represented by the expression this.time) is greater than zero and (&&) is greater than half a second less than the video’s full duration.  The very first item in the if condition, ready &&, adds one more filter level, so to speak.  Because onStatus fires so often, it can be triggered more than once even while the other two portions of the condition are true.  For this reason, ready is set to false as soon as the whole condition evaluates to true.

If all three sub-conditions are true, the ready variable is set to false.  This keeps onStatus from perfroming the next part too many times in a row.  The currentVideo variable is incremented by one (currentVideo++), which changes it from 0 to 1.  Next, a second if statement checks of currentVideo (now 1) is less than the Array.length property of videosArray.length returns the number of items in an array (we have three strings, so the property returns 3 in this case).  1 is less than 3, so the following line is executed:  ns.play(videos[currentVideo]);.  That’s the same line we saw earlier, but this time, the value of current Video has changed, and the second video plays.  When it does, the onMetaData handler will again set ready to true, which re-allows onStatus to do its thing.

This procedure will occur again at the end of the second video, at which point currentVideo will increment to 2.  2 is still less than 3, so eventually, the third video will also play.  At that point, currentVideo will increment to 3.  Obviously, 3 is not less 3, so the else clause will execute, which simple shuts down the onStatus event handler.

To fulfill the requirement of disabling user controls (see “How to Build a Basic Slider Widget (AS2)” and “How to Build a Basic Toggle Button (AS2)”), the same area of the NetStream.onStatus event handler could be used:

ns.onStatus = function(evt:Object):Void {
  if (ready && this.time > 0 && this.time >= (duration - 0.5)) {
    ready = false;
    currentVideo++;
    if (currentVideo > 0) {
      toggleButton.enabled = true;
      sliderKnob.enabled = true;
    }
    if (currentVideo < videos.length) {
      ns.play(videos[currentVideo]);
    } else {
      delete this.onStatus;
    }
  }
}

All that has changed is the addition of a new if statement, which checks if currentVideo is greater than zero.  If it is, then we’re obviously past the first video, which was the commercial.  At that point, any user controls can be turned on (they would have been disabled earlier by default).  Re-enabling your user controls will, of course, depend on how they were made.  If you’re using UI Components that ship with Flash, you’ll have to consult their respective APIs in the ActionScript 2.0 (or 3.0) Language Reference or Components Reference.  In Paul’s case, the controls were made from movie clips, which means the MovieClip class entry is what he’d need to consult.  In ActionScript 2.0, the MovieClip class happens to feature _visible and enabled properties.

ActionScript 3.0

Here’s the same basic outline in AS3:

import flash.events.NetStatusEvent;
var videos:Array = new Array("a.flv", "b.flv", "c.flv");
var currentVideo:uint = 0;
var duration:uint = 0;
var ready:Boolean = true;

var nc:NetConnection = new NetConnection();
nc.connect(null);
var ns:NetStream = new NetStream(nc);
videoPlayer.attachNetStream(ns);
ns.play(videos[currentVideo]);

var listener:Object = new Object();
listener.onMetaData = function(evt:Object):void {
  duration = evt.duration;
  ready = true;
};
ns.client = listener;

ns.addEventListener(
  NetStatusEvent.NET_STATUS,
  nsHandler
);

function nsHandler(evt:NetStatusEvent):void {
  if (ready && ns.time > 0 && ns.time >= (duration - 0.5)) {
    ready = false;
    currentVideo++;
    if (currentVideo < videos.length) {
      ns.play(videos[currentVideo]);
    } else {
      ns.removeEventListener(
        NetStatusEvent.NET_STATUS,
        nsHandler
      );
    }
  }
};

How it works

Must of the syntax overlaps.  I’ve replaced the Number type with uint in the first few lines (see “Mind Your ints and uints”).  The onMetaData event handler is structured differently — one of the rare cases in which AS3 requires a liaison listener Object instance — but the same basic activity takes place.  The NetStatusEvent.NET_STATUS event handler replaces AS2’s onStatus.  The NetStream instance (ns) is associated with a custom function, nsHandler, by way of the addEventListener() method.  The nsHandler function repeats what we’ve already seen, except that instead of using delete to unhook the event handler, it uses removeEventListener().

How About the FLVPlayback Component?

If you’re using the FLVPlayback component, fewer lines of code are needed.  You’ll simply drag an instance of FLVPlayback to the Stage, use the Property inspector to associate it with the first of your FLVs, then employ the following:

ActionScript 2.0

var videos:Array = new Array("a.flv", "b.flv", "c.flv");
 var currentVideo:Number = 0;
videoPlayer.skin = "";
var listener:Object = new Object();
listener.complete = function(event:Object):Void {
  currentVideo++;
  if (currentVideo > 0) {
    videoPlayer.skin = "MojaveOverPlaySeekMute.swf";
  }
  if (currentVideo < videos.length) {
    videoPlayer.contentPath = videos[currentVideo];
    videoPlayer.play();
  }
};
videoPlayer.addEventListener("complete", listener);

Note that the skin part is a different approach toward disabling user interface.  Because skins are actually separate SWF files, we would need a way to access the MovieClip instance in the main SWF that represents the loaded skin in order to disabled it via, say, MovieClip.enabled.  Rather than hassle with that, I’ve chosen to set the FLVPlayback.skin property to an empty string at first (""), then set it to the file name of an existing skin later (obviously, that skin must be present, or it won’t show).

ActionScript 3.0

var videos:Array = new Array("a.flv", "b.flv", "c.flv");
 var currentVideo:uint = 0;

videoPlayer.addEventListener(
  Event.COMPLETE,
  completeHandler
);

videoPlayer.mouseChildren = false;

function completeHandler(evt:Event):void {
  currentVideo++;
  if (currentVideo > 0) {
    videoPlayer.mouseChildren = true;
  }
  if (currentVideo < videos.length) {
    videoPlayer.source = videos[currentVideo];
    videoPlayer.play();
  }
}

In AS3, the DisplayObjectContainer.mouseChildren property, inherited by FLVPlayback, is enough to disable the optional skin controls, then re-enable them later.

Leave a Reply