How much is that download in the window?
August 17, 1999
by Zac Belado
Users demand feedback. This is a simple fact of life. An accompanying rule is that bored or frustrated users, go elsewhere. When it comes to net-enabled applications, like Shockwave games or presentations, a user's attention span is frighteningly short. So it's always in your best interest to give your users feedback when they are downloading a Shockwave app.
What you need to do, especially with larger Shockwave files, is give the user not only an indication of the progress of the download but also something moving on screen to keep their attention. A simple animation, a status bar and some optional data (percent complete, amount downloaded) is often enough to keep users waiting for your masterpiece to download.
Luckily Director has two functions that make creating this sort of user feedback quick and simple.
Fetch!
The first function we need to look at is preloadNetThing (). The format for preloadNetThing () is
preloadNetThing (<url>)
The function will, as the name indicates, begin to preload a file to the local cache. Any subsequent calls for that file (using gotoNetMovie for instance) will use the local (cached) version instead of trying to reload the remote version of the file. The function returns a network ID that you can use to track the status of the download.
Click here to see the code in action. A sample movie is available for download in Mac or PC format.
Note that preloadNetThing () will only download the Director file at that URL. If the movie requires other linked assets (such as casts) then they will need to be downloaded using subsequent preloadNetThing calls. For the duration of the article we will assume that the file we need to download has no linked assets that are required for it to run.
The benefit of using preloadNetThing is that this process is asynchronous, meaning that the movie still runs while the download occurs. You can monitor and update the status of the download while you are playing an animation or even, as some people have done, let the user play a game.
Are we there yet?
The Lingo Dictionary mentions that you can use netDone() to determine if a download is finished. I prefer to use getStreamStatus () instead because it is far more functional and provides more information than netDone(). The format for getStreamStatus () is
getStreamStatus (netID)
The function takes the network ID that was returned from the preloadNetThing() function as a parameter and returns a property list with 5 properties detailing the current state of the download.
- #URL - the URL used to start the process referenced by netID
- #state- A string indicating the status for the process. It can either be "Connecting", "Started", "inProgress", "Complete", "Error", or "NoInformation"
- #bytesSoFar - The number of bytes received
- #bytesTotal - The total number of bytes for the file being downloaded. This may be 0 if the http server does not include the file length in the MIME headers it returns when you request a file.
- #error - Either an EMPTY string if the download is not complete, "OK" if it is completed without an error or an error code if the download attempt caused an error.
It's rather easy to see why this function is much more preferable than a simple netDone () function. Not only does getStreamStatus () supply more information, but it practically does all the calculations we need. In fact, all we do need to compute is a percentage of the download completed. The rest is done for us.
Hook, line and sinker
The whole download process can be handled in three steps
- Initiate the download
- Monitor the download
- Play the movie if completed
As with most of my projects, we will accomplish this task by writing a small, autonomous, object to initiate and monitor the download process. In order to keep the process as simple as possible we will write a frame behavior to store details that we need for the download. This behavior will, as it leaves the frame, initiate the download process by creating an instance of our object and passing the details it stored to the object. Then we simply wait for the download to either finish and then play the movie or generate an error condition to display to the user if the download was not completed successfully.
As the object monitors the download, it will use the getStreamStatus () function to get the details of the download's process and report it back to the user. We will also write an additional behavior to create a small status bar that will expand as the download progresses.
Step 1: The frame behavior
The reasoning behind using a frame behavior to initiate the download process is twofold.
The details of the download are unrelated to the actual process. The code to handle the process doesn't care what it is trying to download but is concerned with checking the state of the download and reporting it. As such, if we make the code as generic as possible (by keeping the details external) then we have written a more functional and reusable piece of code.
Secondly, there are some details, like the options to display the total data downloaded, that are unrelated to the download process but still need to be placed somewhere convenient so it is easy to modify them.
This allows you, as the programmer, to separate the specifics of the process (the code and the objects needed to run it) from the actual mechanism needed to start the process. This also gives you a simple mechanism to allow other people to make small changes (like the URL of the file to download) without having to modify the object that does the actual download.
The behavior is quite simple. It has a getPropertyDescriptionList handler to display and retrieve the details from the user.
property pURL, pDisplayK, pDisplayPercentComplete property pKStartSprite, pKEndSprite, pCompleteSprite, pStatusSprite on getPropertyDescriptionList me set d = [:] set valueRange = ["True", "False"] addProp d, #pURL, [#default: "http://", #format: #string, ¬ #comment: "The URL to the file you want to download"] addProp d, #pStatusSprite, [#default:1, #format:#integer, ¬ #comment:"The status bar's sprite"] addProp d, #pDisplayK, [#default:True, #format:#boolean, ¬ #comment:"Display the K's downloaded?"] addProp d, #pDisplayPercentComplete, [#default:True, #format:¬ #boolean, #comment:"Display the % complete?"] addProp d, #pKStartSprite, [#default:1, #format:#integer,¬ #comment:"The first field sprite for the K's downloaded"] addProp d, #pKEndSprite, [#default:1, #format:#integer,¬ #comment:"The second field sprite for the K's downloaded"] addProp d, #pCompleteSprite, [#default:1, #format:#integer,¬ #comment:"The field displaying the % complete"] return d end
Once the details are stored (and you have done any error checking which the code in this movie does not do) you need to ensure that all the display fields and sprites are visible and then create the download object.
on prepareFrame me --set up the display sprites sprite (pKStartSprite).visible = pDisplayK sprite (pKEndSprite).visible = pDisplayK sprite (pCompleteSprite).visible = pDisplayPercentComplete -- set the default text sprite (pKEndSprite).member.text = "0K" sprite (pCompleteSprite).member.text = "0% Complete" end on exitFrame me -- create the object tempObjRef = new (script "preLoadMovie", pURL, pStatusSprite, ¬ pKEndSprite, pCompleteSprite, pDisplayK, pDisplayPercentComplete) end
if you want you can also include all the data in a simple exitFrame call and avoid using the behaviour entirely.
on exitFrame movieURL = "test/YDKL.dcr" thisTempObj = new (script "preLoadMovie", movieURL,TRUE, 5) end
But, as you can see, this is slightly harder to understand than using the frame behavior. While you might understand what all these values mean now, the same might not be true a month from now. Yet another reason to take a few extra moments and code the explanations for the parameters into a frame behavior.
Step 2: Monitor the download
At this point the control of the process has been handed off to the object we created. This object does three things:
- initiates the download process
- monitors the download
- plays the movie if there were no errors
In order to do its work the object needs to store a few properties. Depending on the settings that the user entered into the frame behavior that initiated the object these properties might not be used.
- pStatusSprite - the sprite that is used as the status bar
- pThingURL - the url of the file we want to download
- pNetID - the network ID of the download process we initiate
- pKSprite - the sprite containing the field that displays the kilobytes downloaded
- pCompleteSprite - the sprite containing the field that displays the percent of the file downloaded
- pDisplayK - a boolean that tells use whether or not to display the K's downloaded
- pDisplayPercent - a boolean to tell use whether or not to display the percent downloaded
When the frame behavior calls the preLoad object's new method the object simply stores the data gathered in the frame behavior and then starts the download of the file.
-- this is a parent script and -- not a behavior. property pStatusSprite, pThingURL, pNetID property pKSprite, pCompleteSprite, pDisplayK, pDisplayPercent on new me, aURL, aSpriteNumber, aFieldSprite, ¬ anotherFieldSprite, displayBoolean, percentBoolean -- store the variables pStatusSprite = aSpriteNumber pThingURL = aURL pDisplayPercent = percentBoolean pDisplayK = displayBoolean pkSprite = aFieldSprite pCompleteSprite = anotherFieldSprite -- start the download pNetID = preloadNetThing (pThingURL) add the actorList, me end
The most important point to notice is that the object needs to store the network ID of the process so that it can check it later, and then it adds itself to the actorList so that it can check the status of the download each frame. By placing itself in the actorList (or to be more precise by placing a reference to itself in the actorList) it will receive a stepFrame event each time the frame head advances. We can then place code in the object's stepFrame method to check, each frame, for the state of the downlaod.
The download is going to be in one of three states
- downloading
- done
- error
To test the state we need to get the details of the current download using the getStreamStatus function
on stepFrame me -- store the properties from the getStreamStatus call thisPropList = getStreamStatus (pNetID) loadDone = thisPropList.error thisError = netError (pNetID)
Once we have that property list we can test the individual elements of it to determine what action we need to take.
-- see if the download is done if loadDone = "OK" or loadDone = 0 then if thisError = 0 OR thisError = "OK" then -- kick ourselves out of the actorList deleteOne the actorList, me -- play the movie gotoNetMovie pThingURL end if else if loadDone = "" then -- update the status bar if we need to updateDisplay me, thisPropList.bytesTotal, thisPropList.bytesSoFar else -- there must have been some sort of error abortDownload me, loadDone end if end
If the download is done then we need to check to see if there was an error. Note that this code not only checks the error code returned from the getStreamStatus () function but also the netError () function as well. This is not absolutely necessary but it has been included to ensure that all possible errors are tracked and checked.
If the download is still progressing then we need to display the current status (amount downloaded, percentage complete) to the user. Again, the getStreamStatus () function provides all the data we need with the bytesTotal and bytesSoFar properties that it returns. We simply need to pass these values to a new function.
updateDisplay me, thisPropList.bytesTotal, thisPropList.bytesSoFar
The updateDisplay method starts out by making sure that neither of the byte values passed to it are zero (to avoid math errors). It then generates a percentage value by dividing the bytes downloaded so far by the total size of the project.
on updateDisplay me, totalBytes, soFar if soFar = 0 OR totalBytes = 0 then percentage = 0 else percentage = float (soFar) / totalBytes end if
Notice that the soFar value is turned into a float (it is an integer) before the percentage is calculated. This is done to avoid the integer division rounding that occurs in Director.
put 2047/22074 -- 0 put 2047.00/22074 -- 0.0927
If the numerator (or denominator) is not a float value then Director will return 0 for any numerator that is less than the denominator. Since this will be the case for every value we receive during the download process (until the downlaod is completed) then we need to do this in order to get values we can use.
If the user has specified that they want to see the percentage value displayed as text then we need to take the numerical value of the percentage and convert it into a string. As well, in order to make it easier to read, we'll just take the first two characters of the percentage (.char[1..2]).
if pDisplayPercent then -- wrap it to 2 characters thisPercentage = (string(percentage * 100)).char[1..2] thisPercentage = value(thisPercentage).integer sprite (pCompleteSprite).member.text = thisPercentage && "% Complete" end if
The observant reader will notice that in the case of single digit percentages, the first two characters will also include an unwanted decimal point (for example "3."). The quickest way to remove this is to convert the value back into an integer.
thisPercentage = value(thisPercentage).integer
The final steps of the display process are to display the kilobytes downloaded so far (if required) and then send the percentage downloaded to our status bar sprite for it to use it its display
if pDisplayK then sprite (pKSprite).member.text = string(soFar / 1024) && "K" end if -- send the percentage data to the status bar sprite sendSprite (pStatusSprite, #updateStatus, percentage) end
Step 2a: The status bar
A slightly unrelated element in this whole process is the status bar. Since it is an external element you can use any code you'd like (you would need to rewrite the sendSprite call in the download script though) but for the sake of completeness I'll illustrate the code I've used.
The status bar behavior has three properties
- pMySprite- the sprite that contains the status bar bitmap
- pMySpriteWidth- the original width of the bitmap
- pActive - a boolean value to indicate whether the status bar is active
When the sprite begins it stores the values for pMySprite and pMySpriteWidth, turns itself on (by setting pActive to true) and then sets the width if the member on screen to 0
property pMySprite, pMySpriteWidth, pActive on beginSprite me pMySprite = sprite(me.spriteNum) pMySpriteWidth = pMySprite.width pActive = TRUE pMySprite.width=0 end
The download object communicates with the status bar by sending values to the status bar's updateStatus method
sendSprite (pStatusSprite, #updateStatus, percentage)
This method simply takes that percentage value and then, if the status bar is active, calculates a new width for the member on the stage. It also ensures that the width of the member doesn't exceed its original dimensions.
on updateStatus me, percentage -- check to see if the bar is active first if pActive then -- make sure we were sent a percentage value if NOT voidP(percentage) then -- assign a new width based on the percentage newWidth = pMySpriteWidth * percentage -- don't make the bar wider than it actually is --ie don't make it 103% if newWidth <= pMySpriteWidth then pMySprite.width = newWidth end if end if end if end
For this demonstration movie I have made the status bar move from left to right. In order to do so I had to make sure that the reg point of the bitmap was on the left side of the bitmap. Director will scale a bitmap in relation to the reg point. So if the reg point was in the middle of the bitmap (as is the default) it would scale it from the middle as well. This would make your status bar grow to the left and right as the downlaod progressed...which is actually a very neat effect.
Finally the status bar has a method to toggle its active state. If the status bar is made inactive then the width is set back to 0.
on toggleState me pActive = NOT pActive if NOT pActive then pMySprite.width = 0 end if end
Step 3: Play the movie if completed
Once the download is done then we can safely play the new movie. even though this script immediately plays the movie, there is no reason why you can't jump to a new screen and let the user click a button when they are ready to proceed (very useful for games).
This article demonstrates a very simple case. One in which there is only a single file to download and the file plays as soon as it had been loaded to the local cache. The code can easily be extended to cover many situations but if your application demands that you download numerous files then it might be advisable to wrap the calls to the download object in another object that tracks and controls the downloads.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.