Hi everyone! In this lesson, I will present you how we can manage file uploads and file downloads with nice visualization of the progression.
In this example, I'm downloading directly binary files into the browser and as you will notice, we've got some progression bars that move in real time.
Earlier, before XHR2, the new version of Ajax, it was possible to do this but we had to ask at regular intervals of time the cerver.
« Hey server! how many bytes did you receive from my Browser? » Now, monitoring the progression is made much easier because we've got progression callbacks that are called by the browser while it's uploading or downloading a file.
This small multitrack player I've already shown during the week one, about Web Audio, is downloading binary files, monitoring progress, and so on.
So, let's start with a very simple example.
Here I will download a song that is located on one of my servers here... mainline.i3s.unice.fr
When I will click on the download (button) and play the example song, it will start the download.
So let's look at the downloadSoundFile function, that is called when we click on the button.
How do we use XHR2? We first start by creating an object that is an XML HTTP request.
We set the method: GET is for downloading a file, the URL, and the last parameter "true", leave it like that.
Then, as we are going to download a binary file, we set the response type to "arrayBuffer", and this is new.
It was not possible with the previous version of Ajax, that was available in the browsers.
HTTP is a text based transfer protocol.
So the text encoded files needed to be decoded in the browser or in the server.
And when we manually, programmatically, in the JavaScript code, used Ajax, we had two decode this by hand.
Now, we ask the browser: "please, decode this for me" and give me directly, automatically, a binary file.
We prepared the request here, then we send the request: xhr.send() will send asynchronously the request.
That means that the server will handle this in the background.
This message "Ajax request sent...", will be displayed as soon as this instruction is executed.
And if the file we are going to download is big, is large, then it can take from dozens of seconds, or maybe one minute.
And when the file is arrived the callback, xhr.onload callback, will be called by the browser.
Then, we call the initSound() function that will decode in memory the mp3 file, and then enable the start and stop buttons, so that we can play the song using Web Audio.
Before looking at the initSound (function) let's start it.
I click… You can see that the message "Ajax request sent" is displayed first, then "song downloaded".
This is on the onload callback, and then we call the initSound function that will decode the file and display the rest of the messages.
And now I can play the song [music] In this example, we are using Web Audio: it's not streaming the sound.
It’s playing a sample directly in memory so… you can give a look at the code.
It uses what we saw during the first week.
The playSound function builds a small graph with the decoded buffer as a source and directly to the speakers (to the destination).
If you want to monitor progress during download (so, this is the same example), it's very easy.
Just use a progress HTML element with a value of zero, and it will grow depending on the number of bytes the browser will have sent to the remote server.
Or will have downloaded... in case of a download.
So, when we started the request, we added a xhr.onprogress callback here that gets in event sent by the browser, that has two properties.
One is called "total", that is a total number of bytes in the files we are downloading, and "loaded" is the number of bytes we have actually downloaded.
And we can set these two properties directly to the "value" property of the progress element and to the "max" property.
And if we try this (download the file), we can see the progress bar growing because this onprogress callback is called regularly by the browser every second or something like that.
So, it's very easy to monitor the progress.
You can put vertically this bar using CSS, change the style.
Refer to the HTML5 Part 1 course for that.
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 all browsers (> 2012) 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):
// Load a binary file from a URL as an ArrayBuffer.
function loadSoundFile(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
initSound(this.response); // this.response is an ArrayBuffer.
};
xhr.send();
}
In this example, instead of reading the file from disk, we download it using XHR2.
Complete source code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>XHR2 and binary files + Web Audio API</title>
</head>
<body>
<p>Example of using XHR2 and <code>xhr.responseType = 'arraybuffer';</code> to download a binary sound file
and start playing it on user-click using the Web Audio API.</p>
<p>
<h2>Load file using Ajax/XHR2 and the arrayBuffer response type</h2>
<button onclick="downloadSoundFile('https://myserver.com/song.mp3');">
Download and play example song.
</button>
<button onclick="playSound()" disabled>Start</button>
<button onclick="stopSound()" disabled>Stop</button>
<script>
// WebAudio context
var context = new window.AudioContext();
var source = null;
var audioBuffer = null;
function stopSound() {
if (source) {
source.stop();
}
}
function playSound() {
// Build a source node for the audio graph
source = context.createBufferSource();
source.buffer = audioBuffer;
source.loop = false;
// connect to the speakers
source.connect(context.destination);
source.start(0); // Play immediately.
}
function initSound(audioFile) {
// The audio file may be an mp3 - we must decode it before playing it from memory
context.decodeAudioData(audioFile, function(buffer) {
console.log("Song decoded!");
// audioBuffer the decoded audio file we're going to work with
audioBuffer = buffer;
// Enable all buttons once the audio file is
// decoded
var buttons = document.querySelectorAll('button');
buttons[1].disabled = false; // play
buttons[2].disabled = false; // stop
alert("Binary file has been loaded and decoded, use play / stop buttons!")
}, function(e) {
console.log('Error decoding file', e);
});
}
// Load a binary file from a URL as an ArrayBuffer.
function downloadSoundFile(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
console.log("Song downloaded, decoding...");
initSound(this.response); // this.response is an ArrayBuffer.
};
xhr.onerror = function(e) {
console.log("error downloading file");
}
xhr.send();
console.log("Ajax request sent... wait until it downloads completely");
}
</script>
</body>
</html>
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-55: The 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.
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.
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).
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
...
xhr.onprogress = function(e) {
// do something
}
xhr.send();
Note that an alternative syntax such as xhr.addEventListener('progress', callback, false) also works.
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
...
xhr.upload.onprogress = function(e) {
// do something
}
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.
The event e passed to the onprogress callback has two pertinent properties:
loaded which corresponds to the number of bytes that have been downloaded or uploaded by the browser, so far, and
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:
<progress id="downloadProgress" value=0><progress>
JavaScript:
// progress element
var progress = document.querySelector('#downloadProgress');
function downloadSoundFile(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
...
xhr.onprogress = function(e) {
progress.value = e.loaded;
progress.max = e.total;
}
xhr.send();
}
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:
And a current download of 2000 bytes will define <progress value=2000 max=10000> and will look like this:
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.