In case you’re wondering what’s happening, I’m working on the “business” end of my site. The goal was to embed my movies into simple presentable pages. I succeeded but in the process I learned once again why I don’t want to do this web development thing for a living.

Here is the goal: to embed mp4 movies in my pages while keeping them XHTML compliant. I don’t want to force people into one and only one plugin, I don’t want the plugin to load whenever you load the page and the page should be compatible with as much browsers as possible but at the same time stay very simple. I don’t want a special case for each and every browser.

The plan of attack was: create a large image on every page, and replace this image with the movie whenever it’s clicked. Straight and simple DHTML.

To replace an image, you need to find it (getElementById), find it’s parent (parentNode) and replace it with another element (parentNode.replaceChild(new_element, old_element). Easy enough.

To create a movie object, you need this code (official Apple Quicktime embedding code):

<OBJECT CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
 CODEBASE="http://www.apple.com/qtactivex/qtplugin.cab"
 HEIGHT=yy
 WIDTH=xx>
  <PARAM NAME="src" VALUE="MyMovie.mov" />
  <EMBED SRC="MyMovie.mov"
   HEIGHT=yy WIDTH=xx
   TYPE="video/quicktime"
   PLUGINSPAGE="http://www.apple.com/quicktime/download/" />
</OBJECT>

That doesn’t really look like XHTML to me and it’s not plugin-agnostic either. But it’s a start. I knew from Flash Satay that it’s possible to massage this kind of code into something more sensible. After a few hours, I came to this result: the minimum amount of code you need to get Mozilla-derivates to load a movie into a webpage:

<object data="movie.mp4" type="video/mp4" width="384" height="304" />

Nice and clean. You need to add 16 pixels to the height for the controller bar of the plugin but other than that it’s perfect. But it doesn’t work in Safari. Safari needs a param telling it what the src of the object is, allthough it’s clearly mentioned in the data attribute. So we come to this:

<object data="movie.mp4" type="video/mp4" width="384" height="304">
  <param name="src" value="movie.mp4" />
</object>

Nice thing is, this works in Microsoft Internet Explorer for PC too. But you need to wait until the whole movie has been downloaded until the plugin appears. Of course I already knew this from the Flash Satay article. So I settled on two versions: the one directly above for everyone non MSIE and this one for MSIE:

<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
 codebase="http://www.apple.com/qtactivex/qtplugin.cab"
 width="384" height="304">
  <param name="src" value="movie.mp4" />
</object>

This clearly leaves MSIE users with no other choice but the Quicktime plugin but at least they won’t have to wait ’till the movie has finished downloading. I could try the same trick as Satay by using a quicktime reference movie which loads another one (use a qtsrc parameter on the object to point it to the final movie) but then my viewers would have to click twice before the final movie appears.

Next thing I tried was Internet Explorer for Macintosh. The horror. It needs an <embed...>-tag. Back to square one it seems. So the code for MSIE became:

<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
 codebase="http://www.apple.com/qtactivex/qtplugin.cab"
 width="384" height="304">
  <param name="src" value="movie.mp4" />
  <embed src="movie.mp4"
   height="384" width="304"
   type="video/mp4"
   pluginspace="http://www.apple.com/quicktime/download/" />
</object>

Not XHTML compliant, but somehow I think MSIE users don’t really care.

Next stop: javascript. I’ve never ever written a single javascript in my life. My first try went like this:

function load_player(movie, hsize, vsize) {
  var W3CDOM = (document.createElement && document.getElementById);
  if (!W3CDOM) return;
  var player = document.getElementById('player');
  var p = player.parentNode;
  var el = document.createElement('object');
  el.setAttribute('width', hsize);
  el.setAttribute('height', vsize + 16);
  if (navigator.appName == 'Microsoft Internet Explorer'){
    var prm = document.createElement('param');
    prm.setAttribute('name', 'src');
    prm.setAttribute('value', movie);
    el.appendChild(prm);
    var mbd = document.createElement('embed');
    mbd.setAttribute('src', 'movie');
    mbd.setAttribute('pluginspace', 'http://www.apple.com/quicktime/download');
    mbd.setAttribute('width', hsize);
    mbd.setAttribute('height', vsize + 16);
    el.appendChild(mbd);
  } else {
    el.setAttribute('data', movie);
    el.setAttribute('type', 'video/mp4');
    /* for Safari */
    var prm = document.createElement('param');
    prm.setAttribute('name', 'src');
    prm.setAttribute('value', movie);
    el.appendChild(prm);
  }
  p.replaceChild(el, player);
  el.setAttribute('id', 'player');
}

This thing was meant to be called from on onClick handler on an image with id of "player". The non-MSIE side of the function worked allright, but MSIE took exception to the attachement of the <embed> tag to the <object> (the line that says el.appendChild(mbd)). Whatever I tried, I couldn’t get rid of this. Technically MSIE is right, you cannot have an <embed> tag in an XHTML document but this wasn’t the time nor the place for being pedantic.

Kind of an awkward situation: I need the <embed> tag for MSIE Mac to function, but MSIE (PC and Mac) won’t let me write it into the document. Then it dawned upon me: I needed to inject some dirty HTML in the document and I’m using all kind of good mannered methods. Dirty results call for dirty deeds and the deed called for here has the name element.innerHTML (a Microsoft invention).

The only thing needed was something to put the dirty code into. I put a <div> around the <img> and was able to use this <div>‘s innerHTML to create the necessary object and embed‘s.

I tried to be nice and let the non-MSIE code leave the <div> alone and just replace the <img> but that resulted in a movie plugin and empty space the size of the replaced image. So for now I’m replacing the whole <div> and it seems to work OK.

As a result, the body now has the following structure:

<div id="player">
  <img src=... onClick="load_player('movie.mp4', width, height)" />
</div>

and the Javascript function looks like this:

function load_player(movie, hsize, vsize)
{
  var W3CDOM = (document.createElement && document.getElementById);
  if (!W3CDOM) return;
  /* first do the IE stuff. */
  if (navigator.appName == 'Microsoft Internet Explorer'){
    var player = document.getElementById('player');
    var str = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ';
    str += 'codebase="http://www.apple.com/qtactivex/qtplugin.cab" ';
    str += 'width="' + hsize + '" height="' + (vsize+16) + '">\n';
    str += '<param name="src" value="' + movie + '" />\n';
    str += '<embed src="';
    str += movie;
    str += '" type="video/mp4" pluginspace="http://www.apple.com/quicktime/download" ';
    str += 'width="' + hsize + '" height="' + (vsize+16) + '"></embed>\n';
    str += '</object>';
    player.innerHTML = str;
  } else {
    var player = document.getElementById('player');
    var p = player.parentNode;
    var el = document.createElement('object');
    el.setAttribute('data', movie);
    el.setAttribute('type', 'video/mp4');
    el.setAttribute('width', hsize);
    el.setAttribute('height', vsize+16);
    /* for Safari */
    var prm = document.createElement('param');
    prm.setAttribute('name', 'src');
    prm.setAttribute('value', movie);
    el.appendChild(prm);
    p.replaceChild(el, player);
    el.setAttribute('id', 'player');
  }
}

You can see an example here. Let me know if it works for you, especially if you have a strange combination of browser and movie player plugin (I’m looking at you, lonely linux user).

I heard a rumour somewhere W3C is planning to drop the <img> tag and use <object>‘s instead. Somehow I disagree.