Archive for June, 2007

Automatic Bandwidth Detection for SMIL + FLVPlayback + Progressive Downloads

Tuesday, June 5th, 2007

This technique makes use of the URLLoader's close method. Therefore, the code is only available for AS3.

You know what would be nice? If we could serve every user the same 1024×768, 512 kilobits per second video. Unfortunately, some selfish people still refuse to pay us developers the simple courtesy of buying that T1 line (I'm looking at you, Mom). To make matters even worse, our bosses want these selfish losers to be able to watch our videos. Luckily, Flash's FLVPlayback component supports a subset of SMIL that allows us to serve different videos to users with different bandwidths.* For those who don't know, a SMIL file looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE smil PUBLIC "-//W3C//DTD SMIL 2.0//EN"
    "http://www.w3.org/2001/SMIL20/SMIL20.dtd">
<smil xmlns="http://www.w3.org/2001/SMIL20/Language">
    <body>
        <switch>
            <video src="video2.flv" system-bitrate="512000" />
            <video src="video1.flv" system-bitrate="256000" />
            <video src="video0.flv" />
        </switch>
    </body>
</smil>

Given the above SMIL file, users with connections transferring at a rate of 512kbps or higher should see "video2.flv," users with transfer speeds between 256kbps and 512kbps should see "video1.flv," and everybody else should get "video0.flv." But they won't — unless you let the FLVPlayback component know what their transfer rate actually is. Generally, this means having to download a file of known size and estimate the user's bandwidth based on how long the download takes to complete. However, with the advent of AS3 (and a little clever actionscripting), we can automate this process entirely.

See the NCManagerAuto Example and then download the NCManagerAuto Source and Example Files to try it out for yourself. (Or grab it from my as3 subversion repository.)

Most of the work of the FLVPlayback component is actually done by the VideoPlayer object that it wraps. The VideoPlayer class, in turn, uses a helper class called NCManager to (of all things) manage NetConnections. Therefore, we could make some pretty significant changes to how our FLVPlayback components operate if we could only substitute our own versions of NCManager for the default. If you haven't already guessed, Adobe provides us with a simple method to do exactly that. By setting the static iNCManagerClass property of the VideoPlayer class, we can change how VideoPlayers (and by extension FLVPlayback components) handle NetConnections. By setting the property to the xa NCManagerAuto class, we can enable automatic (and completely transparent) bandwidth detection for our FLVPlayback components. But enough babbling, here's how you do it:

import fl.video.FLVPlayback;
import fl.video.VideoPlayer
import com.exanimo.video.NCManagerAuto;

VideoPlayer.iNCManagerClass = NCManagerAuto;
var myFLVPlayback:FLVPlayback = new FLVPlayback();
myFLVPlayback.source = 'example.smil';
this.addChild(myFLVPlayback);

Except for the addition of one line (okay, one line and two import statements), everything here is business as usual. You can create your FLVPlayback component with ActionScript on a frame, in a document class, or even drag it onto the stage. Just add VideoPlayer.iNCManagerClass = NCManagerAuto; and the FLVPlayback component will be able to parse your SMIL file and play the video appropriate for your user's internet connection.

So How Does it Work?

The NCManagerAuto class uses a variation of the same trick people have always used to estimate a user's bandwidth. Historically, this required a small file of known size on your server. This file would then be downloaded and, based on the time the download took, the bandwidth could then be estimated. However, NCManagerAuto doesn't require any extra files on your server. Instead, it creates an instance of the xa BandwidthChecker, which it uses to download the first 35K of one of the FLVs in your SMIL file — an uncached version of course. It then sets the bitrate of your component using the data returned by the BandwidthChecker.

Of course, this method of determining bandwidth isn't entirely foolproof. Its drawbacks have been documented by almost everybody who's written on the subject so I won't go into it. However, since the actual detection is encapsulated in the BandwidthChecker, it'll be a simple switch if a more accurate method arises. Both the NCManagerAuto and the BandwidthChecker are available under the MIT License. Enjoy the free code.

*There appears to be a bug in how Adobe's FLVPlayback component parses SMIL files: the SMILManager 's parseVideo method throws away the system-bitrate attributes of your video nodes. Therefore, the component will think that the first node is your default video, and use it every time. The source code that Adobe includes with Flash 9 (located in the "Component Source" directory) does not exhibit this problem, so I can only guess that the FLVPlayback component was compiled with an earlier version of the SMILManager class.

This technique addresses the problem transparently by using a different SMILManager class. Even if you do not want to use automatic bandwidth checking (and instead want to detect bandwidth using your own method), you can still use the NCManagerAuto class to work around this bug. Simply set the bitrate property of your FLVPlayback instance and the component will use that value instead of trying to determine the user's bandwidth itself.