How to Build a Flash Video (FLV) Progress Bar (Part 2)
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.
July 11th, 2007 at 7:04 am
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!
July 11th, 2007 at 9:43 am
kweku,
You wouldn’t have to preload the whole movie, just the FLV itself. Notice that the
NetStreamclass features two useful properties,bytesLoadedandbytesTotal, 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
MovieClipclass. if you’re looking for functionality on non-static text fields, look up theTextFieldclass, and so on. Check out one of my earliest posts on this blog for more info.July 12th, 2007 at 4:31 am
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.
July 20th, 2007 at 12:14 am
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!
July 20th, 2007 at 3:51 am
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
July 20th, 2007 at 8:42 am
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 invokeNetStream.play()on thatnsinstance, 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 useNetStream.seek()to send it to zero. When it’s time to actually play, invokeNetStream.pause()again (note in the Help docs that thepause()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 toseek()afterpause()doesn’t always work, so to hack it, consider seeking to zero after a slight delay. You could usesetTimeout()for that.July 24th, 2007 at 11:05 am
Useful, thanks
July 24th, 2007 at 4:11 pm
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
July 27th, 2007 at 2:21 pm
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.
August 3rd, 2007 at 2:05 am
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
August 5th, 2007 at 7:55 pm
mischa,
You’re certainly on the right track.
I would create another rectangle movie clip (or even reuse the
trackclip, 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 asloader.When it comes time to load the video, start your
setInterval()loop in motion as before, but add some new code to theupdateKnob()function. The new code would set theMovieClip._xscaleproperty of yourloaderinstance toNetStream.bytesLoadeddivided byNetStream.bytesTotal, times 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 theknob. So, ultimately, you could give theloaderrectangle its ownsetInterval()loop — making sure to use a uniquely namedidvariable to capture that one’s interval id, as well as a new function to execute … say,updateLoader()— and useclearInterval()on that loop afterns.bytesLoaded / ns.bytesTotal * 100equals 100.[Note: “How to Build an Interactive Flash Video (FLV) Load Progress Bar” now addresses these questions.]
August 9th, 2007 at 3:12 pm
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.
August 15th, 2007 at 9:35 am
Marius,
Wow, thanks! Glad to hear that.
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
durationproperty, 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 atrace()function (usingfor..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 anonEnterFrameorsetInterval()loop to update the value of theMovieClip._xproperty of the knob movie clip. Use anifstatement to set the knob’s_xproperty to the smaller value in a comparison between the mouse’s position and the download progress bar’s_xplus its_width. You could useMath.min()to make that determination.[Note: “How to Build an Interactive Flash Video (FLV) Load Progress Bar” now addresses these questions.]
August 16th, 2007 at 6:39 am
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.
August 16th, 2007 at 7:48 am
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.onEnterFrameorsetInterval()— 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.onPressevent handler to the originaltrackmovie clip. Have its function note theMovieClip._xmouseposition and compare that value against the sameratioexpression used by the knob. In the same way I suggested to Marius, you could use anifstatement to ensure that you onlyseek()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.]
August 21st, 2007 at 4:22 am
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.
August 21st, 2007 at 12:17 pm
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!
August 21st, 2007 at 1:29 pm
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!
August 22nd, 2007 at 4:17 am
OK David,
thanks a lot. In the meantime I’ll keep trying to see how it goes.
Many Thanks!!!
September 7th, 2007 at 4:31 am
Yessss!!!
Finaly a great and simple tutorial for preloading FLV-s!
Thank u man
September 14th, 2007 at 5:39 pm
vilim,
Thanks!
October 24th, 2007 at 2:41 pm
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.
October 27th, 2007 at 9:36 pm
Keith,
In answering someone else’s question on FMS (streaming) video, I learned that truly streamed FLVs dispatch the
NetStream.onMetaDataevent every time theNetStream.seek()method is called. That likely means you’re getting a conflict between two triggerings ofupdateKnob(), because theonReleasehandler and theonMetaDatahandler both start anupdateKnob()loop.You could step around this issue with a quick-and-dirty Boolean flag:
… but then you’d have to set that
firstTimevariable back to true if you load additional FLVs.October 29th, 2007 at 4:09 pm
Excellent! That worked perfect. thank you David!
October 29th, 2007 at 7:52 pm
Keith,
Glad to hear that! Woo-hoo!
January 26th, 2008 at 6:29 pm
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
January 26th, 2008 at 6:29 pm
…sorry it keeps cutting off the code ???
January 26th, 2008 at 6:33 pm
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
January 27th, 2008 at 10:30 pm
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<and>, respectively).As for the
seekBarHandle_mchandle 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 newonPressevent 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.January 29th, 2008 at 10:14 am
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
January 29th, 2008 at 10:32 am
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.
January 30th, 2008 at 5:46 pm
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.
January 30th, 2008 at 9:51 pm
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!
In order for the
playedmovie clip to follow theknobmovie clip, you’ll have to repeatedly updateplayed’s position. The single line of code you showed will set those_xproperties to the same value at that moment. To repeat the process, you’ll have to put that line inside the loop that continuously movesknob(in the blog article that happens inside asetInterval()loop).January 30th, 2008 at 10:22 pm
Thanx!
February 1st, 2008 at 2:11 pm
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.
February 1st, 2008 at 8:17 pm
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).
February 21st, 2008 at 2:43 am
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
April 3rd, 2008 at 10:59 pm
Alex,
The
onMetaDataevent requires Flash Player 7 or higher — could that be it?April 4th, 2008 at 5:17 pm
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!
April 4th, 2008 at 8:03 pm
jdq,
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 aNumberdatatype (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).April 28th, 2010 at 8:49 am
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 !!!