CMXtraneous: Flash

Right on the edge of useful

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

Posted Tuesday, July 10, 2007 9:20:35 AM by David Stiller

David Stiller

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("monovideo2.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.

Category tags: Flash

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

Posted Thursday, July 05, 2007 7:04:55 AM by David Stiller

David Stiller

Following on the heels, at least conceptually, of “How to Control Video (FLV) without a Component” here’s a quick look at how to indicate the progression of an FLV by way of a custom made progress bar (thanks for the suggestion, Rick!).  In a follow-up article, I’ll show how to make the progress bar interactive by having the draggable knob seek to keyframes in the FLV.  It turns out that much of the code for this first part derives from “How to Determine the Completion of a Flash Video (FLV) File,” which shows how to determine video length, with and without the use of Components, in ActionScript 2.0.  In this article, we’ll be going the non-Component route, because FLVPlayback already has a progress bar.  Before we delve into the code, we need to prepare two small movie clips.

Creating the progress bar itself

The artwork can get as pretty as you like, but for illustrative purposes, all you need for the progress bar’s track is a rectangle.  Mine happens to be 12 x 180.  Draw the rectangle and convert it to a movie clip, making sure the registration point is in the upper left corner.  Give it the instance name track.  For the knob, draw a smallish circle or almond shape.  My circle is 15 x 15.  Convert it to a movie clip, making sure the registration point is in the center, and give it the instance name knob.  These can be on separate layers or the same, it doesn’t matter; just make sure the knob is above the track and the registration points are set as described.  Position the knob at the left edge of the track.

I’ve you’ve been reading the other video-related articles on this blog, you’ll already be familiar with the non-Component display of FLVs.  If not, you may want to give a quick read through the previous material.  In addition to the two movie clips that comprise the progress bar, you’ll also need a Video object on the Stage.  In this example, we’ll use the instance name videoPlayer.

An answer, short and sweet

Here’s the ActionScript, and we’ll step through it afterward.

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(
    function ():Void {
      knob._x = track._x + ns.time * ratio;
    }, 50
  );
};

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

How it works

The first three variables, duration, ratio, and id, prepare the way for three values used later in the code.  The duration value is later determined in a NetStream.onMetaData event handler as described in the article on determining video length.  The ratio value — this is the new part — is also set in the same event handler.  id is used to track a setInterval() timer loop, which is also introduced in the event handler.  The zeroes, for now, simply give these numbers a default value.

The next block (five lines beginning with var nc) prepares the Video object for play and loads an external FLV file.  This block of code was originally explained in “How to Load External Video (FLV).”

Now comes the brains of it.

ns.onMetaData = function(evt:Object):Void {
  duration = evt.duration;
  ratio = track._width / duration;
  id = setInterval(
    function ():Void {
      knob._x = track._x + ns.time * ratio;
    }, 50
  );
};

As recounted elsewhere on this blog, the onMetaData event is dispatched as an FLV file begins to play.  Depending on how a video file is converted to FLV, it may be given a handful of extra descriptive information, including the video’s duration.  In the above code, this information gets a free ride in via the arbitrarily named parameter evt.  This incoming object carries with it a duration property that is:  a) passed to the previously declared duration variable and b) used to determine a ratio between the width of the track movie clip and the length of the video.  Thanks to this ratio, the knob movie clip can be sent to any location between the left and right edges of the track movie clip in correspondence with how much of the video has been played.  Let’s see how that works.

The id variable is simply a value that keeps track of setInterval() loops.  The setInterval() function returns a value, much like the Math.round() method.  With Math.round(), you feed in a number, and the value you get back is the nearest integer.  With setInterval(), you feed in a) a function to perform and b) in interval in which to perform it.  The function gives back a number that says, “Okay, this is the identifier for the particular loop you just started.”  And what function have we fed in?  In this case, we’re setting the MovieClip._x property of the knob instance to the horizontal position of track plus the result of the video’s current location (the NetStream.time property of the ns instance) multiplied by the ratio variable.  This is performed every 50 milliseconds once the video begins.

To save on processor cycles, the looping should stop when the video ends, so the final block of code uses clearInterval(), with id as a parameter, to do just that.  This happens in response to a NetStream.onStatus event that checks if the video’s progression is both past zero and equal to or greater than a half second shy of the duration value.

Category tags: Flash