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/smiand thewindows/wmvfiles
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 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