Earlier today I was happy to hear that PhoneGap 2.7 was released. While perusing the changelist, I thought I read that progress events for file transfers were added in this release. However, I was wrong. FileTransfer has supported a progress event for a few months now. But since I figured this out while halfway through a demo, I figured I'd finish it up anyway and share it on my blog.
As you can imagine, the onprogress event is a property of the FileTransfer object. It is passed a progressEvent that is - unfortunately - not documented as far as I can see. The code example though gives you enough information I think to deal with it:
So from what I can see - you get a property that determines if the total size is known, and if so, you can get a percentage by using it with the loaded property. Otherwise you're just guessing at the time left, but at least you know something is going on.
For my demo, I thought I'd build a simple MP3 downloader/player. I googled for 'free open source music' and came across this incredible MP3 by Kansas Joe McCoy and Memphis Minnie: When the Levee Breaks. Go ahead and listen to it. As much as I love indie music and trance, the sound of old recordings like this is like pure gold to the ears.
Anyway - I began by creating an incredibly simple web page. It lists the name of the artists and the song along with a picture. I've included a button that will trigger the download. Also make note of the status div I'll be using for - you guessed it - progress events.
Here's a screen shot of it in action.

Ok, now let's take a look at the code.
Starting from the top, the first thing that may interest you is the file system request. I ask for the temporary file system so I have a place to store the mp3. Once I've got a hook to the file system I then enable the button in my web page and start listening for a touch event.
The function that handles the download, startDl, creates the FileTransfer object and points it to the remote MP3. I've used almost the exact same onprogress event as demonstrated in the PhoneGap docs. I changed it to write out a percentage when possible and in other cases, simply append dots to the end of a string. That way people know something is still being transferred.
The final portion simply handles the media portion. I didn't bother adding any real controls so it just begins to play the track and that's it. (To be clear, this wouldn't be hard to add, just check the Media API for more information.)
Watch the video below to see it in action:
As a last tip, note that the docs for Media seem to imply that you need a URI, not a file path. On iOS it seems to require a path, not a URI. Thanks to Simon MacDonald for helping me with this.
Archived Comments
I have a PhoneGap+jQM-based app where I hooked the progress event data to a nice Boostrap CSS-only animated progressbar (since jQM doesn't have progressbar UI) for providing status of uploads to an Amazon S3 bucket. The whole thing works like a charm and looks great, good stuff! :-)
I have to say that my only pain point with PhoneGap/Cordova so far is this long standing issue of camera.getPicture() not being able to support multiple media select at once: https://issues.apache.org/j... The only workarounds I know of are the hassle of integrating plugins to accommodate that or the one I did for myself which was to roll my own image gallery for multiple media select through use File API to read the images available on the device, create thumbnails of them via javascript-based image scaling algorithms, and implement caching/pagination for the whole thing.
My favorite cover of that will always be Led Zepplin. I'm gonna have to check out that site.
Cool code Ray.
@Peter: Does the Capture API work for you instead?
@Raymond Nope, Capture API does allow multiple images/videos to be taken live on the device at once (limited by the optional "limit" parameter) but what I need is the ability to choose multiple existing media files (which had already been taken by the device's camera or copied over to the device's memory in the past) from the device's native gallery.
The current state of the PhoneGap Camera.getPicture method is akin to the web 1.0 days when an html form looking to accept some file from the user would throw a file dialog that only allows one to be selected. Then Flash's equivalent came along and we were able to hold down shift or control and select multiple files at once. I find it strange that cordova/phonegap has reached such maturity and yet still won't have this basic "multiple media select from native gallery" feature until some version past 3.0 or so :-/
Also, I chose the "code my own media gallery in the webview (with help from PG File API)" workaround instead of the "use a phonegap plugin" workaround because: 1) It didn't break my ability to still cloud compile through Build whereas using some random plugin would've 2) It allows me to serve the exact same multiple media select UI/functionality to all users instead of their being being differences between users based on their differing native galleries
Let me also mention that on Android I can use PG File API to access the entire file system but on iOS apps are sandboxed so I actually can't access the media files that are in the camera roll for instance :-/ (So this is where I really do need that multiple media select being supported by camera.getPicture)
Ah, Peter, thanks for clarifying that!
Hello, great article. I really hope you do one about uploading to an amazon s3 bucket using the phonegap file transfer. Best wishes
I don't have one planned, but I know it has been done. I'd do a quick check of the Google group (ie load it and search it ;).
Hi Raymond,
What is the best way to download multiple files from a server (json files) and have a progress event for the lot.
what i a have at the moment is a loop with my fileTransfer.download() function inside...
Hmm. Let's forget about the progress for a bit. The best way (imo) to handle N async events is to use Deferreds in jQuery. They make it (somewhat) easy to say, "I've fired N things and when they are all done, do X."
The progress meter would be more difficult. You could do N progress meters of course. Windows does that - if I remember right, when copying/moving a set of files. One PM would be fairly difficult. You would need to adjust the Max value as you get more and more data in. To support that, you would need to associate on PM event with one file. I'm not sure you can do that. No... I think you could. Yeah, so you would need to create an aggregate file size.
I think it is possible. Just non-trivial. ;)
Thanks for your reply Raymond. Sadly i am using zepto and i'm not sure there is a deferred function...
Back again... Underscore might be my solution http://underscorejs.org/#defer
I am getting the loaded size twice as that of the total size
fileTransfer.onprogress = function(progressEvent) {
if (progressEvent.lengthComputable) {
perc = Math.floor((progressEvent.loaded / progressEvent.total) * 100);
//perc = Math.floor(progressEvent.loaded / progressEvent.total * 100);
status.text(perc + "% loaded...");
loaded.text(progressEvent.loaded);
if(fileSize.text()==""){
fileSize.text(" of "+progressEvent.total+" bytes");
}
}// else {
It could possibly be this - from the docs: "On both Android an iOS, lengthComputable is false for downloads that use gzip encoding."
thank you Raymond for reply,
I tested lengthComputable in funbook, it is true. I dont understand why
progressEvent.loaded goes only twice of progressEvent.total, another thing is 'download complete message' is fired only once at the end, it means download event is fired only once not twice
Yeah you got me there man. I would have to go deep into your code and test to give you firm reasons.
sorry raymond i have been busy somewhere else, did you got the same problem, i still couldn't find the reason, your feedback might be very helpful and give me some hints.
Thanks for giving your time in my problem
No, I'm not seeing it myself.
I have ran into the same problem...
The same code works different on iOS and Android.. iOS ok, but getting 200% on Android... tried on Phonegap 2.7.0 and 2.9.0.. btw, i'm using Phonegap Build service.
Are you using gzip? The docs say this will change the values.
I have the same problem with the progress percent. It goes up to 200% in Android. I didn't tested on iOS. I am using Cordova 3.0
I think I have to adjust the value .. but it's not the best solution.
Or it might be a bug somewhere ?
@Peter, could you link me to the phonegap plugin that will enable multiple image selecting ?
There does appear to be an issue with the .loaded value on android. The returned a value is x2 greater than what's actually been received.
I came across this post after discovering the issue on my own implementation of progress on FileTransfer. It's much the same as you have above, with the exception that I wasn't testing the .lengthComputable.
I've tried with different file types and with gzip enabled & disabled on the server; none of which made any changes to the .loaded value.
Cordova version 3.0.8 with an AVD that's running 4.3; tested with both Arm and Atom CPUs.
As a work around until a proper fix is found I've installed the device plugin :
cordova plugin add https://git-wip-us.apache.o...
And then modified the calculation using :
var loaded = device.platform == "Android" ? (prog.loaded / 2) : prog.loaded;
var pcnt = Math.round( (loaded / prog.total) * 100 );
If anyone has resolved this issue properly please let us know.
Thanks for sharing this, Andrew!
I have seen video ,here downloading and playing the file is not taking more than 5 seconds but Incase of my build everything working fine but taking more than 10 seconds.Is it depend on bandwidth or download speed?
Thankyou
Hi Rayman,
Nice blog, i've found the tutorials on filetranfer.download really useful.
Do you have a working example of how to download multiple files and firing an event when all are completed (as you mentioned before in a previous comment) with jQuery.Deferred?
If not no worries just thought I would check as I am struggling to get my head round how this could be achieved.
Thanks
Alex
Sorry, I don't.
Thanks anyway, I managed to get multiple files upload working which is great, I think it was courtesy of a Simon McDonald!
Basically just put the files to be downloaded in an array and popped the last element off when it was succesfully download and just recalled the same function. It was pretty straight forward when you think about it, thanks again Simon!
http://stackoverflow.com/qu...
Ok, glad you got it. :)
Great article. I got it to work first time without stress on a dialog box on Jquery Mobile. I got a problem with this line that goes up to 200%;
var perc = Math.floor(progressEvent.loaded / progressEvent.total * 100);
But since I am using the code to download updates from within my application, I change the 100 to 50 which halve the total percentage to 100% maximum.
@Tarhe - Remember that it's only android that's suffering with the incorrect .loaded value. Add the device plugin and check for device.platform == "Android" before making the adjustment.
@Andrew the Apps is meant to run on Android only, at least for now.
how to pause and cancel download?
Please read the docs:
http://cordova.apache.org/d...
There is an abort method - but no pause.
Hi Raymond,
Could you please suggest me,
I have a zipped folder (may include images, small audio and video files), now i need to download and extract it to client device.
Thank you.
Try zip.js - http://gildas-lormeau.githu.... I've blogged about it before. Not in context of PhoneGap, but for another demo.
Hi Raymond. Thanks for the tutorial. I have tried your code but it doesn't seem to work. when i click the download button nothing happens. I am using phonegap Build (Phonegap 3.0), tested the app on Android 4.3 device.
Here's what i did:
<span id="dlink"><img src="img_bookdl.png"></span>
<span id="status"></span>
document.addEventListener('deviceready', deviceready, false);
var server = "http://studentlife.com.ng/";
var sectionname;
var buttonDom;
var statusDom;
var fileSystem;
function deviceready() {
console.log('dv ready');
//step one is to request a file system
window.requestFileSystem(LocalFileSystem.TEMPORARY, 0,
function(fs) {
buttonDom = document.querySelector('#dlink');
statusDom = document.querySelector('#status');
buttonDom.addEventListener('touchend', dlink, false);
fileSystem = fs;
}, function(e) {
alert('failed to get fs');
alert(JSON.stringify(e));
});
}
function StartDownload()
{
var fileTransfer = new FileTransfer();
var uri = encodeURI("http://studentlife.com.ng/the great gatsby.pdf");
var filePath = fileSystem.root.fullPath+'/studentlife.pdf';
fileTransfer.onProgress = function (progressEvent)
{
if (progressEvent.lengthComputable)
{
var perc = Math.floor(progressEvent.loaded / progressEvent.total * 100);
statusDom.innerHTML=perc + "% loaded...";
}
else
{
statusDom.innerHTML="Loading";
}
}
fileTransfer.download(
uri,
filePath,
function(entry) {
statusDom.innerHTML="download complete: " + entry.fullPath;
},
function(error) {
statusDom.innerHTML="download error source " + error.source;
statusDom.innerHTML="download error target " + error.target;
statusDom.innerHTML="upload error code" + error.code;
});
}
Are you sure you added Filesystem support for the plugin? That's new for 3.0 and PhoneGap Build.
Hi,
I am new to Phonegap and android. I am stuck in a task where I have to upload multiple files to the server. This also include database insertion operation (Fetch form entry values and store in DB). I am using HTML tag for browsing file (Image .jpg). Eg.
<input type="file" id="upload_file_1" />
Kindly help us in this to upload files on to server.
Thanks in advance.
Regards
"Kindly help us in this to upload files on to server."
This can be handled by the FileTransfer code I talk about in this blog entry. Please read the documentation and let me know if you have a *specific* question.
Can you tell me how to show the progress of all files being downloaded from the server...
@Neerav: Not sure what you mean. Given N transfers - you can create N divs and update them as I did in the example above. Or you could attempt to use one progress display and do the math to figure out the right %.
The problem with progressEvent.loaded having twice the expected value seems to be caused by the definition of the class *SimpleTrackingInputStream* in FileTransfer.java.
SimpleTrackingInputStream overrides the three read methods from InputStream, in each case calling the corresponding InputStream.read and incrementing a counter by the number of bytes read. The problem is that InputStream implements _read(byte[] buffer)_ by calling _read(byte[] bytes, int offset, int count)_ - the end result is that the counter is incremented twice. Commenting out the method _SimpleTrackingInputStream....(byte[] bytes, int offset, int count)_ in FileTransfer.java solved the problem for me.
@Colin - thank you for doing this research. Cordova is open source - could you submit a pull request with that fix?
OK, will do!
I've submitted a pull request for the changes:
https://github.com/apache/c...
Also added comment to issue on JIRA:
https://issues.apache.org/j...
Hope I got this right - haven't done it before!
Thank you - as a PG user - I appreciate it. :)
Thanks a lot for this article and for your whole website! I'm currently working as an intern and I have to create an app which can record audio files and send them on a server and your work was a huge help for me.
The audio quality is still pretty bad (even in MPEG4 96Kbps) but it's working ;)
Thank you, thank you, thank you ;-)
Great post!
hi Ray,
I have used the code snippet given in the article.
I am using jquery mobile, phonegap
I am getting the Crap something went wrong... error .
ft.download(uri, downloadPath,
function(entry) {
statusDom.innerHTML = "";
var media = new Media(entry.fullPath, null, function(e) { alert(JSON.stringify(e));});
media.play();
},
function(error) {
alert('Crap something went wrong...');
});
I have checked the url of the auido file from the server. but still i am getting the error.
Could you please guide me on this, what went wrong here
Thanks
R
Change the 'crap' message to actually output the error and share that.
i have given the code
alert("download error source " + error.source);
alert("download error target " + error.target);
alert("upload error code" + error.code);
it is giving source as my server filelocation
it is giving target file path
upload error code as 1
Hi Randy
A small observation , when i alert fileSystem.root.fullPath, it is only giving "/".
will that cause any issue? becuase as it is trying to download the mp3 to root directory
Please suggest me , i struck ed here
1 in FileTransfer errors means file not found. Ensure your source is right.
Surely the source is avalable, do we have any documentation on phonegap about the error codes
Is it related to destination ?
You can find the error codes here:
https://github.com/apache/c...
thank you Ray
Is there any thing that i am missing here ?
In android i am i am getting / when i use - fileSystem.root.fullPath
is this some thign that i need to look at ?
I think the issue is with the source, not the destination. You said it exists, are you 100% sure it does? Care to share the URL here? Also, maybe edit the access block of config.xml to ensure it is allowed.
Yes it is
I am able to fix it
it is because of the fullpath.
after changing fileSystem.root.fullpath to fileSystem.root.toURL()
it is working.
I need to check the functionality completely.
I will confirm you once it is working
thank you soo much
Ok cool. FileSystem stuff has changed a *lot* lately.
and one more observation is , if change from temparary to persistant, it is now working
any suggestions ?
You got it working though, right?
Yes with LocalFileSystem.TEMPORARY but not with LocalFileSystem.PERSISTENT
I'd look at the FileSystem plugin docs. I know some folders are read only and some are meant for your data.
sure thank you Ray
When i alert target path , it is saying localhost as one of the directory in the path, but it should show my application name than localhost.
I have seen that you have answered some where else, i lost that thread.
if you remember, can you share me that
What I'm saying is - look at the docs for the FileSystem. They have aliases now for common paths, and some are *specific* for you to save stuff too.
Sure thank you.
Hey Raymond and thanks first for this great tutorial.
I'm just working on a file upload (video) to a server and this works fine. Even your progress event works fine and tells me the percentage.
But how can i realise a small green bar under my video, that displays the actual state of the progress? The Text is nice, but he doesn't look good. I would like to have a small bar, which gets more green the more the upload is done.
Can you tell me, how this could be realised?
Thanks in Advance!
KR from Germany!
One simple way would be to use a green gif sized appropriately - so 10% of max for 10% completed, and just update the width of the graphic.
You are awesome.
you need to change fullPath to toURL() in you code.
Yep, the file API has updated quite a bit.
Thank you Raymond for sharing your knowledge.
I often finish to read one of your posts when I'm trying to understand something about the Cordova platform or one of his plugins.
I'm in pain with this problem: I'm starting multiple downloads in background, saving the files with temporary names (to avoid saving incomplete or corrupted files if connection is lost): after each download finished (in success callback) I rename the temporary file with the correct name.
This works very well in Android, but in iOS I found that the success callback is never called.
It's seams to be a bug in org.apache.cordova.file-transfer plugin (I found various issues about it: https://issues.apache.org/j..., https://issues.apache.org/j..., https://issues.apache.org/j.... This means that I can only save files with temporary names: by now I set a workaround to use temporary names only on Android, but obviously I'm not satisfied.
I'm using Cordova version 3.6.3-0.2.13 and org.apache.codova.file-transfer plugin version
0.4.6.
I found that the on_progress callback is always called, also in iOS, so I tried to use it to understand if the file download was completed.
When (progressEvent.loaded == progressEvent.total), the download is completed, perfect, but how can I know which one specific file download is completed?
There is not "entry" parameter in on_progress callback or in progressEvent parameter.
Any idea?
Thank you and sorry for my English.
That's a real good question. I don't know. I'll do some digging.
So to be clear, the success handler is not firing for iOS due to the bugs you mentioned?
Yes.
The success handler, but also the error handler, are never fired in iOS.
Only onprogress is fired.
You got me then. Unless you can wait for the iOS bug to be fixed?
Sorry, I didn't understand.
I don't know why the success or error handler are never fired in iOS (but it's all ok in Android): I think is because of the bug I pointed out, but I'm not sure.
By now I finished to use temporary file names only for Android and not for iOS, but I was trying to find a workaround, to have the App behave in the same way in the two platforms.
If I don't find it, I will wait for a new version of the plugin, hoping it will fix my problem...
Great Article! one thing to note here is that in current version of cordova 6, the fullPath method is deprecated and instead you could use toURL method which holds the file:/// protocol schema inside it. Another weird thing is that Android ignores onprogress event.
In theory you could just use XHR2 now. :) That should support progress events fine.
Actually after playing around with the file transfer finally I implemented the unprogressive event. Looks like the problem was it could not determine the total volume to be downloaded and I solved it by getting the volume from the server manually.
Hi Ray, any idea why the progress isn't firing on GZipped files?
Sorry no. I'd file a bug report on the plugin. Also - it may be a web server thing. I believe it has to report the size to the requestor, and it may not be doing so for gzip files.
Thanks for the info. I'll try to look into it a bit.
Thanks for sharing it, helped me a lot.