Ajax/XHR2 and binary files, downloading files

Ajax and binary files – downloading files and monitoring progress

HTTP is a text based protocol, so when you upload/download images, videos or any binary file, they must first be text encoded for transmission, then decoded on-the-fly upon receipt by the server or browser. For a long time, when using Ajax, these binary files had to be decoded “by hand”, using JavaScript code. Not recommended!

We won’t go into too much detail here, but recent browsers (> 2012) usually support  XHR2. XHR2 adds the option to directly download binary data. With XHR2, you can ask the browser to encode/decode the file you send/receive, natively. To do this, when you use XMLHttpRequest to send or receive a file, you must set the xhr.responseType as arrayBuffer.

Below is a function that loads a sound sample using XMLHttpRequest level 2.

Note: 1) the simple and concise syntax, and 2) the use of the new arrayBuffer type for the expected response (line 5):

  1. // Load a binary file from a URL as an ArrayBuffer.
  2. function loadSoundFile(url) {
  3.    var xhr = new XMLHttpRequest();
  4.    xhr.open(‘GET’, url, true);

  5.    xhr.responseType = ‘arraybuffer’; // THIS IS NEW WITH HTML5!
  6.    xhr.onload = function(e) {
  7.       initSound(this.response); // this.response is an ArrayBuffer.
  8.    };

  9.    xhr.send();
  10. }

Example: download a binary song file using XHR2 and responseType=’arraybuffer’, and play it using Web Audio

Try this example on JSBin:

The above function is used, and we modified an example from the HTML5 part 1 course that shows how to read a binary file from disk using the File API’s method readAsArrayBuffer. In this example, instead of reading the file from disk, we download it using XHR2.

Downloading file with Xhr2

Complete source code:

  1. <!DOCTYPE html>
  2. <html lang=“en”>
  3.  <head>
  4.    <title>XHR2 and binary files + Web Audio API</title>
  5.  </head>
  6. <body>
  7. <p>Example of using XHR2 and <code>xhr.responseType = ‘arraybuffer’;</code> to download a binary sound file
  8. and start playing it on user-click using the Web Audio API.</p>
  9. <p>
  10. <h2>Load file using Ajax/XHR2 and the arrayBuffer response type</h2>
  11. <button onclick=downloadSoundFile(http://myserver.com/song.mp3&#8217;);>
  12.      Download and play example song.
  13.  </button>
  14. <button onclick=playSound() disabled>Start</button>
  15. <button onclick=stopSound() disabled>Stop</button>
  16.   // WebAudio context
  17.   var context = new window.AudioContext();
  18.   var source = null;
  19.   var audioBuffer = null;
  20.   function stopSound() {
  21.     if (source) {
  22.        source.stop();
  23.     }
  24.   }
  25.   function playSound() {
  26.     // Build a source node for the audio graph
  27.     source = context.createBufferSource();
  28.     source.buffer = audioBuffer;
  29.     source.loop = false;
  30.     // connect to the speakers
  31.     source.connect(context.destination);
  32.     source.start(0); // Play immediately.
  33.   }
  34.   function initSound(audioFile) {
  35.     // The audio file may be an mp3 – we must decode it before playing it from memory
  36.     context.decodeAudioData(audioFile, function(buffer) {
  37.       console.log(“Song decoded!”);
  38.       // audioBuffer the decoded audio file we’re going to work with
  39.       audioBuffer = buffer;
  40.       // Enable all buttons once the audio file is
  41.       // decoded
  42.       var buttons = document.querySelectorAll(‘button’);
  43.       buttons[1].disabled = false; // play
  44.       buttons[2].disabled = false; // stop
  45.       alert(“Binary file has been loaded and decoded, use play / stop buttons!”)
  46.     }, function(e) {
  47.        console.log(‘Error decoding file’, e);
  48.     });
  49.   }
  50.   // Load a binary file from a URL as an ArrayBuffer.
  51.   function downloadSoundFile(url) {
  52.     var xhr = new XMLHttpRequest();
  53.     xhr.open(‘GET’, url, true);
  54.     xhr.responseType = ‘arraybuffer’; // THIS IS NEW WITH HTML5!
  55.     xhr.onload = function(e) {
  56.        console.log(“Song downloaded, decoding…”);
  57.        initSound(this.response); // this.response is an ArrayBuffer.
  58.     };
  59.     xhr.onerror = function(e) {
  60.       console.log(“error downloading file”);
  61.     }
  62.     xhr.send();
  63.        console.log(“Ajax request sent… wait until it downloads completely”);
  64.   }
  65. </body>
  66. </html>

Explanations

    • Line 12: a click on this button will call the downloadSoundFile function, passing it the URL of a sample mp3 file.
    • Lines 58-73: this function sends the Ajax request, and when the file has arrived, the xhr.onload callback is called (line 63).
    • Lines 39-55The initSound function decodes the mp3 into memory using the WebAudio API, and enables the play and stop buttons.
    • When the play button is enabled and clicked (line 15) it calls the playSound function. This builds a minimal Web Audio graph with a BufferSource node that contains the decoded sound (lines 31-32), connects it to the speakers (line 35), and then plays it.

Monitoring uploads or downloads using a progress event

downloading progression using a progress element

1 – Declare a progress event handler

XHR2 now provides progress event attributes for monitoring data transfers. Previous implementations of XmlHttpRequest didn’t tell us anything about how much data has been sent or received. The ProgressEvent interface adds 7 events relating to uploading or downloading files.

attribute type Explanation
onloadstart loadstart When the request starts.
onprogress progress While loading and sending data.
onabort abort When the request has been aborted, either by invoking the abort() method or navigating away from the page.
onerror error When the request has failed.
onload load When the request has successfully completed.
ontimeout timeout When the author specified timeout has passed before the request could complete.
onloadend loadend When the request has completed, regardless of whether or not it was successful.

The syntax for declaring progress event handlers is slightly different depending on the type of operation: a download (using the GET HTTP method), or an upload (using POST).

Syntax for download:

  1. var xhr = new XMLHttpRequest();
  2. xhr.open(‘GET’, url, true);
  3. xhr.onprogress = function(e) {
  4. // do something
  5. }
  6. xhr.send();

Note that an alternative syntax such as xhr.addEventListener(‘progress’, callback, false) also works.

Syntax for upload:

  1. var xhr = new XMLHttpRequest();
  2. xhr.open(‘POST’, url, true);
  3. xhr.upload.onprogress = function(e) {
  4. // do something
  5. }
  6. xhr.send();

Notice that the only difference is the “upload” added after the name of the request object: with GET we use xhr.onprogress and with POST we use xhr.upload.onprogress.

Note that an alternative syntax such as xhr.upload.addEventListener(‘progress’, callback, false) also works.

2 – Get progress values (how many bytes have been downloaded) and the total file size

The event e passed to the onprogress callback has two pertinent properties:

    1. loaded  which corresponds to the number of bytes that have been downloaded or uploaded by the browser, so far, and
    2. total which contains the file’s size (in bytes). 

Combining these with a <progress> element, makes it very easy to render an animated progress bar. Here is a source code extract that does this for a download operation:

HTML:

  1. <progress id=“downloadProgress” value=0><progress>

JavaScript:

  1. // progress element
  2. var progress = document.querySelector(‘#downloadProgress’);
  3. function downloadSoundFile(url) {
  4.   var xhr = new XMLHttpRequest();
  5.   xhr.open(‘GET’, url, true);
  6.   …
  7.   xhr.onprogress = function(e) {
  8.     progress.value = e.loaded;
  9.     progress.max = e.total;
  10.   }
  11.   xhr.send();
  12. }

Explanations: by setting the value and max attributes of the <progress> element with the current number of bytes downloaded by the browser and the total size of the file (lines 10-11), it will reflect the actual proportions of the file downloaded/still to come.

For example, with a file that is 10,000 bytes long, if the current number of bytes downloaded is 1000, then <progress value=1000 max=10000> will look like this: 

progress bar 10%

And a current download of 2000 bytes will define <progress value=2000 max=10000> and will look like this:

progress bar 20%

Complete example: monitoring the download of a song file

monitoring download

 This is a variant of the previous example that uses the progress event and a <progress> HTML5 element to display an animated progression bar while the download is going on.

Try it on JSBin – look at the code, which includes the previous source code extract.

Leave a comment