How to Build an Interactive Flash Video (FLV) Load Progress Bar

ActionScript 2.0

A number of readers have expressed interest in the last handful of video-related blog entries.  These include “How to Build a Flash Video (FLV) Progress Bar” (Part 1 and Part 2) and, somewhat related, “How to Build a Basic Slider Widget (AS2).”  In some of the blog comments, mischa, Marius, and kweku were asking about how to display the load progress of an FLV file.  This was in addition to the existing functionality, which allows the user to see how much of the video has played and also to seek by dragging a knob along a track.  Questions included a) how to make sure the user couldn’t drag the seek knob beyond the loaded portion of the video and b) how to make the track itself clickable, so the user could bypass the knob if desired.  Let’s take a look at how to incorporate these new elements by adding them to the ActionScript 2.0 presented in Part 2 of the progress bar series. 

Patching on the new stuff

The code we’re going to build on is laid out under the “An answer, short and sweet” heading of “How to Build a Flash Video (FLV) Progress Bar (Part 2),” so make sure to get yourself acquainted with the project to that point, which may even require that you read its prequel.

Here, in a one swoop, is the updated code.  There are four objects on the Stage now, and one of them is new.  Starting from the bottom up:  an instance of a movie clip symbol shaped like a rectangle, which has the instance name track; another instance of that same movie clip symbol, this time with the instance name loader (this is the new object); an instance of a circular or almond shaped symbol with the instance name knob; and finally, a Video object with the instance name videoPlayer.  The registration point of knob is in the center of that movie clip, and the registration point of the rectangle is in its upper left.  The loader rectangle has been darkened a bit due to an adjustment of the Color: Brightness property in the Property inspector.  Both rectangles are stacked like playing cards, with loader on top (only loader is visible) and knob is positioned on the left side, center, of loader.

Here’s the code:

var duration:Number = 0;
var ratio:Number = 0;
var idTracking:Number = 0;
var idLoading:Number = 0;

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

ns.onMetaData = function(evt:Object):Void {
  duration = evt.duration;
  ratio = track._width / duration;
  idTracking = setInterval(updateKnob, 50);
  idLoading = setInterval(updateLoader, 50);
};

ns.onStatus = function(evt:Object):Void {
  if (this.time > 0 && this.time >= (duration - 0.5)) {
    trace("Video complete");
    clearInterval(idTracking);
    delete this.onStatus;
  }
};

function updateKnob():Void {
  knob._x = track._x + ns.time * ratio;
}

knob.onPress = function():Void {
  clearInterval(idTracking);
  ns.pause(true);
  this.onMouseMove = function():Void {
    if (track._xmouse > 0 && track._xmouse < loader._width) {
      this._x = track._x + track._xmouse;
    }
  }
}
knob.onRelease = function():Void {
  delete this.onMouseMove;
  ns.seek((this._x - track._x) / ratio);
  ns.pause(false);
  idTracking = setInterval(updateKnob, 50);
}
knob.onReleaseOutside = knob.onRelease;

function updateLoader():Void {
  loader._xscale = ns.bytesLoaded / ns.bytesTotal * 100;
  if (loader._xscale >= 98) {
    loader._xscale = 100;
    clearInterval(idLoading);
  }
}

track.onRelease = function():Void {
  if (this._xmouse > 0 && this._xmouse < loader._width) {
    clearInterval(idTracking);
    idTracking = setInterval(updateKnob, 50);
    ns.seek(this._xmouse / ratio);
  }
}

Examining the changes

The above code may look like a lot, but we haven’t gutted the original functionality.  It’s still there, for the most part unchanged.  The new parts shouldn’t be overwhelming.

The first update is a change in one of the variables’ names.  The current idTracking variable used to be called id.  In the new version, we need two such “id” variables — one for the previous knob tracking and one for the new loader progress — so to keep them distinguishable, I declared them as idTracking and idLoading within the first few lines, which obviously required a search-and-replace for “id” throughout the remaining code as it was.

The NetConnection and NetStream preparation hasn’t changed.  The NetStream.onMetaData handler has one new line:

idLoading = setInterval(updateLoader, 50);

What this line does is repeatedly trigger a new function, updateLoader(), every 50 milliseconds.  This happens along with the initialization of the duration and ratio variables from before, and the repeated triggering of the updateKnob() function.

NetStream.onStatus stays the same.  The updateKnob() function also stays the same.

The MovieClip.onPress event handler for the knob movie clip changes a bit — not in principle, but in approach.  Pressing the knob still initiates a dragging routine, but instead of using MovieClip.startDrag(), as before, this time a MovieClip.onMouseMove event handler is assigned to knob dynamically.  The function for this handler updates the MovieClip._x property of knob in relation to the position of the mouse over the track movie clip (MovieClip._xmouse), plus track’s _x position.  Because this happens whenever the mouse moves, it looks just like a constrained horizontal drag, but there’s a catch.  This mechanism is wrapped in an if statement that causes the dragging to only occur when two conditions are met:  a) when track._xmouse is greater than 0 (this keeps the knob from falling off the left edge of the track) and (&&) b) when track._xmouse is less than the width of the loader clip.  Why does this matter?  Well, loader’s width will be changing over time, as dictated by the updateLoader() function, and its width illustrates how much of the video has loaded.  The second condition of the if statement ensures that knob can’t be dragged beyond the representation of the how much of the video is available.

knob’s MovieClip.onRelease handler is close to what it was before:  rather than invoking MovieClip.stopDrag(), it deletes the function assigned to the onPress event, which stops the dragging in much the same way conceptually.

Now we come to the two new functions.

The first is updateLoader(), which adjusts the width of loader based on how much of the FLV file has loaded.

function updateLoader():Void {
  loader._xscale = ns.bytesLoaded / ns.bytesTotal * 100;
  if (loader._xscale >= 98) {
    loader._xscale = 100;
    clearInterval(idLoading);
  }
}

In ActionScript 2.0, the MovieClip._xscale property is based on percentage.  Here, the NetStream.bytesLoaded and bytesTotal properties are divided, and the quotient is multiplied by 100.  Because the registration point for this rectangle is on the left, the shape will seem to grow from a very narrow sliver to a wide rectangle as the bytes values update.  When loader is almost fully grown (loader._xscale >= 98), its _xscale property is set to 100 for good measure and the repeated updateLoader() triggering is halted by way of the clearInterval() function, which is fed the idLoading variable as a parameter.

And finally, here’s the function that handles the clicking version of seeking.  Same idea as the draggable knob, but this is taken care of simply by mouse clicks.

track.onRelease = function():Void {
  if (this._xmouse > 0 && this._xmouse < loader._width) {
    clearInterval(idTracking);
    idTracking = setInterval(updateKnob, 50);
    ns.seek(this._xmouse / ratio);
  }
}

Again, an if statement checks to ensure that seeking only occurs when the mouse is clicked within a meaningful area (rightward from the left edge and leftward from the width of loader).  The current onStatus event handler kills tracking when the video reaches its end.  Dragging and releasing knob restarts tracking, so that part is fine, but the clicking needs to do the same thing, in case the video has already reached completion.  That explains the clearInterval() and setInterval() lines.  Finally, seeking is performed based on the location of the mouse horizontally divided by the same ratio used in the knob logic.

Leave a Reply