Recipe 16.14.
Determining User Bandwidth
Problem
You want to optimize a user's video playback by
determining her network bandwidth.
Solution
Download an image file and time the download to
calculate the speed of the user's network connection.
Discussion
Unfortunately, the Flash Player doesn't have a
built-in bandwidth detection system. And because actual bandwidth
varies based on many factors (such as network usage, interference
in wireless networks, applications running on the same system
competing for bandwidth, etc.), there is no way to accurately
predict what a user's bandwidth will be for the next 10 minutes,
hour, or any amount of time. However, you can measure a user's
actual bandwidth over a period of time and use that to determine
what her bandwidth could be in the
near future.
To measure a user's bandwidth, you need to
download a (noncompressed) file, such as a JPEG file using Flash
Player. Using ActionScript, you can measure both the total bytes
downloaded and the amount of time it took to download those bytes.
Using those two values, you can calculate an average amount of data
downloaded per unit of time. For the purposes of video, bandwidth
is usually measured as bit rate in units of kilobits per second.
There are 8 bits in a byte and 1,000 bytes per kilobyte. That means
you can use the following to convert from bytes to kilobits:
kilobits = bytes / 1000 * 8;
|
The ratio of bytes/kilobytes and bits/kilobits
is different if you are talking about data communication or disk
storage. For disk storage, the ratio is 1/1024, while for data
communications it is 1/1000.
|
|
The larger the file that the user has to
download, the more accurate the measurement is likely to be. For
example, if the user downloads a 10 kilobyte file, it may be that
the request hits a network lag and the measurement can be
significantly lower than the actual average bandwidth. On the other
hand, you don't want to force the user to download too large a
file, since that would cause the user to have to wait too long
while testing bandwidth.
One option is to use a moderately sized file
(something in the 50100 kilobytes range) and run the test several
times. That way, if the first two tests are within a certain range
of each other you can assume subsequent tests would also be
relatively close, and you don't need to run further tests. If the
first two tests have a wide margin, then you can run additional
tests. If the same file is downloaded several times, you need to
make sure that you use a unique URL each time so the Flash Player
doesn't retrieve the file from a browser's cache. The following
class illustrates how this sort of bandwidth test works:
package com.oreilly.as3cb.util {
import flash.events.EventDispatcher;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.util.getTimer;
public class BandwidthTest extends EventDispatcher {
private var _downloadCount:uint;
private var _bandwidthTests:Array;
private var _detectedBandwidth:Number;
private var _startTime:uint;
public function get detectedBandwidth( ):Number {
return _detectedBandwidth;
}
public function BandwidthTest( ) {
_downloadCount = 0;
_bandwidthTests = new Array( );
}
// Run the bandwidth test.
public function test( ):void {
// Use a URLLoader to load the data.
var loader:URLLoader = new URLLoader( );
// Use a URL with a unique query string to ensure the data is
// loaded from the server and not from browser cache.
var request:URLRequest = new URLRequest("bandwidthtestimage.jpg?unique="
+ (new Date( )).getTime( ));
loader.load(request);
loader.addEventListener(Event.OPEN, onStart);
loader.addEventListener(Event.COMPLETE, onLoad);
}
// When the file starts to download get the current timer value.
private function onStart(event:Event):void {
_startTime = getTimer( );
}
private function onLoad(event:Event):void {
// The download time is the timer value when the file has downloaded
// minus the timer value when the value started downloading. Then
// divide by 1000 to convert from milliseconds to seconds.
var downloadTime:Number = (getTimer( ) - _startTime) / 1000;
_downloadCount++;
// Convert from bytes to kilobits.
var kilobits:Number = event.target.bytesTotal / 1000 * 8;
// Divide the kilobits by the download time.
var kbps:Number = kilobits / downloadTime;
// Add the test value to the array.
_bandwidthTests.push(kbps);
if(_downloadCount == 1) {
// If it's only run one test then run the second.
test( );
}
else if(_downloadCount == 2) {
// If it's run two tests then determine the margin between the
// first two tests.
// If the margin is small (in this example, less than 50 kbps)
// then dispatch a complete event. If not run a test.
if(Math.abs(_bandwidthTests[0] - _bandwidthTests[1]) < 50) {
dispatchCompleteEvent( );
}
else {
test( );
}
}
else {
// Following the third test dispatch a complete event.
dispatchCompleteEvent( );
}
}
private function dispatchCompleteEvent( ):void {
// Determine the avarage bandwidth detection value.
_detectedBandwidth = 0;
var i:uint;
for(i = 0; i < _bandwidthTests.length; i++) {
_detectedBandwidth += _bandwidthTests[i];
}
_detectedBandwidth /= _downloadCount;
// Dispatch a complete event.
dispatchEvent(new Event(Event.COMPLETE));
}
}
}
You can use instances of the preceding class to
run a bandwidth test. To do so, create a new instance, add a
listener, and run the test( ) method.
var bandwidthTester:BandwidthTest = new BandwidthTest( );
bandwidthTester.addEventListener(Event.COMPLETE, onBandwidthTest);
bandwidthTester.test( );
When the complete event occurs, you can retrieve
the detected bandwidth using the detectedBandwidth
property.
private function onBandwidthTest(event:Event):void {
trace(event.target.detectedBandwidth);
}
See Also
Recipes 19.1
(the APIs that load binary
data, such as image files,
can be used) and 19.4
|