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.

41 Responses to “How to Build a Flash Video (FLV) Progress Bar (Part 2)”

  1. kweku Says:

    Hi David,

    You are the man, thanks for this tutorial. I’ve been waiting for it, thanks again. About using a preloader to disable the knob until loading complete. Does this mean controlling the entire movie with a preloader?

    David, do you also have some tutorials on how to create a play/pause button for FLVs?

    Plus I’m new to actionscript, could you help me with how I can really learn this programming stuff step by step?

    Thanks a bundle!

  2. David Stiller Says:

    kweku,

    About using a preloader to disable the knob until loading complete. Does this mean controlling the entire movie with a preloader?

    You wouldn’t have to preload the whole movie, just the FLV itself. Notice that the NetStream class features two useful properties, bytesLoaded and bytesTotal, for just this sort of thing. If you wanted to, you could repeatedly compare those values, following the same principle as described in “How to Tell When an External SWF has Fully Loaded” (in that one, loaded and total values are the result of method calls, but in this case, you could simply compare the properties themselves).

    Play/pause for FLVs is covered in “How to Control Video (FLV) without a Component.”

    As for programming in general … I’d say your best bet is to think in terms of objects and recognize that, in ActionScript, just about everything can be thought of in terms of objects. Classes define objects, so look up class entries in the Help docs to find what you’re after. If you’re looking for functionality on movie clips, look up the MovieClip class. if you’re looking for functionality on non-static text fields, look up the TextField class, and so on. Check out one of my earliest posts on this blog for more info. :)

  3. kweku Says:

    David,

    Thanks a lot for everything. One more thing, I think I wasn’t very clear on the play pause buttons. What I actually wanted to know was a play pause toggle button, where one can switch between the graphic elements that represent PLAY and PAUSE. Thanks.

  4. MattyG Says:

    David,

    These posts have helped me immensely! Thanks for sharing with the world.

    Here’s a silly question, how would I load the video but instead of having it play right away start it so that it is paused? I am trying to create a pre-loader so I can buffer some of my larger videos a bit so I’d like to have the video be stopped as it is loaded.

    Thanks again!

  5. kweku Says:

    Hi David,

    Please help me with this. How do I put links after streaming the flash video, like the way YouTube does it where you have links to other videos?

    Many thanks

  6. David Stiller Says:

    To kweku …

    I’ll help you with the pause/play button. In fact, I’ll make a blog entry about it, so keep your eyes peeled [note: that article has since been written]. As for YouTube-like links … well, it looks like they’re pausing the video on the last frame. Then they drop a semi-transparent black rectangle over it with button-shaped holes. That could be a pre-designed movie clip either added at runtime (MovieClip.attachMovie()) or set to invisible beforehand (MovieClip._visible) and revealed afterward. The buttons could easily be part of that movie clip, and would invoke NetStream.play() on that ns instance, providing it a new FLV path.

    To MattyG …

    As soon as you specify the FLV to play, invoke NetStream.pause(), which will halt the video. That may pause it a few milliseconds past the beginning, so you can also use NetStream.seek() to send it to zero. When it’s time to actually play, invoke NetStream.pause() again (note in the Help docs that the pause() method accepts an optional Boolean parameter to explicitly determine the pausing behavior — otherwise the method is a toggle). In my own experience, I’ve noticed that an immediate call to seek() after pause() doesn’t always work, so to hack it, consider seeking to zero after a slight delay. You could use setTimeout() for that.

  7. Dmitry Says:

    Useful, thanks

  8. klp Says:

    Thanks David for the comment you posted for MattyG’s question. Just for clearity, would you agree that we are able to preload the full duration of the flv and buffer it at the same time, while Netstream.seek() is set to 0? or is it not possible? if so, how do we do that?

    Thanks

  9. David Stiller Says:

    klp,

    The video loads whether or not it’s playing. You got it! You can seek where you like, except for video frames that haven’t yet loaded.

  10. mischa Says:

    Thank you David for this very helpful tutorial. I do have a question though.

    I’m starting my movie paused to let it preload for a bit. I want to put a darker ‘loaded’ bar just above the ‘track’ track (and below the ‘knob’ layer) to indicate how much of the movie is already preloaded. But I’m having trouble with the bytesLoaded and bytesTotal properties.

    Any thoughts?

    Thanks

  11. David Stiller Says:

    mischa,

    You’re certainly on the right track. :) I would create another rectangle movie clip (or even reuse the track clip, perhaps darkening it in the Property inspector with a Color property, such as Brightness or Tint). Give the new rectangle its own instance name, such as loader.

    When it comes time to load the video, start your setInterval() loop in motion as before, but add some new code to the updateKnob() function. The new code would set the MovieClip._xscale property of your loader instance to NetStream.bytesLoaded divided by NetStream.bytesTotal, times 100:

    function updateKnob():Void {
      knob._x = track._x + ns.time * ratio;
      loader._xscale = ns.bytesLoaded / ns.bytesTotal * 100;
    }

    That’s a quick and dirty approach, and if you trace the same new expression …

    trace(ns.bytesLoaded / ns.bytesTotal * 100);

    … you’ll see what’s going on.

    The downside to my suggestions is that the new line of code is redundant after the video has fully loaded. Plus, in the original example, the setInterval() loop is temporarily canceled when the user grabs the knob. So, ultimately, you could give the loader rectangle its own setInterval() loop — making sure to use a uniquely named id variable to capture that one’s interval id, as well as a new function to execute … say, updateLoader() — and use clearInterval() on that loop after ns.bytesLoaded / ns.bytesTotal * 100 equals 100.

    [Note: “How to Build an Interactive Flash Video (FLV) Load Progress Bar” now addresses these questions.]

  12. Marius Bratu Says:

    Hello David,

    First of all, i would like to say that you are doing a great job here. Its THE MOST usefull blog that i’ve read until now.

    Second, i have a problem, and maybe you make a little time for me to help. I have built a flv player, using your tutorials here. The problem i ran into its at my progress bar. Here its the link to this player: http://www.autenticmedia.com/player/player_v2.html.

    Now let me explain the problem i have. The only difference between the progress bar you are making in this tutorial, and my progress bar, its that i also have a bar for loading progress(ie show how much was loaded from the flv file). Now, the weird thing that i dont understand. The knob (which its showing the playing progress), for SOME flvs (the ones i encode), its left far behind than the loading bar. Please note, that i’ve tested on a dial-up connection, and in a normal way the loading bar and the knob should be in the same position. And if i drag the knob forward, to the position where to loading bar is, it SNAPS BACK to original position. Why? Because it gaves me a NetStream.Seek.InvalidTime.

    Anyway, like i said before, this its happenign just for some flvs, that i create with an application. Here its a link to such flv, a BAD flv : http://s3.amazonaws.com/episodeflash/AlessioAlex327072007_0016_54.flv

    I will give you also a link to a GOOD flv, thats working perfectly: http://blip.tv/file/get/Make-CompetingAtTheDefconRobotChallenge201.flv

    I beleve its a problem in the way i encode flvs. Maybe you can help me, and tell me what i am doing wrong on encoding. In the test player i provided i also show the metadata’s. I really cant figure out where i am doing wrong.

    Any help would be kindly appreciated.
    Cheers, Marius.

  13. David Stiller Says:

    Marius,

    First of all, i would like to say that you are doing a great job here. Its THE MOST usefull blog that i’ve read until now.

    Wow, thanks! Glad to hear that.

    And if i drag the knob forward, to the position where to loading bar is, it SNAPS BACK to original position. Why? Because it gaves me a NetStream.Seek.InvalidTime.

    That could make sense, given that these are progressive download requests. When you say it snaps back to the original position, do you mean it snaps back to the video’s beginning (seek(0))? Or are you saying it fails to seek and jumps back to where the video happened to be? I’m guessing the latter.

    If one of your encoding processes happens not to include a duration property, or something equivalent, you’ll have to hard code the duration yourself. In this article here, How to Determine the Completion of a Flash Video (FLV) File, I mention a way to run through the metadata with a trace() function (using for..in) to see exactly what your metadata gives you (if anything).

    One idea that occurs to me is that you could redo the dragging to make sure the knob can never be dragged beyond the width of the bar that indicates the download progress. Rather than MovieClip.startDrag(), you could use an onEnterFrame or setInterval() loop to update the value of the MovieClip._x property of the knob movie clip. Use an if statement to set the knob’s _x property to the smaller value in a comparison between the mouse’s position and the download progress bar’s _x plus its _width. You could use Math.min() to make that determination.

    [Note: “How to Build an Interactive Flash Video (FLV) Load Progress Bar” now addresses these questions.]

  14. kweku Says:

    Hi David,

    I also have a similar issue, that is to create a loading bar to indicate how much of the video has loaded and how much is left. Also to make the track or loader clickable so that you can jump to any where on the video by clicking just like Youtube.

    Thanks.

  15. David Stiller Says:

    kweku,

    Check out my reply to mischa (just before my reply to Marius) for a few thoughts toward a load progress indicator. You’ll almost certainly want to create a new loop — MovieClip.onEnterFrame or setInterval() — to handle the change in the new rectangle, as I mentioned to mischa. That way you’ll be able to independently stop that loop when loading is complete, but allow the progress/scrubber knob to continue.

    To make a click-to-jump interface, I would add a MovieClip.onPress event handler to the original track movie clip. Have its function note the MovieClip._xmouse position and compare that value against the same ratio expression used by the knob. In the same way I suggested to Marius, you could use an if statement to ensure that you only seek() if the mouse is less than the width and position of the load progress bar.

    [Note: “How to Build an Interactive Flash Video (FLV) Load Progress Bar” now addresses these questions.]

  16. kweku Says:

    Hi David,

    I’m having problem with the clickable progress bar. Like I said earlier I’m new to actionscript in particular and for that matter programming. I kind of get stacked or confused at certain times. If you could please help me with the coding.

    Many thanks.

  17. heavypops Says:

    many thnx 4 the great blog
    it’s nice 2 b able 2 step outside the standard sknz and b able 2 control videos w actionscript
    I’ll definatly b slidin’ thru again 2 c wht othr goodies u post!

  18. David Stiller Says:

    To kweku …

    Keep an eye on the main page. ;) I’ll post a follow up that steps through what you, Marius, and mischa were working through. [Note: That article, “How to Build an Interactive Flash Video (FLV) Load Progress Bar” is now available.]

    To heavypops …

    Thanks!

  19. kweku Says:

    OK David,

    thanks a lot. In the meantime I’ll keep trying to see how it goes.

    Many Thanks!!!

  20. vilim Says:

    Yessss!!!
    Finaly a great and simple tutorial for preloading FLV-s!
    Thank u man :)

  21. David Stiller Says:

    vilim,

    Thanks!

  22. Keith Says:

    HI David,

    great tutorial! Im having one issue with the seek bar. When I release the knob it does not advance the ns.time or update the new knob._x position. The slider knob does drag but onRelease just snaps back to the original time the flv was at when I Press the knob. (I read the post from Marius, but my duration is there).
    I have traced duration and all other vars and they all seem to be working fine, just will not update the time or knob._x properties. The FLV files reside on FMS 2.0, could that be the issue? I noticed this tutorial was for progressive downloads.

  23. David Stiller Says:

    Keith,

    In answering someone else’s question on FMS (streaming) video, I learned that truly streamed FLVs dispatch the NetStream.onMetaData event every time the NetStream.seek() method is called. That likely means you’re getting a conflict between two triggerings of updateKnob(), because the onRelease handler and the onMetaData handler both start an updateKnob() loop.

    You could step around this issue with a quick-and-dirty Boolean flag:

    var firstTime:Boolean = true;
    
    ns.onMetaData = function(evt:Object):Void {
      if (firstTime) {
        firstTime = false;
        duration = evt.duration;
        ratio = track._width / duration;
        id = setInterval(updateKnob, 50);
      }
    };

    … but then you’d have to set that firstTime variable back to true if you load additional FLVs.

  24. Keith Says:

    Excellent! That worked perfect. thank you David!

  25. David Stiller Says:

    Keith,

    Glad to hear that! Woo-hoo!

  26. HeavyPops Says:

    Dear David
    Once again many thanx!
    I am wondering how to apply a text field with total video length, the one I used breaks the seek bar and scrub handle (maybe something to do with the duration).

    Here’s the code I used:

    ns.onMetaData = function(metadata)
    {
    timerDuration = metadata.duration;
    var dur_seconds:Number = timerDuration;
    var minutes_dspl = Math.floor(dur_seconds/60);
    var seconds_dspl = Math.floor(dur_seconds%60);
    if (minutes_dspl

  27. HeavyPops Says:

    …sorry it keeps cutting off the code ???

  28. HeavyPops Says:

    Is there anyway to apply a function to the handle in the SeekBar component?
    I see it’s name is seekBarHandle_mc from the FL debugger but I can’t apply anything to it.
    This is what I tried:
    _level0.seekBarHandle_mc.onPress = function()
    {
    trace (”handle!”);
    }
    …no luck!
    Thanx

  29. David Stiller Says:

    HeavyPops,

    I followed up with you via email to see about getting the rest of your code that got cut off (WordPress likes to mangle anything that contains the characters < and > (use &lt; and &gt;, respectively).

    As for the seekBarHandle_mc handle question … that’s a good one! I can see the same reference in my AS2 Debugger panel, and you’re right, that movie clip reference just doesn’t seam to pick up. Now, keep in mind, that little triangle already has a number of mouse-related event handlers — you can click and drag that handle, after all — but I can’t explain why a new onPress event handler would fail to override the existing on in an AS2 setting. I’ve used the Debugger panel in the past to find “hidden” references and manipulate them via ActionScript.

  30. Markito Says:

    Hi David,

    Your help is always appreciated! I am having some issues with the seek() command from the NetStream class - I used some of your other tutorials to create a drag-enabled playhead and scub bar, but when I tell the clip to seek, it doesn’t always work. Sometimes I’ll seek to a section of the video and it will play fine, but other times the screen will show the correct frame, but the video stops playing. I read online somewhere that you can only seek to keyframes in FLV files, say it ain’t so?

    Thanks

  31. David Stiller Says:

    Markito,

    What you read can be true, but it depends on the circumstances. If you’re streaming your video content via Flash Media Server, Red5, or the like, then you can seek to any frame you like. If you’re progressively downloading, then seeking goes to the nearest video keyframe after the specified time, and only if that part of the file has already loaded.

    P.S. Now I just read one of your other questions, elsewhere on this blog, and I remember that you’re using Red5. To my understanding, the streaming should indeed allow you to seek where you like.

  32. HeavyPops Says:

    David
    This is related to the question for the handle in the SeekBar component;
    Do You know where those on mouse event handlers are located for the seeBarHandle_mc. I’ve searched through the flash files and can’t find it, maybe an additional function can be added there. ???

    Another thing I’ve been toying with ( in my pursuit of creating a video player like YouTube ) is in the seek bar where the left side of the scrub handle is lighter than the right side.
    I tried:

    played._x = knob._x;

    …”played” is a movie clip that darkens the seek bar with the registration point of on the right. It places the “played” clip where the knob starts but doesn’t follow it as it moves.

  33. David Stiller Says:

    HeavyPops,

    Honestly, I haven’t dipped into those FLVPlayback-related source files much. FLVPlayback is a bit different from the other components, but they’re all pretty complex beasts. The code you’re after isn’t going to be in the FLAs, though. It’s going to be in ActionScript files (class files). In my Win XP machine, those files are located …

    C:\Program Files\Adobe\Adobe Flash CS3\en\First Run\
    Classes\mx\video\

    It really might be easier to start from scratch! ;)

    I tried:

    played._x = knob._x;

    …”played” is a movie clip that darkens the seek bar with the registration point of on the right. It places the “played” clip where the knob starts but doesn’t follow it as it moves.

    In order for the played movie clip to follow the knob movie clip, you’ll have to repeatedly update played’s position. The single line of code you showed will set those _x properties to the same value at that moment. To repeat the process, you’ll have to put that line inside the loop that continuously moves knob (in the blog article that happens inside a setInterval() loop).

  34. HeavyPops Says:

    Thanx!

  35. okeenoodle Says:

    When working with flv files, is there a way to get a millisecond duration & position, similar to working with audio files (mySound.duration and mySound.position return milliseconds). I’m looking to control play/pause/seek functions within 10-20 millisecond window in order to accurately and dynamically attach sound objects to a flv. But everything I’ve found so far controls flv videos by seconds rather than milliseconds.

  36. David Stiller Says:

    okeenoodle,

    There are two ways to go. If you’re streaming your video via Flash Media Server, Red5, or the like, you can seek to any video frame, which may or may not fall on a given millisecond (depends entirely on the framerate of the movie, and I’ve never seen a 1000fps video framerate — not even sure that’s possible). If you’re progressively downloading, as shown in the code sample in the article, then you can only seek to video keyframes, and not only that, but keyframes that have already been loaded. You can still get millisecond-specific, but the chances would be even worse that a video keyframe would fall exactly along a given millisecond. For progressive download, the video seek to the first keyframe encountered after the specified time (use up to three decimal places to indicate times less than a second).

  37. Alex Says:

    WONDERFUL blog…I’m having to do more and more flash at work and you have had a big part of me continuing to be employed! ha! I have a problem with the code above though. I’m following your instructions precisely but when I try to run it it gives me the following error:

    **Error** Scene=Scene 1, layer=actions, frame=1:Line 11: There is no property with the name ‘onMetaData’.
    ns.onMetaData = function(evt:Object):Void {

    any ideas? Thanks so much

  38. David Stiller Says:

    Alex,

    The onMetaData event requires Flash Player 7 or higher — could that be it?

  39. jdq Says:

    dave, you told “okeedoodle”

    “For progressive download, the video seek to the first keyframe encountered after the specified time (use up to three decimal places to indicate times less than a second).”

    So you are saying SEEK is accurate to thousand’s of a second (to the extent that the keyframes are there)??

    As Okeedoodle says “But everything I’ve found so far controls flv videos by seconds rather than milliseconds” — this is the impression I got too (when talking about progressive downloads). If it’s true that seek can be used to something like frame accuracy, as opposed to whole second accuracy, that will be great! I hope you are right!

  40. David Stiller Says:

    jdq,

    So you are saying SEEK is accurate to thousand’s of a second (to the extent that the keyframes are there)??

    Yes, to the extent that a keyframe is present, you have approximately millisecond accuracy. Keep in mind that most video is encoded at NTSC and PAL framerates or slower — which aren’t anywhere near 1,000fps (not by a long shot!) … but at least you can specify a seek to the nearest millisecond. Might be very hard to demonstrate practically, though.

    A hint in support of my assertion is that the NetStream.seek() method in AS3 accepts a Number datatype (floating point) rather than, say, int (integer). If that sort of accuracy is really important, though, your best bet is very like a streaming server, which allows you to seek to any video frame (not just keyframes).

  41. tishuggah Says:

    Hello EveryOne- I come 2 years after the last post. I hope some one will hear me !!
    So here’s my problem the same as Keith but different … I have made a plyer with this code in a MovieClip, this code is in frame 2. In an other MovieClip, we can choose to watch other video. When you choose one, the player go to an play frame1, son when it arrive at the frame 2 , with your code the new vido can ba played.
    The vide plays !!! YEAAAAAAAAAHHHH! The progress bar and the downlad bar act has it should, !!! YEAAAAAAAAAHHHH again! But when you want to drag de knob or click on the progress bar… every thing go wrong… The video stops, or it only seek the next cue point, or the progress bar doesn’t work any more.
    I’ ve seen that you said tha tit was due to the NetStream.onMetaData. but does avery one know how to solve this? Even if as2 is obselte in front of as3…

    Thank any way for this great code !!!

Leave a Reply