How to Fast Forward and Rewind Video (FLV) Content

ActionScript 2.0

Traditional VCR controls usually incorporate fast forward and rewind buttons, which either increase the playback speed of the video or quickly play it in reverse, respectively.  The result is that you can skip around in a video but still maintain some sense of where you are.  Useful as these controls might be, you won’t find them in any of the FLVPlayback Component skins.  (What appear, at first glance, to be FF/RW buttons, are actually a means to jump to optional navigation cue points in the video, much like jumping to chapters on a DVD.)  Is it possible, then, to fast forward and rewind FLV files?  Sure thing.  Let’s take a look. 

An answer, short and sweet

As explained in “How to Load External Video (FLV)” and “How to Control Video (FLV) without a Component,” it’s entirely possible to load and manage FLV files without the use of the FLVPlayback Component.  Is there anything wrong with FLVPlayback?  Not a bit.  I use it on occasion, just like I occasionally go out to eat at a restaurant, where the food is all prepared for me.  But as it happens, I enjoy cooking.  :)   I like experimenting with things on my own, and I also appreciate the file size savings I get from using ActionScript to control a Video object.  So we’ll start from a Component-less point of view, because the concept here is the important part and can be implemented more or less the same way with FLVPlayback.

Assuming a Video object already on the Stage with the instance name videoPlayer — see the earlier articles, if necessary, to get up to speed — here’s your ActionScript 2.0:

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

var id:Number;

ff.onPress = function():Void {
  id = setInterval(function():Void {
    ns.seek(ns.time + 0.5);
  }, 100);
}
ff.onRelease = function():Void {
  clearInterval(id);
}

rw.onPress = function():Void {
  var dest:Number = ns.time;
  id = setInterval(function():Void {
    ns.seek(dest -= 2);
  }, 100);
}
rw.onRelease = function():Void {
  clearInterval(id);
}

How it works

The first block (var nc, etc., through ns.play()) establishes an HTTP connection to make an FLV file request.  At this point, a file named externalVideo.fla has begun its loading process and will play as soon as enough data have been buffered (usually a few seconds, and it starts to play, even while continuing to download).

Next, a Number variable, id, is declared, but not yet set to anything.  The reason it’s declared here, rather than inside the event handler functions immediately following, is that by putting it here, we’re scoping it to the main timeline.  When clearInterval() looks for the id variable later, it will look first in the scope of its own function, won’t find it, and will then look “up the chain” until it sees id, which is the same variable used by a number of functions here — they all share this variable.

Now, have have this:

ff.onPress = function():Void {
  id = setInterval(function():Void {
    ns.seek(ns.time + 0.5);
  }, 100);
}

… which expects a button symbol with the instance name ff (for fast forward).  A function is assigned to the Button.onPress event of this Button instance.  When the user presses down on the fast forward button, the following happens:  a setInterval() loop causes a function of its own to be triggered every 100 milliseconds (every tenth of a second).  This function references the ns (NetStream) instance declared earlier and invokes on it the NetStream.seek() method, which sends the video to a given point in its video timeline. What point is that?  Well, the value must be in seconds, but decimal places are okay.  For this particular example, we’re sending the video to an expression that reads ns.time + 0.5, which means, “Where you are right now (ns.time) plus half a second”; in other words, half a second from the current position.  Experiment with this value as you please.  Add 0.25 if you like.  For super fast forward, add 5.  Up to you.

In ActionScript 2.0, setInterval() is simply a function that repeats other functions at the interval specified.  setInterval() happens to return a value (simply a number) that we’re collecting in that id variable.  Why?  So that we can turn this particular function-triggering loop off again.  This will happen when the user lifts up from the FF button:

ff.onRelease = function():Void {
  clearInterval(id);
}

Pretty straight forward.  Now for reverse.

Reverse is a little more tricky, because the natural tendency of the FLV file is to play forward.  The way NetStream seeks — for this sort of setup (progressive download) — is that it looks for video keyframes — special frames in the video file itself — and navigates to those.  The frequency of video keyframes is determined when the video is encoded.  You might have keyframes every 300 frames.  For a video encoded at 25 frames per second (fps), that’s one keyframe every 12 seconds.  Sending the video ahead is one thing … it’s already traveling in that direction.  Sending the video back is somewhat akin to swimming upstream.  The actual numbers you find useful may vary, depending on the framerate, keyframe distribution, and length of the FLV in question, so the following values are just numbers that happened to work for me:

rw.onPress = function():Void {
  var dest:Number = ns.time;
  id = setInterval(function():Void {
    ns.seek(dest -= 2);
  }, 100);
}

Same sort of concept as before, but this time we declare a new variable, dest (for destination, but the name is arbitrary) and set it to the video’s current position — again, taken from the NetStream.time property.  Why take a “snapshot” of the position and use that, rather than a direct call to ns.time?  Well, unless we go to the trouble of pausing the video first, which means we’d have to unpause it later, the video wants to move forward.  Rather than deal with a constantly “struggling” value — time would exasperatedly be asking, “Do you want me to go forward or not!?” — we’ll just deal with a static number.  Here, again, a setInterval() loop decrements the seek value repeatedly.  This example decrements by 2 and does so every 100 milliseconds.  Start with those values, but experiment and find your own sweet spot.

An onRelease event of the rw button stops the loop, as before.

Things to keep in mind

Nothing stops the rewind routine from heading into negative numbers.  If the user holds down the RW button long enough, ns will be asked to seek to -100, -220, and so on.  Fortunately, seek() doesn’t care.  Anything lower than 0 gets sent to 0.  Same goes for the upper end:  anything higher than the duration of the video obviously can’t be sent higher than the number of seconds the video contains (and it won’t).

Perhaps most importantly, seek() cannot send progressively downloaded videos to keyframes that haven’t yet loaded.  If the FLV file is still downloading and your user mashes the fast forward button, nothing may happen for a while.  Them’s the brakes.

Can it be done with the FLVPlayback Component?

You bet.  Remember, the principle is that same, but the mechanics of it change a bit.  The FLVPlayback class in the Help docs serves up what you need. (this is AS2, remember, so if you’re in an AS3 environment you’ll have to do some digging anyway).

var id:Number;
ff.onPress = function():Void {
  videoPlayer.pause();
  var dest:Number = videoPlayer.playheadTime;
  id = setInterval(function():Void {
    videoPlayer.seek(dest += 2);
  }, 100);
}
ff.onRelease = function():Void {
  videoPlayer.play();
  clearInterval(id);
}
rw.onPress = function():Void {
  videoPlayer.pause();
  var dest:Number = videoPlayer.playheadTime;
  id = setInterval(function():Void {
    videoPlayer.seek(dest -= 2);
  }, 100);
}
rw.onRelease = function():Void {
  videoPlayer.play();
  clearInterval(id);
}

In this case, I found it helped after all to pause the video while seeking, which is why the onRelease handlers invoke FLVPlayback.play() again.  I also found that even fast forwarding benefits from the temporary dest variable; otherwise things got ugly pretty quickly.  Note, too, that I adjusted the increment value for fast forwarding.

So … dash o’ this, splash o’ that.  This particular article is more of a loose recipe than a bulleted list of steps to follow, but I hope it gives you a bit to play with.  :)   Thanks to Dean Nixon for suggesting this topic.

Leave a Reply