Flash MX 2004 MP3 Player Pg.13
source: http://www.thegoldenmean.com/
13 — Dynamic Bandwidth Detection
Detecting Your User’s Download Speed
This paragraph should be replaced by a Flash movie if you have at least version 7 of the Flash Player and your browser has Javascript enabled.
What?
You can streamline and at the same time enhance your visitor’s experience by evaluating their connection speed through code. The length of time your Player pauses to buffer data can be tailored to their connection speed without requiring their input.
Project files for this modified version of the Player may be downloaded here.
Gratitude
This tutorial’s concluding refinement exists entirely thanks to Doug Gray ( www.dougstudio.co.uk). Doug read the previous tutorials in this series, wrote to ask if it was possible to dynamically determine the user’s connection speed, proceeded to answer his own question by doing it and then was generous enough to share his solution. I have modified his MX-based approach to take advantage of some MX 2004 features but the underlying idea is thanks to Doug. I wasn’t entirely convinced this feature was that big a deal until I started to work on it. Now I can’t imagine any justification not to do it considering how painless it is to implement.
This page present three different approaches to the solution. The first is the least accurate but requires the least tinkering with your Player’s code. The second is the most complicated and the final one is I think a reasonable compromise. Consider all three before deciding.
Solution Number One
The strategy is this: before loading the Player module itself, a Flash movie loads a reasonably large graphic file. Code in the movie times how long the download takes. Once the graphic has loaded ActionScript can determine its file size. Divide Kilobytes by seconds and you have KBps - Kilobytes per second. Do this again and average the two results for greater accuracy. Now set the global _soundbuftime variable according to the calculated connection speed. Macromedia sets the default buffer time to 5 seconds. You might increase that to 10 seconds for connections slower than 20 KBps, and you might reduce it to 3 seconds of buffer time for connections that are downloading data faster than 100KBps. Please understand that these cut off points are arbitrary and entirely up to you.
In order to avoid influencing the test by the Player movie loading I decided to make a “container” movie which would first perform the connect speed test and then import the Player movie. This is good news for you - Solution One does not require that you modify the existing Player you may have already made (except to remove the setup panel’s connection speed input radio buttons). The down side to Solution One is that it is the least accurate of the three. Let’s explore it anyway - we are bound to learn something useful!
We will need two new assets in our project folder: a graphic file to load and a new Flash movie which I named “playerContainer.fla”.
The Graphic File
We want an image that is big enough to provide reasonably accurate results but not so big that users on slower connections get annoyed. I chose 50 KB as a reasonable compromise. How does one obtain a file that is 50 KB? I used the “Save For Web” optimization panel, available in Photoshop version 7 and CS. “Optimize to File Size” is an extremely nifty feature hidden in this panel which allows one to specify a target file size. (Find the little triangle near the top right of the Save for Web dialog and click it to obtain a fly-out submenu.) The software then does what it needs to to generate a file of the specified size.
So - make a graphic in Photoshop, select File > Save For Web, choose jpeg as the formats and specify 50 KB in the popout target file size menu. After generating the file I saved mine in the “tracks” folder to keep things organized.
The Container Movie
Create a new Flash movie to the same pixel dimensions as the Player. The container movie needs to run two tests and then load the Player .swf. It only needs to be three frames long and two layers high. The top layer (“logic” in my demo) spans all three frames. Variables and Objects we wish to remain persistent are established here. The bottom layer has keyframes in each of its three frames. It is responsible for the individual tests.
Logic Layer code:
Begin by creating an instance of the LovieClipLoader Object. New to Flash MX 2004, this is a very useful Class for monitoring the load status of an image. We aren’t really using it for that, but we are going to take advantage of one method of the Class: onLoadComplete. (A similar method, onLoadInit(), is more desirable if you want to actually do something with the loaded image, but in this case the load is merely utilitarian in nature so the more limited onLoadComplete() method is not a limitation for us.) This event fires when the image has loaded completely which will be the trigger to stop timing the download. The movie’s main time line is registered as the listener for the MovieClipLoader.
var imageLoader:MovieClipLoader = new MovieClipLoader(); imageLoader.addListener(this);
Now declare the variables that will hold the results of our tests:
var startTime1:Number; var endTime1:Number; var elapsedTime1:Number; var KBps1:Number; var startTime2:Number; var endTime2:Number; var elapsedTime2:Number; var KBps2:Number; var avgKBps:Number;
Finally, create the movieclip that the test graphic and the Player .swf will be loaded into, and a small text field to give the visitor a status report:
this.createEmptyMovieClip("image_mc", 5); this.createTextField("status_txt", 10, 30, 30, 0, 0); status_txt.fontColor=0x000000; status_txt.autoSize="left";
Connect Speed Layer, Frame One:
Begin by giving the visitor a little information:
status_txt.text="determining connection speed...";
Next, define the callback function which is triggered by the onLoadComplete event. This function divides bytes of the loaded asset by 1024 to get Kilobytes, divides that by elapsed time to calculate KB per second and stores the result in the KBps1 variable.
onLoadComplete = function(target:MovieClip) { var fileSize:Number = image_mc.getBytesTotal()/1024; endTime1 = getTimer()/1000; elapsedTime1 = endTime1 - startTime1; KBps1 = fileSize/elapsedTime1; this.play(); }
The real action of this frame involves capturing the current time (actually the milliseconds since the movie started) and loading the graphic. Since we are going to use the same graphic file for both this test and the next one we tack on a unique number the graphic’s URL ensuring that an un-cached version gets loaded each time. This frame captures a time value, loads the image and stops the movie. The function is invoked when the graphic loads and the movie is advanced by a frame. Just to clarify one thing: the MovieClip method getTimer() returns a number that represents the total number of milliseconds your movie has been running. You can get that number when something begins and again when something ends. Subtract one from the other and what remains is essentially elapsed time.
startTime1 = getTimer()/1000; imageLoader.loadClip("tracks/50k.jpg?noCache= "+new Date().getTime(), image_mc); stop();
Connect Speed Layer, Frame Two: The second test
This is so similar to the previous frame that not too many additional comments are needed.
status_txt.text="verifying connection speed..."; onLoadComplete = function(target:MovieClip) { var fileSize:Number = image_mc.getBytesTotal()/1024; endTime2 = getTimer()/1000; elapsedTime2 = endTime2 - startTime2; KBps2 = fileSize/elapsedTime2; avgKBps = Math.round((KBps1 + KBps2)/2); if(avgKBps >= 0 && avgKBps <= 20) { _soundbuftime = 10; } else if (avgKBps > 20 && avgKBps <= 100) { _soundbuftime = 5; } else if (avgKBps > 100) { _soundbuftime = 3; } this.play(); } startTime2 = getTimer()/1000; imageLoader.loadClip("tracks/50k.jpg?noCache="+ new Date().getTime(), image_mc); stop();
This script performs the same test as was previously done. Now that there are two KBps values the second function averages them and sets _soundbuftime to whatever arbitrary standards we decided on. Again, the movie is advanced to its final frame by the function.
Connect Speed Layer, Frame Three: Load the Player
This tiny movie has fulfilled its purpose. It has set a value for the buffer time. Now delete the text field and load the Player:
status_txt.removeTextField(); image_mc.loadMovie("mp3player5e.swf"); stop();
Pretty darn slick wouldn’t you say? Well yes, almost. The problem is that 50 KB is not enough data from which to derive solid conclusions about the connection speed. In testing this code I got extremely different results if I did it over and over again. But we don’t want to force the user (especially the user on a slow connection) to endure a longer wait do we? It turns out the user probably is prepared to endure a much longer file download: the audio track itself! Let’s use each track to refine the buffer time while your visitor enjoys your music!
Solution Number Two
Solution Two will use the exact same playerContainer movie we just made, to establish at least a preliminary baseline value for buffer time. We don’t need to modify that movie in any way. We will just make some very minor additions to the Player ActionScript which will permit us to perform the same test as above but on a significantly larger file.
You should be pretty familiar with you Player .fla by now so I will use a kind of shorthand. Starting from the top of the code in 14th frame of the “script” layer, section B (declaring variables) add the following:
//values used to establish connection speed: var startTime:Number; var endTime:Number;
Next, locate the doPlay() function. Modify the code following the initialization portion as shown (changes in bold). We first supply a new value for the startTime variable. Then the unique number suffix is added to the file name to load. Recall that this is to insure that a non-cached version of the file is loaded. Loading a cached version of the track will completely skew the _soundbuftime value.
startTime = getTimer(); myTunes = new Sound (this); myTunes.onLoad = Delegate.create(this, doneStreaming); myTunes.loadSound (url+"?noCache="+new Date().getTime(), true); myTunes.setVolume (volume);
The doneStreaming() function gets the biggest re-work. As before, new code is bold. It involves essentially the same calculations we wrote for the playerContainer movie so I won’t go into much detail - we have covered this already. Note that writing text to the output_txt text field is purely optional. I used that field initially as a debugging diagnostic tool and wound up liking it enough to keep. Your call.
function doneStreaming():Void { trackLoaded = true; endTime = getTimer(); //how much time (in seconds) did the download take? var elapsedTime:Number = (endTime-startTime)/1000; var fileKB:Number = Math.round(myTunes.getBytesTotal()/1024); var KBps:Number = Math.round(fileKB/elapsedTime); output_txt.text = KBps+" KBps"; if(KBps >= 0 && KBps <= 20) { _soundbuftime = 10; } else if (KBps > 20 && KBps <= 100) { _soundbuftime = 5; } else if (KBps > 100) { _soundbuftime = 3; } }
Solution Number Three
Simple - just use the modified Player movie without the playerContainer movie! Why not just accept Macromedia’s 5 second default buffer time for the first track? From then on the buffer time will be modified to reflect real life conditions for your user. That requires one less .swf and .jpg file to worry about. It seems to me this is the best alternative but now you have three options to decide for yourself.
Conclusion
When I look back at the relatively primative first tutorial I can’t help but marvel at the transformation this little MP3 Player has undergone. If I had glimpsed where that first humble Player would have taken me I might not have had the strength to even start it! Ignorance or naivete is indeed sometimes bliss.
This third installment has introduced some solid Flash MX coding practices as well as some very exciting “power tools” which I suspect will be of benefit no matter what ActionScript 2.0 project you undertake next.
The concluding page of this tutorial is entitled “Inspiration”, and contains links to several noteworthy Players by other coders. Perhaps something in this short collection will intrigue or inspire you.
I do believe that this brings me to the end of what I have to contribute to the topic of MP3 Players. Presumably if you have gotten to this sentence you have the skills and background to implement whatever specific modifications you want for your own needs. I am going to wish you the best of luck. I always love it when readers send me links to examples of how they have applied the Player to their own sites. Allow me to express my gratitude to the many readers of previous tutorials whose questions have pushed me to develop the Player to this point. With all respect to you all - I have devoted enough of my time to MP3 Players! Please direct any questions you might have regarding this tutorial to the Discussion Forum at the GurusNetwork. And Good Luck!!
--top--