Audio/Video Tag: Don’t Forget to load() before you play()

This is a gotcha that will catch a lot of people out because it goes against the expectation that you just change an image’s appearance by setting its “src”, done. And also, it’s a deviation from the more general convention that you change DOM properties declaratively to make stuff happen. Probably for good reason, given the temporal nature of audio and video playback, but just beware it’s a different model.

Bottom line is – load() between changing src and play()ing:

javascript
< view plain text >
  1. function play(url) {
  2.   var audio = document.querySelector("audio");
  3.   audio.src = url;
  4.   audio.load(); // !HUL|_O! PAY ATTENTI0N!
  5.   audio.play();
  6. }

A lot of online sources talk about play() and omit to mention this vital fact, because they’re only play()ing a single track…and the first track works fine. This is an oddity – you can just call play() the first time and it will automatically load. This happens whether you set “src” in the initial HTML’s “audio” tag or whether you set it programatically. I did some fiddling and it looks like the behaviour is the same in Chrome and Firefox. It’s like the track is auto-load()ed the first time you set its source. (And note that Firefox doesn’t auto-buffer so that’s nothing to do with it.)

devx has you covered on this topic, and points out the subtle issues which you would need to look at for a quality production:

However, media by its very nature is a time-spanning process. You’re working with both the need to load and cache audio and video files and the network connection, which itself can prove to be a challenge that’s usually not a factor for most non-temporal resources. For these reasons, when dealing with video and audio on the web, if you want to take control of the process in any way, you have to work asynchronously, tying into the various events that are passed back to the client from the media-serving website. Table 4 (taken directly from the HTML 5 documentation) provides a full listing of the various events and when they are dispatched. … Of all the events in Table 4, perhaps the most useful are the canplay and canplaythrough events. The first, canplay, will fire when enough data has been loaded for the video player to start actually rendering content constructively, even if not all of the data has been loaded. The canplaythrough event, on the other hand, will fire when the data has essentially completely loaded into the browser’s buffer, making it possible to play the video all the way through without the need to pause and retrieve more content.

Yoink: Extracting All Scripts and Stylesheets on the Page

This here is a script that will put the code of all scripts and stylesheets into a single variable. Usage:

javascript
< view plain text >
  1. yoink(function(all) {
  2.   console.log("scripts", all.scripts); // string array
  3.   console.log("stylesheets", all.stylesheets); // string array
  4. });

As you can see, you have to provide a callback because this stuff is asynchronous. It downloads the files one at a time to keep things simple and without using any fancy-pants Ajax queueing library. And right now, timeouts won’t be too graceful. Also, browsers will typically allow scripts and stylesheets to work cross-domain, but of course XHR won’t. So any external scripts and stylesheets are unfortunately out of reach and will not be captured. This is an especially tragic circumstance in the case where they are hosted on a different subdomain. But, as they say, “hey”. I don’t know what that means, but, as they say, “hey”.

Yoink will download inline scripts (script tags with bodies), remote scripts (script tags having a “src” attribute), inline style attributes (elements having a “style” attribute), inline style tags (style tags with bodies), and remote stylesheets (links of type “text/css”).

There are several purposes. I did a quick hack version of this a while back to improve logging info; in principle, a logging method could introspect on the source tree to show extra context info (locate the log message I’m currently outputting, and then say which line it’s coming from, for example).

In the case of Single Page Applications a la TiddlyWiki, a plugin might use Yoink to inline all the remote content.

Right now, I’m more interested in using it for browser extensions, such as the Feature Creep extension I suggested, to show what’s happening on the page. Some of that stuff you can work out by inspecting the DOM, but other stuff … show me the code!

Get it here – http://gist.github.com/378850. Shown below for convenience:

javascript
< view plain text >
  1. function yoink(complete) {
  2.  
  3.   var all={
  4.     scripts: [],
  5.     stylesheets: []
  6.   }
  7.  
  8.   function downloadResources(urls, onComplete) {
  9.  
  10.     var resources = [];
  11.  
  12.    (function inlineResource(index) {
  13.  
  14.       if (index==urls.length) {
  15.         onComplete(resources);
  16.         return;
  17.       }
  18.  
  19.       var xhr = new XMLHttpRequest();
  20.       xhr.open("GET", urls[index], true);
  21.       xhr.onreadystatechange = function() {
  22.         if (xhr.readyState!=4) return;
  23.         if (xhr.status==200) resources.push(xhr.responseText);
  24.         inlineResource(index+1);
  25.       }
  26.       xhr.send();
  27.     })(0);
  28.  
  29.   }
  30.  
  31.   var scripts = document.getElementsByTagName("script");
  32.   var scriptSources = [];
  33.   for (var i=0; i<scripts.length; i++) {
  34.     if (scripts[i].src) scriptSources.push(scripts[i].src); // remote
  35.     else all.scripts.push(scripts[i].innerHTML); // inline
  36.   };
  37.   downloadResources(scriptSources, function(resources) {
  38.     all.scripts = all.scripts.concat(resources);
  39.  
  40.     var styles = document.getElementsByTagName("styles");
  41.     for (var i=0; i<styles.length; i++) { all.stylesheets.push(styles[i]); };
  42.     var allElements = document.getElementsByTagName("*");
  43.     for (var i=0; i<allElements.length; i++) {
  44.       var inlineStyle = allElements[i].getAttribute("style")
  45.       if (inlineStyle) all.stylesheets.push(inlineStyle);
  46.     };
  47.     var links = document.getElementsByTagName("link");
  48.     var cssHrefs = [];
  49.     for (var i=0; i<links.length; i++) {
  50.       if (links[i].type=="text/css") cssHrefs.push(links[i].href);
  51.     };
  52.     downloadResources(cssHrefs, function(resources) {
  53.       all.stylesheets = all.stylesheets.concat(resources);
  54.       complete(all);
  55.     });
  56.  
  57.   });
  58.  
  59. }

SPA Hacks: Hacks Emerging From the World of Single-Page Web Apps

This is a permalink for my JSConf.US talk. Full slides are online here:

THE LOST HACKS: Ridiculous browser tricks from the world of single-page Applications

The talk will overview TiddlyWiki and Single-Page Apps, and then cover eight specific hacks:

  • File access without browser extensions
  • Javascript-HTML chameleon files
  • SVG-VML chameleon files
  • Inline SVG
  • iFrame squriting
  • Script islands
  • Embedded images
  • Fragment IDs

I’ll be posting a link to the slideshow notes from here. I’m pleased to say the nascent TiddlySlidy app has been a pleasure to dogfood it in.

[Image credits]

Web-O-Random is New

Web-O-Random is a new site I made, a simple random number generator with simple URLs. The About Page tells the story:

Random Numbers

Random Lists

Programming Formats

(The old Web-O-Random is late. Deceased. Run down the curtain and joined the bleedin’ choir. This is another of those things I was planning to do for several years and finally got around to it.)

WebWait Updated

One of the projects I wanted to work on in my time off was WebWait.

It finally does what I wanted it to do all along: Permanently record benchmarks. You can get a unique URL for each benchmarking session you run by hitting Save. Funny – WebWait was running as a Rails app for several years, but was always a pure browser-side client until I finally completed this. (Ultimately, I didn’t use Rails anyway – see below.)

Some other enhancements too …

  • Bar charts (using the Google Charts API):

  • Expanded dashboard, showing a box for each site you’ve trialled. (Previously, there was only one box ever shown, the latest one. Since people like taking screenshots of these, I figured it would be cool to include multiple boxes.)

  • There is also a JSON view of the trials that were recorded.

It would be cool to expand it further, for example allowing pages to be cross-linked when they benchmark the same trial. But I’ll leave it here and see where demand takes it.

The new WebWait server is powered by the incredibly productive trio of Ruby, Sinatra, and TokyoCabinet. On the browser, it’s still jQuery, with the DataTables plugin and my own jQuery iFrame plugin. Hosted on SliceHost (where I have now happily migrated many things.)