package com.exanimo.net { import ArgumentError; import flash.display.Loader; import flash.events.Event; import flash.events.ProgressEvent; import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLRequestHeader; import flash.utils.getTimer; /** * * Estimates the user's bandwidth. * * @author matthew at exanimo dot com * @author Ryan Sprake * @version 2007.06.05 * */ public class BandwidthChecker extends URLLoader { public static const BYTES_PER_SECOND:String = 'bytesPerSecond'; public static const BITS_PER_SECOND:String = 'bitsPerSecond'; public static const KILOBYTES_PER_SECOND:String = 'kilobytesPerSecond'; public static const KILOBITS_PER_SECOND:String = 'kilobitsPerSecond'; private var _bytes:Number; private var _bandwidth:Number; private var _downloadTime:Number; private var _maximumBytes:uint; private var _startTime:Number; private var _units:String; /** * * Creates a bandwidth-checking URLLoader. * */ public function BandwidthChecker(requestOrURL:* = null) { this.units = KILOBYTES_PER_SECOND; this.addEventListener(ProgressEvent.PROGRESS, this._calulateBandwidth); this.addEventListener(Event.COMPLETE, this._calulateBandwidth); if (requestOrURL) { this.check(requestOrURL); } } // // accessors // /** * * The user's estimated bandwidth, in the units specified by the * BandwidthChecker's units property. This information * becomes available after the check has completed. * */ public function get bandwidth():Number { switch(this.units) { case BYTES_PER_SECOND: return this._bytes / this._downloadTime * 1000; case BITS_PER_SECOND: return this._bytes * 8 / this._downloadTime * 1000; case KILOBITS_PER_SECOND: return this._bytes * 8 / this._downloadTime case KILOBYTES_PER_SECOND: default: return this._bytes / this._downloadTime; } } /** * * The maximum number of bytes that should be downloaded before * canceling the load process. * */ public function get maximumBytes():uint { return this._maximumBytes; } /** * @private */ public function set maximumBytes(maximumBytes:uint):void { this._maximumBytes = maximumBytes; } /** * * The units in which the bandwidth measurement should be returned. * Possible values are BandwidthChecker.BYTES_PER_SECOND, * BandwidthChecker.BITS_PER_SECOND, * BandwidthChecker.KILOBYTES_PER_SECOND, * BandwidthChecker.KILOBITS_PER_SECOND. * * @default BandwidthChecker.KILOBYTES_PER_SECOND * */ public function get units():String { return this._units; } /** * @private */ public function set units(units:String):void { switch(units) { case BYTES_PER_SECOND: case BITS_PER_SECOND: case KILOBYTES_PER_SECOND: case KILOBITS_PER_SECOND: this._units = units; break; default: throw new Error('Invalid value. Valid values for this property are BandwidthChecker.BYTES_PER_SECOND, BandwidthChecker.BITS_PER_SECOND, BandwidthChecker.KILOBYTES_PER_SECOND, and BandwidthChecker.KILOBITS_PER_SECOND.'); } } // // public methods // /** * * Estimate the user's bandwidth by downloading a file (or part of a * file). * * @param requestOrURL * the URLRequest or url (String) identifying the file to use * */ public function check(requestOrURL:Object):void { if (requestOrURL is String) { var request:URLRequest = new URLRequest(requestOrURL as String); this.load(request); } else if (requestOrURL is URLRequest) { this.load(requestOrURL as URLRequest); } else { throw new ArgumentError('Argument must be either a url or URLRequest.'); } } /** * * Perform the check. Overridden so that request can be manipulated to * prevent caching and to start timer. * * @param request * The URLRequest to use to check the bandwidth. The file at this * URL will be partially downloaded. * */ override public function load(request:URLRequest):void { // // Mess with the URLRequest to make sure we don't load a cached version. // var oldURL:String = request.url; if (/^(http:|https:)/i.test(request.url) || ((request.url.indexOf('://') == -1) && /^(http:|https:)/i.test(new Loader().contentLoaderInfo.loaderURL))) { var sep:String = request.url.indexOf('?') == -1 ? '?' : '&'; request.url = request.url + sep + 'cacheBuster=' + new Date().getTime().toString(); } var noCacheHeader:URLRequestHeader = new URLRequestHeader('pragma', 'no-cache'); request.requestHeaders.push(noCacheHeader); // Load it. this._startTime = getTimer(); super.load(request); // Put the request back to its original state. request.url = oldURL; for (var i:Number = 0; i < request.requestHeaders.length; i++) { if (request.requestHeaders[i] == noCacheHeader) { request.requestHeaders.splice(i, 1); break; } } } // // private methods // /** * * A helper function that updates the bandwidth reading whenever a chunk of * the file is downloaded. If the file has been downloaded (or maximumBytes * has been reached), a COMPLETE event will be dispatched. At this point, * you can use the bandwidth property to get the user's estimated bandwidth. * */ private function _calulateBandwidth(e:Event):void { this._downloadTime = getTimer() - this._startTime; this._bytes = e.currentTarget.bytesLoaded; switch (e.type) { case Event.COMPLETE: this.removeEventListener(ProgressEvent.PROGRESS, this._calulateBandwidth); this.removeEventListener(Event.COMPLETE, this._calulateBandwidth); break; case ProgressEvent.PROGRESS: if (this.maximumBytes && (this._bytes >= this.maximumBytes)) { this.removeEventListener(ProgressEvent.PROGRESS, this._calulateBandwidth); this.removeEventListener(Event.COMPLETE, this._calulateBandwidth); this.close(); var e:Event = new Event(Event.COMPLETE); this.dispatchEvent(e); } break; } } } }