UnityWebRequest.downloadProgress skips end

Hi All,

I’m trying to make a fancy loading wheel for my application and it works but it skips the end because downloadProgress skips its end

The end of download progress goes something like this:
…85%86%87%89% Done!
or this…
0% 21% 43% 50% Done!

I figure it might have something to do with the way I serve up files so here is the PHP code that is serving up these files:

header('Content-Description: Asset Bundle Transfer');
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename*=UTF-8''".rawurlencode($name));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filePath));
   
// echo file_get_contents($filePath);        // tried with this, same issue
               
// read in chunks so we don't get a memory error           
$chunkSize = 1024 * 1024;
$handle = fopen($filePath, 'rb');
while (!feof($handle))
{
      $buffer = fread($handle, $chunkSize);
       echo $buffer;
       ob_flush();
       flush();
}
fclose($handle);

One thing that is suspicious is that if I use my browser to make this request, the Content-Length header is suspiciously missing, with both the chunked, and the naive approach.

For completeness sake, here is my unity code:

uwr.SendRequest();
while(!uwr.isDone) {
Debug.Log(uwr.downloadProgress);
yield return waitFrame;
}

Has anyone else encountered this problem?

Try outputing uwr.bytesDownloaded besides downloadProgress.
Also, what DownloadHandler are you using?

The code is in a using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(url)){
block and I don’t specify a downloadhandler.

I checked downloaded bytes and it reports exactly the same number of bytes as the file, however, when it reaches this number, downloadprogress is at 0.88

The helper utility you call attaches DownloadHandlerAssetBundle.
As for progress, this is specific to AssetBundles, the progress attempts to indicate the progress of the whole operation, which is download + AssetBundle load.

so why does it skip the end then? Seems like a bug to me. It would make more sense for the downloadprogress to be 1 when the loop ends.

That end indicates how much of work is left to load AssetBundle. After you take AssetBundle out from DownloadHandler, progress will be 1. I think this is a bit of legacy from old WWW class.

1 Like

Well, do you know how I could make a loading bar then that smoothly goes all the way to the end, because I can’t use downloadprogress because that smoothly goes to 90%, or 70%, or 50% and then skips to the end.

So, just to make sure, is the current behavior correct? Or should I file a bug report?

It is considered correct.

Ok, so you can confirm then that there is no way to make a progress bar that smoothly goes to the end? It will always smoothly go to 70 or 80% and then skip? I’m not trying to be an ass, just wondering what I’m doing wrong.

I don’t know whether the actual figure are correct, but UnityWebRequest by design reports progress from AssetBundle system, so it’s progress is equal to the progress of underlying AssetBundle load operation.

Hey @Aeonvirtual , can I ask you how/where you host your files?

I’m trying to use AppEngine and PHP but I can not figure out how to get download progress. I just don’t get any, like if Content-size was missing from the response.

using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle("https://myurl/mybundle"))
        {
            yield return uwr.SendWebRequest();

            if (uwr.isNetworkError || uwr.isHttpError)
            {
                Debug.Log(uwr.error);
            }
            else
            {
                while(!uwr.isDone)
                    Debug.Log(uwr.downloadProgress);
            }
}

My code yield on return uwr.SendWebRequest() until the file is loaded and uwr.isDone returns true.

Any help would be much appreciated!

After couple of days searching and wasting time implementing my own download handler, I found solution:

    using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(uri, hash)) {
      uwr.SendWebRequest();

      int bundleSize = 0;
      if (!Caching.IsVersionCached(uri, hash)) {
        using (UnityWebRequest request = UnityWebRequest.Head(uri)) {
          yield return request.SendWebRequest();
          bundleSize = Int32.Parse(request.GetResponseHeader("Content-Length"));
        }
      }

      while (!uwr.isDone) {
        bundleData.progress = (bundleSize != 0) ? (uwr.downloadedBytes / (float)bundleSize) : uwr.downloadProgress;
        yield return new WaitForEndOfFrame();
      }
    }

Since UnityWebRequestAssetBundle is sealed (for no reason) we cannot override CompleteContent method to get bundle size. So I decided to send head request and get bundle size. The size will help us calculate actual download progress (because downloaded byes property hold true information).

IMPORTANT make sure that you do not do yield return uwr.SendWebRequest() as official documentation demonstrates in samples (the issue which @FlorianBernard has) because that will paused coroutine until download is fully complete.

Also we request bundle size only after download process was started. That way we do not waste time… the download is going by its own while we are getting bundle size from server. Lastly, we do head request, ONLY if current bundle does not exist in cache.

I hope this will save your time.

2 Likes

Looks like a great workaround Andrew. Thank you so much for sharing!

What is bundleData.progress for??

The bundleData is the object which I pass by reference. It includes the URL of the bundle and the name of asset which needs to be loaded. It also contains the reference to loaded bundle and the list of assets loaded (so I can unload bundle automatically, if no assets reference it).

So progress is just a public property which holds the download progress value (I have another progress for loading single asset as well but practically it’s 0 and then 100%). Since the whole process of downloading/loading is done in the coroutine, this property is the way to send information (progress) back to main logic.

You can do it simpler. Just update the loading progress bar from coroutine (you can send it by reference to downloader coroutine). If you have any other question, feel free to ask. Good luck!

I know its old post, but you are right, showing the progress after calling
DownloadHandlerAssetBundle.GetContent(assetBundleWebRequest);

show the progress to 1 or 100%