How to Build a Flash Video (FLV) Progress Bar (Part 2)

ActionScript 2.0

In Part 1, not quite a week ago, we looked at a relatively simple way to track the progress of an FLV file as played without the FLVPlayback Component in a SWF.  Here in Part 2, we’ll make the knob draggable, causing the video to seek to the point in time that corresponds to the knob on its track.  As it turns out, the ActionScript involved doesn’t change all that much.  It may look like a lot more code, but the mechanics should be easy enough to follow. 

An answer, short and sweet

Picking up from last time, we have a Video object on the Stage with the instance name videoPlayer.  We have two movie clips, also on the main timeline, with the instance names knob and track.  Here’s the code, and I’ll step through what changed from last time.

var duration:Number = 0;
var ratio:Number = 0;
var id: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;
  id = setInterval(updateKnob, 50);
};

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

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

knob.onPress = function():Void {
  clearInterval(id);
  ns.pause(true);
  var vertical:Number = track._y + (track._height / 2);
  this.startDrag(
    true,
    track._x,
    vertical,
    track._x + track._width,
    vertical
  );
}
knob.onRelease = function():Void {
  this.stopDrag();
  ns.seek((this._x - track._x) / ratio);
  ns.pause(false);
  id = setInterval(updateKnob, 50);
}
knob.onReleaseOutside = knob.onRelease;

How it works

From line 1 (var duration:Number = 0;) through the NetStream.onStatus event handler (ns.onStatus …) only one change has occurred.  In the original, the function reference in the setInterval() loop was spelled out in over three lines, right there in the first parameter slot (before the comma and the 50):

…
id = setInterval(
  function ():Void {
    knob._x = track._x + ns.time * ratio;
  }, 50
);
…

In the updated version, the function is now a named function — the custom updateKnob() — that appears immediately below the onStatus handler.  Why move to a named function approach?  The answer is simply ease of use.  In the new version, the concept of positioning the knob repeatedly occurs twice, so rather than type out the same function literal two times, I’ve chosen to give the function its own definition and call that instead.  Using a named function here makes it easier to update this code in the future, because you’ll only have to change the code in one place.  Note that the updateKnob() function is virtually identical to its previous incarnation.

So far, then, the code is the same as last time, speaking from a practical standpoint.  Here’s where the change happens.

The knob movie clip gets three event handlers of its own:  Button.onPress, Button.onRelease, and Button.onReleaseOutside.  This is typical of a drag-and-drop approach in ActionScript 2.0.  Pressing starts a drag, and the other two stop the drag.  In this case, there’s a bit more to it, though.  The first thing the onPress handler does is to stop the setInterval() loop:  the repositioning of knob every 50 milliseconds should come to a halt, so as not interfere with the dragging.  Next, the NetStream.pause() method is invoked on the ns instance, pausing the video.  A temporary variable, vertical, is set to the position of track plus half its height, which essentially means the vertical center of the track movie clip.  Why?  Well, we’re going to use that value twice, so like the declaration of the updateKnob() function, this variable saves us a bit of typing.  Finally, the MovieClip.startDrag() method is invoked on the knob instance — here, the global this property refers to knob — and five optional parameters are passed in.  The first means that dragging will snap knob’s registration point to the mouse.  The rest indicate an arbitrary bounding box in which knob’s dragging should occur.  The left-most boundary should be track’s _x position.  The top-most should be halfway down the track’s vertical area, a value we just stored in the vertical variable.  The right-most should be track’s right edge, described the by expression track._x + track._width, and finally, the bottom-most should be the same as the top-most.

When the user lets go … that’s when the rubber hits the road.  First, dragging is stopped, by virtue of the MovieClip.stopDrag() method.  Next, the NetStream.seek() method is invoked on the ns instance, with the expression (this._x - track._x) / ratio — the reverse of the forumula used to determine where to position knob in the setInterval() loop — fed in as the parameter.  The video is again set in motion (ns.pause(false)), and finally, the setInterval() loop is reconvened.  This is where the custom adjustKnob() function comes in handy.  Note that id, again, is set to the return value of the call to setInterval(), which allows the looping to be stopped again, if need be, either by the end of the video or another drag from the user.

Because the dragging is constrained, it’s possible the user may press over knob, but release outside of it.  To cover that possibility, the Button.onReleaseOutside event is handled identically to onRelease, simply by setting its function to the same one associated with the other.

Keep in mind

FLV files downloaded progressively, as in this example, can’t be sent to video keyframes that haven’t yet loaded.  This means the scrubber doesn’t work fully until all of the FLV has been cached on the user’s hard drive.  The above example won’t break if the user drags to a position that corresponds to a location on the video that hasn’t yet loaded — the knob will simply snap back into being animated again along the track — but be aware of the limitation.  You may want to use a preloading technique to disable knob until loading is complete.

Leave a Reply