Embedded Web Video in a Standards-Compliant, Accessible, and Successful Way

The word “Successful” in the title, when juxtaposed with “Standards-Compliant” and “Accessible,” should be big, bold and flashing (except that the flashing style would then go against web accessibility best practice). The goal is to embed a video clip into a web page that validates as “XHTML4.01 Transitional”, includes a Closed Captioning text track to be displayed in the web page, and could be viewed in one of three flavors: Windows Media, QuickTime, and Real. And the content being presented is about using accessible technologies in the classroom, so it had to be “right.” This task was much harder than I thought, and I’ll offer much harder than it should have been. Piecing together sources too numerous to mention, I managed to make it happen … with just a few caveats. Here, documented for all time, or at least until dltj.org goes away or the next major browser/streaming-client revision (which ever comes first) is how it can be done.

The Content


I hope someday soon to actually post a link to this content online where you can see at least the public face of this solution at play. The content is for a series of web-based modules for faculty and administrators to help improve the quality of education for students with disabilities. It is top-notch stuff, and it has been a real honor to work with the team that put it together. For my piece of it, though, they gave me a set of directories, broken out by media type, with this content:

Windows/
.wmv (the video) and .smi (the closed caption text track)
Real/
.rm (the video), .rt (the closed caption text track) and .smi (the container that pulls the first two together)
Quicktime/
.mov (the video)

Now right out of the gate we’re fudging things a little bit. For whatever reason the team developing the content decided to punt when it came to the text track in Quicktime. I don’t know if there was an insurmountable barrier or some other reason, but the practical upshot is that the Quicktime version of the video includes the text track an “open caption” encoded right into the video stream. That makes Quicktime, from my perspective, very easy to deal with.

Those with a keen eye will notice that both the Windows Media and the Real Media version have a .smi file. This is true, but they are not identical. Windows media wants to use a .asx file as its container object and only uses the .smi for the text stream. The Real Media version has the text stream in .rt files. Could these be combined? Perhaps, but I’ve got real-world problems to solve and this is what I was given. In any case, I don’t think it makes the end result any easier or harder than it would be otherwise.

Serving Up the Content


We use a Helix Streaming Media server from Real Networks, Inc., as our content server, but that isn’t the really interesting part of this quest. Another requirement, was that the URLs to these various media files needed to be persistent, is an interesting problem. The HTML that will include the URLs could be copied and distributed to hundreds if not thousands of sites eventually, so updating the URLs in web pages because a machine name changed or we decide to use a Darwin Streaming Server rather than a Helix Streaming Server for Quicktime would be impossible to get done right. OhioLINK (my employer), being a content provider for member libraries, created a persistent URL service years ago for its content called “RAVE” (or Random Access to Virtually Everything), so it made sense to have the URLs to the media pieces be “RAVE URLs”. RAVE URLs are handled by PERL scripts running on an Apache virtual host called “rave.ohiolink.edu”. These PERL scripts read the parameters of the request and issue an HTTP 302 (“Moved Temporarily”) redirect to the real location of the content. In doing so, we only need to change the address of the content in one place — the RAVE script — when the location moves (rather than in the hundreds of web pages or bookmarks or what-have-you).

For the purposes of this project, these RAVE URLs were defined as this (the http protocol prefix is omitted so over-anxious RSS readers will not attempt to turn them into hyperlinks; “blah” is a placeholder for the project name; and [id] represents the identifier for the video segment):

rave.ohiolink.edu/dmc/blah/quicktime/[id]
Redirects to the location of the Quicktime file (a hinted .mov file, so it starts playing in the browser right away)
rave.ohiolink.edu/dmc/blah/real/[id]
Redirects to the location of the .smi container file via the mms protocol through the Helix streaming server
rave.ohiolink.edu/dmc/blah/windows/wmv/[id]
Redirects to the location of the wmv video file via the mms protocol through the Helix streaming server
rave.ohiolink.edu/dmc/blah/windows/smi/[id]
Pulls the .smi file off of the streaming server and sends it back to the browser with the appropriate MIME type
rave.ohiolink.edu/dmc/blah/windows/asx/[id]
Dynamically creates a .asx container that pulls together the windows/smi and the windows/wmv files

Here are the first of the tricky bits. First, it seems like Windows Media Player can’t cope with a 302 redirect message in response to its request for a .smi file. “Can’t cope” can be restated as “ignores”; it doesn’t throw an error, it just plays the file as if there was no closed captioning text. To get around this, the RAVE script has a bit of PERL where it asks like a web client to pull the .smi file off of the video server and send it back out to the media player.

Note — the Perl script included below was updated in February 2007 to include a fix for the deprecation of the MMS protocol in Windows Media Player.

Second — again for those with eagle eyes — is the dynamic generation of an .asx file. As it turns out, one can relate a .smi text-track file with a .wmv video file by including it as a SAMI parameter on the URL. By far the best practice for doing this seems to be to relate the .smi and the .wmv files in a .asx container file. So we create one on-the-fly. The PERL code looks like this:

#!/usr/bin/perl -w
# Copyright (C) 2006 OhioLINK
#
# This file is part of the OhioLINK Digital Resource Commons (DRC) Project.
#
# The OhioLINK DRC is free software; you can redistribute it and/or
# modify it under the terms of the Affero General Public License as
# published by Affero, Inc. -- either version 1 of the License, or
# (at your option) any later version.
#
# The OhioLINK DRC Project is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY -- without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# Affero General Public License for more details.
#
# You should have received a copy of the Affero General Public
# License in the LICENSE.txt file that comes with the DRC project;
# if not, write to DRC Development Team, OhioLINK, 2455 North Star Rd, 
# Suite 300, Columbus, OH 43221, USA.
 
if (!$ENV{PATH_INFO}) {
  print "Location: http://no-path-info-given/\r\n\r\n";
} else {
  ($format,$id) = $ENV{PATH_INFO} =~ q#^/+(.*?)/(.*)$#;
 
  if ($format =~ /Quicktime/i) {
    print "Location: http://video.ohiolink.edu/blah/Quicktime/$id\r\n\r\n";
  } elsif ($format =~ /Real/i) {
    print "Location: http://video.ohiolink.edu:8080/ramgen/blah/Real/$id\r\n\r\n"; 
  } elsif ($format =~ /Windows/i) {
    ($winType,$winFile) = $id =~ q#^(.*?)/(.*)$#;
    if ($winType =~ /asx/i) {
      $winFile =~ s/\..*$//;
      print >> "EoMarkup";
Content-type: video/x-ms-asf
 
<asx version="3.0">
<copyright>(c) 2005 - xxx</copyright>
<entry>
<ref href="mms://video.ohiolink.edu/blah/Windows/$winFile.wmv?SAMI=http://rave.ohiolink.edu/dmc/blah/windows/smi/$winFile.smi">
<copyright>(c) 2005 - xxx</copyright>
</ref></entry>
</asx> 
EoMarkup
    } elsif ($winType =~ /wmv/i) {
      print "Location: mms://video.ohiolink.edu/blah/Windows/$winFile\r\n\r\n";
    } elsif ($winType =~ /smi/i) {
      use LWP::UserAgent;
      $ua = LWP::UserAgent->new;
      $ua->agent("$0-lwp/0.1 " . $ua->agent);
      $req = HTTP::Request->new(GET => "http://video.ohiolink.edu:8080/blah/Windows/$winFile");
      # send request
      $res = $ua->request($req);
      # check the outcome
      if ($res->is_success) {
        print "Content-type: application/smil\r\n\r\n";
        print $res->decoded_content;
      } else {
        print "Location: http://error-from-remote-server/$winType/$winFile/".$res->code."/".$res->message."\r\n\r\n";
      }
    } else {
      print "Location: http://invalid-Windows-format-given/$winType/$winFile\r\n\r\n";
    }
  } else {
    print "Location: http://invalid-format-given/$format/$id\r\n\r\n";
  }
}

The first part of the script parses the PATH_INFO variable — everything left over from the blah script name — and if what is requested is the Real Media or the Quicktime version of the video it sends the redirect back to the client (in the form of the “Location:” header with Apache HTTP daemon handling the 302 response code). If what is requested is the Windows variety, it further parses the PATH_INFO piece for the file type and acts according to that: for wmv, it redirects; for asx, it generates the .asx file; and for .smi, it pulls the content off the video server and returns it to the client.

The Markup


So now that we can serve up the video content with persistent URLs, we need to tackle the HTML markup. As a general rule, we can’t use the popular yet non-standard <embed> tag; since it is not part of any formal specification, validation will always fail. As a consequence, use in future browsers is less assured. So we need to use <object> — and unfortunately due to varied, if not outright broken implementations of the <object> tag, we need to be a little tricky in how we code it. Other tricks are in play here as well — check the list of “Resources Consulted” below for more information.

Also note that many of the recommendations out there start off with something like “if you have media to show, don’t embed it into your web page; but if you must, here is one way to do it.” The reason for this is pretty simple, if not obvious after a little thought: folks using alternate browsing/computing techniques (e.g. keyboard only for limited mobility, auditory only for sight impaired, visual only for the hearing impaired, etc.) lose much of their ability to control the flow of information because it is tied up in the functionally-limited browser rather than the native media player. These same recommendations go on to say that if you do embed the media into the web page, provide a link for the user to access it using the native media player.

So based on the URLs to the media objects described above, this is what the HTML markup looks like:

Quicktime



<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" width="320" height="380">
  <param name="src" value="http://rave.ohiolink.edu/dmc/blah/quicktime/[id].mov" />
  <param name="controller" value="true" />
  <param name="autoplay" value="true" />
  <!--[if !IE]>-->
    </object><object type="video/quicktime" data="http://rave.ohiolink.edu/dmc/blah/quicktime/[id].mov" width="320" height="380">
     <param name="autoplay" value="true" />
     <param name="controller" value="true" />
    </object>
   <!--<![endif]-->
 
&lt;br /&gt;
<a href="http://rave.ohiolink.edu/dmc/blah/quicktime/[id].mov" title="View in Native Player">View Video with External Player</a>

Windows Media



<object classid="clsid:6bf52a52-394a-11d3-b153-00c04f79faa6" type="application/x-oleobject" width="320" height="304" id="Player1">
  <param name="url" value="http://rave.ohiolink.edu/dmc/blah/windows/asx/[id].asx" />
  <param name="src" value="http://rave.ohiolink.edu/dmc/blah/windows/asx/[id].asx" />
  <param name="autostart" value="true" />
  <param name="showcontrols" value="true" />
  <param name="uimode" value="mini" />
  <param name="captioningID" value="CapText" />
  <param name="SAMIFileName" value="http://rave.ohiolink.edu/dmc/blah/windows/smi/scott.smi" />
    <!--[if !IE]>-->
     </object><object type="video/x-ms-wmv" data="http://rave.ohiolink.edu/dmc/blah/windows/asx/[id].asx" width="320" height="304" id="Player2">
     <param name="src" value="http://rave.ohiolink.edu/dmc/blah/windows/asx/[id].asx" />
     <param name="autostart" value="true" />
     <param name="showcontrols" value="true" />
     <param name="uimode" value="mini" />
     <param name="captioningID" value="CapText" />
     <param name="SAMIFileName" value="http://rave.ohiolink.edu/dmc/blah/Windows/smi/[id].smi" />
    </object>
   <!--!<[endif]-->
 
&lt;br /&gt;
<a href="http://rave.ohiolink.edu/dmc/blah/windows/asx/[id].asx" title="View in Native Player">View Video with External Player (including captions)</a>

Real Media


One last caveat. As if the shenanigans above weren’t bad enough, apparently as part of a court settlement against Microsoft a number of years ago, there was an injunction placed against the creator of Internet Explorer that prevented that browser from automatically launching content embedded in a web page. For whatever reason (I didn’t have time to investigate why this happened), it only impacts Real Media content. Somehow, though, it is okay for the browser to start it if the action is called from an external piece of JavaScript. Go figure. You can read more about it at Real Media’s support site for Web Page Embedding > Internet Explorer Changes. In actuality, though, it makes the Real Media part of the equation pretty easy:

<script language="JavaScript" type="text/javascript">
  AC_RunRealContent( 
	"id", "player",  
	"width", "320",  
	"height", "280",  
	"CONTROLS", "imagewindow",  
	"CONSOLE", "radio",  
	"AUTOSTART", "TRUE",  
	"type", "audio/x-pn-realaudio-plugin",  
	"SRC", "http://rave.ohiolink.edu/dmc/blah/real/[id].smi"
  );
  window.document.write('&lt;br /&gt;');
  AC_RunRealContent( 
	"id", "controls",  
	"width", "320",  
	"height", "36",  
	"CONTROLS", "ControlPanel",  
	"CONSOLE", "radio", 
	"type", "audio/x-pn-realaudio-plugin",  
	"SRC", "http://rave.ohiolink.edu/dmc/blah/real/[id].smi"
  );
</script>
&lt;br /&gt;
<span class="removed_link" title="http://rave.ohiolink.edu/dmc/fame/Real/scott.smi">View Video with External Player (including captions)</span>

Conclusion

  1. It seems to work for me and the machines I test with; your mileage may vary.
  2. If you know of a better way to do it, please let me know!

Resources Consulted


Here is a short list of the most helpful resources used to accomplish this feat:

(This post was updated on 21-Aug-2013.)