hacks.mozilla.org

Daily Archive for June 18th, 2009

using HTML5 video with fallbacks to other formats

The Mozilla Support Project and support.mozilla.com (SUMO for short) is an open and volunteer powered community that helps over 3 million Firefox users a week get support and help with their favorite browser. The Firefox support community maintains a knowledge base with articles in over 30 languages and works directly with users through our support forums or live chat service. They’ve put together the following demonstration of how to use open video and Flash-based video at the same time to provide embedded videos to users regardless of their browser. This demo article was written by Laura Thomson, Cheng Wang and Eric Cooper

Note: This article shows how to add video objects to a page using JavaScript. For most pages we suggest that you read the article that contains information on how to use the video tag to provide simple markup fallbacks. Markup-based fallbacks are much more elegant than JavaScript solutions and are generally recommended for use on the web.

One of the challenges to open video adoption on the web is making sure that online video still performs well on browsers that don’t currently support open video.  Rather than asking users with these browsers to download the video file and use a separate viewer, the new <video> tag degrades gracefully to allow web developers to provide a good experience for everyone.  As Firefox 3.5 upgrades the web, users in this transitional period can be shown open video or video using an existing plugin in an entirely seamless way depending on what their browser supports.

At SUMO, we use this system to provide screencasts of problem-solving steps such as in the article on how to make Firefox the default browser.

If you visit the page using Firefox 3.5, or another browser with open video support, and click on the “Watch a video of these instructions” link, you get a screencast using an Ogg-formatted file.  If you visit with a browser that does not support <video> you get the exact same user experience using an open source .flv player and the same video encoded in the .flv format, or in some cases using the SWF Flash format.  These alternate viewing methods use the virtually ubiquitous Adobe Flash plugin which is one of the most common ways to show video on the web.

The code works as follows.

In the page which contains the screencasts, we include some JavaScript.   Excerpts from this code follow, but you can see or check out the complete listing from Mozilla SVN.

The code begins by setting up an object to represent the player:

Screencasts.Player = {
width: 640,
height: 480,
thumbnails: [],
priority: { 'ogg': 1, 'swf': 2, 'flv': 3 },
handlers: { 'swf': 'Swf', 'flv': 'Flash', 'ogg': 'Ogg' },
properNouns: { 'swf': 'Flash', 'flv': 'Flash', 'ogg': 'Ogg Theora' },
canUseVideo: false,
isThumb: false,
thumbWidth: 160,
thumbHeight: 120
};

We allocate a priority to each of the possible video formats.  You’ll notice we also have the 'canUseVideo' attribute, which defaults to false.

Later on in the code, we test the user’s browser to see if it is video-capable:

var detect = document.createElement('video');
if (typeof detect.canPlayType === 'function' &&
    detect.canPlayType('video/ogg;codecs="theora,vorbis"') == 'probably' ) {
      Screencasts.Player.canUseVideo = true;
      Screencasts.Player.priority.ogg =
          Screencasts.Player.priority.flv + 2
}

If we can create a video element and it indicates that it can play the Ogg Theora format we set canUseVideo to true and increase the priority of the Ogg file to be greater than the priority of the .flv file. (Note that you could also detect if the browser could play .mp4 files to support Safari out of the box.)

Finally, we use the priority to select which file is actually played, by iterating through the list of files to find the one that has the highest priority:

for (var x=0; x < file.length; x++) {
  if (!best ) {
    best = file[x];
  } else {
    if (this.priority[best] < this.priority[file[x]])
      best = file[x];
  }
}

With these parts in place, the browser displays only the highest priority video and in a format that it can handle.

If you want to learn more about the Mozilla Support Project or get involved in helping Firefox users, check out their guide to getting started.

Resources

* Note: To view this demo using Safari 4 on Mac OSX, you will need to add Ogg support to Quicktime using the Xiph Quicktime plugin available from http://www.xiph.org/quicktime/download.html

XHR progress and rich file upload feedback

This demo is by Olli Pettay (smaug) with help from Austin King.

A common limitation on the web today has been a rich file upload widget for web applications. Many sites use Flash or a desktop helper applications to improve the experience of uploading files.

Firefox 3.5 bridges one of these gaps allowing a better progress indicator to be built. Many developers don’t realize that they can use Firefox’s File object (nsIDOMFile) and XMLHttpRequest together to accomplish file uploads. This demo will feature an upload widget that gives the kind of rich progress feedback that users have come to expect, as well as fast and easy multiple simultaneous file uploads.

Progress Indicators

It’s always a good idea to expose feedback that your application is hard at work for them, and when the current action is expected to finish. The two main types of progress feedback are:

  • indeterminate progress – some activity just happened
  • deterministic progress I’m 40% done, I’m 42% done… etc

Deterministicsaidwhat? The Demo

We’ve created a simple file upload / download page that demonstrates the progress bar:

The demo is host at mozilla.pettay.fi and requires Firefox 3.5 beta4 or later. It demonstrates how to do multiple simultaneous file uploads without posting a form or leaving the current page. For each file upload / download we display the current speed, % complete, and bytes transmitted. We’ll go over a few key snippets of the code which are used in the screenshot above. Please click through the the demo and view source for the full code example.

The page contains two HTML inputs, one type="file" and one type="button". The form is never actually submitted, instead we add an onclick handler to the button:

<input type="file" id="file">
<input type="button"
          onclick="startXHR();"
          value="Upload file using XHR">

In the startXHR function, we create an XMLHttpRequest and add an event handler to the XHR request to listen for the new ‘progress’ event. With this ProgressEvent’s lengthComputable property, we will know if we are dealing with an indeterminate or deterministic progress. The object also gives us loaded and total bytes.

var xhr = new XMLHttpRequest();
 
xhr.onprogress = function(evt) {        
if (evt.lengthComputable) {
    evt.target.curLoad = evt.loaded;
    evt.target.log.parentNode.parentNode.previousSibling.textContent =
        Number(evt.loaded/k).toFixed() + "/"+ Number(evt.total/k).toFixed() + "kB";
}
if (evt.lengthComputable) {
    var loaded = (evt.loaded / evt.total);
    if (loaded < 1) {
        var newW = loaded * width;
        if (newW < 10) newW = 10;
            evt.target.log.style.width = newW + "px";
        }
    }
};

Now we need some data to send. We grab the contents of the file directly from the input by id:

var files = document.getElementById("file").files;
if (files) {
   var file = files.item(0);
   xhr.sendAsBinary(file.getAsBinary());

And the last step is to start the request:

xhr.open("POST", "cgi-bin/posthandler.pl");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
xhr.sendAsBinary(file.getAsBinary());

These methods would also work with xhr.upload.onprogress.

Notice the use of the sendAsBinary method on the XMLHttpRequest object and getAsBinary on the File object. Starting with Firefox 3 you’ve been able to get at the contents of a file on the client side without form submission. This is quite useful for moving beyond the limitation of tranditional file input and form submissions. It is also part of an up and coming W3C standard for FileUpload.

A related method that the nsIDOMFile provides is the getAsText method which returns a DOMString suitable for slicing, dicing, and displaying.

Here is an example usage, not used by the demo:

file.getAsText("utf8");

So that’s the gist of the code. Check out the demo and view it’s source.

Feedback In User Interfaces

Exposing system feedback to users improves perceived performance. We
can’t always determine how long something will take, so at a minimum we
can show indeterminate progress.

During file uploads and file downloads (assuming the server gives us Content-Length) we do indeed know the total number of bytes. Firefox 3.5’s Progress Events support adds a progress event so that we can show actual upload/download progress.

Traditionally XMLHttpRequests were difficult to get deterministic progress back from. In theory, you could give it callbacks and watch for status code updates and textual message updates, but in practice they turn out to be not very useful. In the past, if a deterministic progress meter was important, you’d have to make a second XHR request to poll for progress.

Enter Progress Events

The W3C has a working draft for Progress Events 1.0 which we include in Firefox 3.5. Firefox has added a key new DOM ProgressEvent progress event, as well as the loadstart event. The other existing events included: error, abort and load.

These same events are also available for uploads and downloads. The progress event gives us the following properties:

  • lengthComputable – true or false, is the size of the request known?
  • loaded – number of bytes received so far
  • total – number of bytes expected for entire request

The Contract

When you’re looking at the properties of those progress events, certain rules apply that you can depend on. They are:

  • The total property will be 0 when lengthComputable is false.
  • The loadstart event is always signaled only once.
  • The progress event is fired off zero or more times after loadstart.

And that’s it. Go forth and improve file uploads with Firefox 3.5 goodness.