Articles Archive
Articles Search
Director Wiki
 

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.

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

  1. Initiate the download
  2. Monitor the download
  3. 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:

  1. initiates the download process
  2. monitors the download
  3. 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.

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

  1. downloading
  2. done
  3. 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

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.

Zac Belado is a programmer, web developer and rehabilitated ex-designer based in Vancouver, British Columbia. He currently works as an Application Developer for a Vancouver software company. His primary focus is web applications built using ColdFusion. He has been involved in multimedia and web-based development, producing work for clients such as Levi Straus, Motorola and Adobe Systems. As well, he has written for the Macromedia Users Journal and been a featured speaker at the Macromedia Users Convention.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.