Embedding Flash video accessibly

author: mike foskett uploaded: 22nd September 2008

A simple, unobtrusive, and lightweight method to embed Flash video accessibly into your page. Content in the HTML is replaced with a Flash inset only if both JavaScript and the minimum level of Flash are supported. Multiple Flash insets are also catered for. The method depicted here is a furtherance to that employed by Tesco Direct to deliver their TV adverts online.

The best media player out there is undoubtedly Jeroen Wijering's mediaplayer. Feature laden and skinable though the most accessible is version 3.16. To be honest it needs a tutorial in it's self. I cannot recommend this player highly enough. If you're using anything else, change.

There are many advantages to this solution:

  1. The simplest of installation set-ups.
  2. Tests for the users Flash version, and only inserts if supported.
  3. Unobtrusive. No extra mark-up in the (X)HTML.
  4. Accessible with or with out JavaScript.
  5. Semantic W3C formal grammar, validates even after the Flash is embedded.
  6. Accessible, with or with out Flash.
  7. Very small footprint (2.56 KB).
  8. Multiple Flash insets per page.
  9. A cross-browser, cross-platform solution. Still in testing though.

Caveat: It appears Firefox 3 has an issue playing video with Adobe's Flash Player v9. The recommended fix is to download Adobe's Flash player 10 beta. Rubbish if you ask me.

From an accessibility perspective there are two types of video:

  1. Essential page content. Which requires: An alternate video version(s), a text transcript, and captioning if you're really serious.
  2. A non-essential embellishment to the page content. Which only requires an image, with good alt text, as an alternative.

Both use the same JavaScript method presented but "essential content" has a little more XHTML.

Embedding Flash video example

Here's the example running on a bare bones demo page. There's a zipped complete set of the files and just the JavaScript used. The zipped set includes minified versions. The smallest of which is only 2.56 KB.

I've still to add captioning and audio description options. The download links on the video are for demo only until I find the original AVIs.

How it works

Once the page has loaded the script looks through all the images on the page and makes a list of those with a class name of img2flv. For each it replaces the content of the image container. The <div> in the example. All the variable parameters required are taken from the image and applied to the video player.

The XHTML

The video parameters are taken from the image in the html:


<div>
  <img id="example1" class="img2flv" src="example1.jpg" width="320" height="240" title="" alt="Example 1: Belly dance" />
  <div class="altContent">
    <p>Download <a href="example1.mp4">Belly dance video [MP4]</a> or <a href="example1.txt">Belly dance text transcript [TXT]</a>.</p>
  </div>
</div>

When the video is a non-essential embellishment to the page content you can leave out the altContent div but ensure the image alt text is accurate. Otherwise please ensure there is an MP4 and TXT version of the content available.

The JavaScript

JavaScript constants

Some global constants are required:


var minFlash=8;
var img2flvClass="img2flv";
var flvPlayer="player.swf";
var altContentClass="altContent";

Where:

  • minFlash=8 - Is the minimum version of the Flash player needed to run the video. I use the On2 VP6 codec for better quality and streaming which requires at least v8.
  • img2flvClass="img2flv" - The class name associated to any image which is to be replaced by a FLV video.
  • flvPlayer="player.swf" - The location and name of the FLV player.
  • altContentClass="altContent" - Class of alternate content div to display if Flash or JavaScript is unsupported.

Standard functions

A few of my standard functions to make life easier:


function $id(i){return(document.getElementById(i)?document.getElementById(i):false)}
function idExists(i){return($id(i)?true:false)}
function domFunctions(){return (document.getElementById&&document.getElementsByTagName)}
function isFlash(v){var t=20,i=0;if(navigator.plugins&&navigator.plugins.length){for(var x=0;x<navigator.plugins.length;x++){if(navigator.plugins[x].name.indexOf('Shockwave Flash')!=-1){i=parseInt(navigator.plugins[x].description.split('Shockwave Flash ')[1]);break}}}else if(window.ActiveXObject){for(var x=2;x<=t;x++){try{if(eval("new ActiveXObject('ShockwaveFlash.ShockwaveFlash."+x+"');"))i=x}catch(e){}}}return((i>=v)?i:0)}

/* author: Simon Willisons - http://simon.incutio.com/archive/2004/05/26/addLoadEvent */
function addLoadEvent(f){var o=window.onload;if(typeof window.onload!='function'){window.onload=f}else{window.onload=function(){o();f()}}}

Where:

  • $id(i) - same as writing document.getElementById(i)
  • idExists(i) - Checks an id exists returning true or false.
  • domFunctions() - Checks Dom functions used are available.
  • isFlash(v) - returns the flash version if it's >= v otherwise 0.
  • addLoadEvent(f) - Allows for multiple window.onload events.

Flash media player object creation

Utilises flvplayer.swf v3.16 by Jeroen Wijering.

Requires the image and FLV to have the same name and be in the same location. Pass in the id of the html image. The video properties: width, height, location and name are extracted from it.

The player location is set as a global. The player skin, if used, should be in the same location and named skin.swf


function flashVideo(id){
  var obj=$id(id);
  if (obj.src){
    var image=obj.src;
    $id(id).style.background="url("+image+") no-repeat";
    var f=image.substring(0,image.lastIndexOf("."))
    var video=f+".flv";
    var skin=flvPlayer.substring(0,flvPlayer.lastIndexOf("/"))+"skin.swf"
    var appWidth=obj.width;
    var appHeight=obj.height;

    // alternative content
    var download=f+".mp4";
    var altText=obj.alt;
    var alt=$id(id).nextSibling.nextSibling;
    var altContent=(alt && alt.className==altContentClass)?alt.innerHTML:"";

    var parent=obj.parentNode;
    if (video && image && parent && appWidth && appHeight && flvPlayer && isFlash(minFlash)){
The Flash variables

      var flvVars='file='+video;
      flvVars+='&displayheight='+appHeight;
      flvVars+='&displaywidth='+appWidth;
      flvVars+='&image='+image;
      flvVars+='&autostart=false';
      flvVars+='&showdigits=false';
      flvVars+='&controlbar=over'; // over/bottom/none
      flvVars+='&screencolor=0xffffff';
      flvVars+='&backcolor=0x261D32';
      flvVars+='&frontcolor=0xFFAB23';
      flvVars+='&lightcolor=0xffffff';
      flvVars+='&showdownload=true';
      flvVars+='&link='+download;
      flvVars+='&showfsbutton=true';
The Flash object

      var str='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version='+minFlash+',0,0,0" width="'+appWidth+'" height="'+appHeight+'">';
      str+='<param name="movie" value="'+flvPlayer+'" />';
      str+='<param name="allowfullscreen" value="true" />';
      str+='<param name="flvQuality" value="high" />';
      str+='<param name="flashvars" value="'+flvVars+'" />';
      str+='<param name="wmode" value="transparent" />';
      str+='<!--[if !IE]> <-->';
      str+='<object data="'+flvPlayer+'" width="'+appWidth+'" height="'+appHeight+'" type="application/x-shockwave-flash">';
      str+='<param name="allowfullscreen" value="true" />';
      str+='<param name="flvQuality" value="high" />';
      str+='<param name="flashvars" value="'+flvVars+'" />';
      str+='<param name="wmode" value="transparent" />';
      str+='</object>';
      str+='<!-->';
      str+='<p>'+altText+'</p>';
// place here if alternative content only required for accessibility
//      str+=altContent;
      str+='<![endif]-->';
      str+='</object>';
// place here if alternative content always required (recommended)
      str+=altContent;
Display alternate content if Flash unavailable then insert new content and close

    }else{
      // no flash, offer alternatives if available.
      var str=parent.innerHTML;
    }
    parent.innerHTML=str;
  }
}

Find the image(s) to replace

A generic image replacement function. It works either specifically by id name or generically by class name:


/* Parameters:
  id for specific object replacement,
  or class name for generic replacements,
  Flash object build function
*/
function img2flash(id,classN,func){
  if (idExists(id) && $id(id).className==classN){
    func(id)
  }else{
    var imgs=document.getElementsByTagName('img');
    var imgIDs=new Array();
    for (var i=0;i<imgs.length;i++){
      if (imgs[i].className==classN){
        imgIDs[i]=imgs[i].id}
      }
    for (var i=0;i<imgIDs.length;i++){
      func(imgIDs[i])
    }
  }
}

Note that if a valid id is passed in it only converts that specific image id, While if the id is null then it looks for each image by it's class name.

Setting it all up:

If the required Dom functions are supported then run setup on page load.


function setup(){
  img2flash('',img2flvClass,flashVideo)
}

/* test Dom functions used are supported */
if (domFunctions()){
  addLoadEvent(setup)
}

Hiding the alternate content

Best practice states the alternate formats should always be visible.

Only if you really must hide the alternate versions, unless JavaScript or Flash are unsupported, then do it in this manner.

In the flashVideo function change the commented lines so:


// place here if only required for accessibility
      str+=altContent;
      str+='<![endif]-->';
      str+='</object>';
// place here if always required (recommended)
//    str+=altContent;

Further reading