Added support for the HTML5 DFXP player
authorPhilippe Le Hégaret <plh@w3.org>
Thu, 11 Dec 2008 21:04:36 +0000
changeset 52 28316b676e53
parent 51 f4c4fc72021d
child 53 60363aaefa36
Added support for the HTML5 DFXP player
testsuite/web-framework/HTML5_player.js
testsuite/web-framework/START.html
testsuite/web-framework/style.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/testsuite/web-framework/HTML5_player.js	Thu Dec 11 21:04:36 2008 +0000
@@ -0,0 +1,495 @@
+/*
+ * Based on jQuery srt
+   http://v2v.cc/~j/jquery.srt/
+
+  usage:
+    <video src="example.movie" id="video" controls>
+      <text lang='en' type="application/ttaf+xml" src="testsuite/Content/Br001.xml"></text>
+    </video>
+
+  license:
+    You can do whatever you want with this code, except for the playSRT function
+    See the jQuery srt license for playSRT function at
+      http://v2v.cc/~j/jquery.srt/
+*/
+
+if (typeof XMLHttpRequest == "undefined" ) {
+    // Provide the XMLHttpRequest class for IE 5.x-6.x:
+    XMLHttpRequest = function() {
+	try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
+	try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
+	try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
+	try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
+	throw new Error( "This browser does not support XMLHttpRequest." )
+    };
+}
+
+function HTML5Caption() {
+}
+
+HTML5Caption_toSeconds = function(t) {
+    var s = 0.0;
+    if(t) {
+	var p = t.split(':');
+	for(i=0;i<p.length;i++)
+	    s = s * 60 + parseFloat(p[i].replace(',', '.'));
+    }
+    return s;
+}
+
+HTML5Caption_strip = function(s) {
+    return s.replace(/^\s+|\s+$/g,"");
+}
+
+HTML5Caption_playSRT = function(video, srt) {    
+
+    srt = srt.replace('\r\n|\r|\n', '\n');
+	
+    var subtitles = {};
+    srt = HTML5Caption_strip(srt);
+    var srt_ = srt.split('\n\n');
+    for(s in srt_) {
+	st = srt_[s].split('\n');
+	if(st.length >=2) {
+	    n = st[0];
+	    i = strip(st[1].split(' --> ')[0]);
+	    o = strip(st[1].split(' --> ')[1]);
+	    t = st[2];
+	    if(st.length > 2) {
+		for(j=3; j<st.length;j++)
+		    t += '\n'+st[j];
+	    }
+	    is = HTML5Caption_toSeconds(i);
+	    os = HTML5Caption_toSeconds(o);
+	    subtitles[is] = {i:is, o: os, t: t};
+	}
+    }
+    var currentSubtitle = -1;
+
+    // create the subtitle area
+    var div = document.createElement("div");
+    div.className = 'srt';
+    video.parentNode.insertBefore(div, video.nextSibling);
+
+    var currentTime = video.currentTime;
+
+    if (typeof currentTime == "undefined") {
+	throw new Error("currentTime is not supported by the Video element");
+    } else {
+	var ival = setInterval(function() {
+		var currentTime = video.currentTime;
+		var subtitle = -1;
+		for (s in subtitles) {
+		    if (s > currentTime)
+			break;
+		    subtitle = s;
+		}
+		if (subtitle != -1) {
+		    if (subtitle != currentSubtitle) {
+			div.innerHTML = subtitles[subtitle].t;
+			currentSubtitle=subtitle;
+		    } else if (subtitles[subtitle].o < currentTime) {
+			div.innerHTML = '';
+		    }
+		}
+	    }, 100);
+    }
+}
+
+var DFXP = "http://www.w3.org/2006/10/ttaf1";
+var DFXP_TTS = "http://www.w3.org/2006/10/ttaf1#style";
+
+HTML5Caption_transDFXPAttributes = function(dp, ht) {
+    var v;
+    v = dp.getAttribute("style");
+    if (v != null) {
+	// @@TODO v is an IDREFS!
+	var el = dp.ownerDocument.getElementById(v);
+	
+	if (el == null) {
+	    // getElementById doesn't work, let's try something else
+	    var styles = dp.ownerDocument.getElementsByTagNameNS(DFXP, "style");
+
+	    for (var i = 0; i < styles.length; i++) {
+		var s = styles.item(i);
+		var id = s.getAttribute("xml:id");
+		if (id == v) {
+		    el = s;
+		    break;
+		}
+	    }
+	}
+	if (el != null) {
+	    HTML5Caption_transDFXPAttributes(el, ht);	    
+	} else {
+	    alert("can't find " + v);
+	}
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "backgroundColor");
+    if (v != "") {
+	ht.style.setProperty("background-color", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "color");
+    if (v != "") {
+	ht.style.setProperty("color", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "direction");
+    if (v != "") {
+	ht.style.setProperty("direction", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "display");
+    if (v != "") {
+	// doesn't work with auto value
+	ht.style.setProperty("display", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "fontFamily");
+    if (v != "") {
+	ht.style.setProperty("font-family", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "fontSize");
+    if (v != "") {
+	ht.style.setProperty("font-size", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "fontStyle");
+    if (v != "") {
+	ht.style.setProperty("font-style", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "fontWeight");
+    if (v != "") {
+	ht.style.setProperty("font-weight", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "lineHeight");
+    if (v != "") {
+	ht.style.setProperty("line-height", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "opacity");
+    if (v != "") {
+	ht.style.setProperty("opacity", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "padding");
+    if (v != "") {
+	ht.style.setProperty("padding", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "textAlign");
+    if (v != "") {
+	if (v != "start" && v != "end") {
+	    ht.style.setProperty("text-align", v, "");
+	}
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "textDecoration");
+    if (v != "") {
+	if (v == "noUnderline" || v == "noOverline" || v == "noLineThrough") {
+	    // this is not accurate but trying the best here ...
+	    v = "none";
+	} else if (v == "lineThrough") {
+	    v = "line-through";
+	}
+	ht.style.setProperty("text-decoration", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "unicodeBidi");
+    if (v != "") {
+	if (v == "bidiOverride") {
+	    v = "bidy-override";
+	}
+	ht.style.setProperty("unicode-bidi", v, "");
+    }
+    v = dp.getAttributeNS(DFXP_TTS, "visibility");
+    if (v != "") {
+	ht.style.setProperty("visibility", v, "");
+    }
+    v = dp.getAttribute("xml:space");
+    if (v != "") {
+	if (v == "preserve") {
+	    v = "pre";
+	    ht.spaces = true;
+	} else {
+	    v = "normal";
+	}
+	ht.style.setProperty("white-space", v, "");
+    }
+}
+
+HTML5Caption_transDFXPElement = function(dp) {
+    if (dp != null) {	
+	if (dp.nodeType == 3) {
+	    return document.createTextNode(dp.data);
+	} else if (dp.nodeType == 1) {
+	    var n = null;
+	    if (dp.localName == "span") {
+		n = document.createElement("span");
+	    } else if (dp.localName == "p") {
+		n = document.createElement("p");
+	    } else if (dp.localName == "br") {
+		n = document.createElement("br");
+	    }
+	    if (n!= null) {
+		HTML5Caption_transDFXPAttributes(dp, n);
+		var child = dp.firstChild;
+		while (child!= null) {
+		    var r = HTML5Caption_transDFXPElement(child);
+		    if (r!= null) {
+			n.appendChild(r);
+		    }
+		    child=child.nextSibling;
+		}
+	    }
+	    return n;
+	}
+    }
+    return null;
+}
+
+HTML5Caption_convertDFXPDuration = function(d) {
+    var i = 0;
+    if (d.indexOf(':') != -1) {
+	return HTML5Caption_toSeconds(d);
+    } else if ((i = d.indexOf('h')) != -1) {
+	return parseFloat(d.substring(0, i)) * 3600;
+    } else if ((i = d.indexOf('m')) != -1) {
+	return parseFloat(d.substring(0, i)) * 60;
+    } else if ((i = d.indexOf('s')) != -1) {
+	return parseFloat(d.substring(0, i));
+    } else if ((i = d.indexOf('ms')) != -1) {
+	return parseFloat(d.substring(0, i)) / 1000;
+    } else if ((i = d.indexOf('t')) != -1) {
+	throw new Error("tick duration is not supported");
+    } else if ((i = d.indexOf('f')) != -1) {
+	throw new Error("frame duration is not supported");
+    }
+}
+
+HTML5Caption_ParentStyle = function(parent, ht) {
+    if (parent != null) {
+	if (parent.parentNode == null) {
+	    // nothing
+	} else if (parent.parentNode.localName == "div") {
+	    HTML5Caption_ParentStyle(parent.parentNode, ht);
+	} else if (parent.parentNode.localName == "body") {
+	    HTML5Caption_ParentStyle(parent.parentNode, ht);
+	} else if (parent.parentNode.localName == "tt") {
+	    HTML5Caption_ParentStyle(parent.parentNode, ht);
+	}
+	HTML5Caption_transDFXPAttributes(parent, ht);
+    }
+
+}
+
+HTML5Caption_playDFXP = function(video, dfxp) {    
+
+    // @@grabing all paragraph elements. ugly but effective so far...
+    var paras = dfxp.getElementsByTagNameNS(DFXP, "p");
+
+    if (paras.length == 0) {
+	alert("No paragraph in DFXP?!?");
+	return;
+    }
+
+    // create the subtitle area
+    // mainDiv is here to prevent the DFXP style from messing up with
+    // with the main container. mainDiv represents the body
+    // element of DFXP
+    var mainDiv = document.createElement("div");
+    mainDiv.className = 'dfxp';
+    var w = video.getAttribute("width");
+    if (w!="") {
+	// the main container gets the side of the video
+	mainDiv.style.setProperty("width", w, "");
+    }
+    var div = document.createElement("div");
+    mainDiv.appendChild(div);
+    var empty = document.createElement("p");
+    var old = empty;
+    div.appendChild(old);
+
+    HTML5Caption_ParentStyle(paras.item(0).parentNode, div);
+    video.parentNode.insertBefore(mainDiv, video.nextSibling);
+
+    // initialize the captions
+    var subtitles = {};
+    for (var i = 0; i < paras.length; i++) {
+	var p = paras.item(i);
+	var begin=p.getAttribute("begin");
+	var end=p.getAttribute("end");
+	var dur=p.getAttribute("dur");
+	var element = HTML5Caption_transDFXPElement(p);
+	if (div.spaces && div.spaces == true) {
+	    // work around the inheritance ?
+	    // cf tt002.xml
+	    element.style.setProperty("white-space", "pre", "");
+	}
+	if (begin != null && end != null) {
+	    sbegin=HTML5Caption_convertDFXPDuration(begin);
+	    send=HTML5Caption_convertDFXPDuration(end);
+	    subtitles[sbegin] = {i:sbegin, o: send, element: element};	
+	} else if (begin != null && dur != null) {
+	    sbegin=HTML5Caption_convertDFXPDuration(begin);
+	    send=sbegin + HTML5Caption_convertDFXPDuration(dur);
+	    subtitles[sbegin] = {i:sbegin, o: send, element: element};	
+	} else if (begin != null) {
+	    sbegin=HTML5Caption_convertDFXPDuration(begin);
+	    var next = paras.item(i+1);
+	    var send = 10000000;
+	    if (next != 0) {
+		var nbegin=next.getAttribute("begin");
+		if (nbegin!= null) {
+		    send= HTML5Caption_convertDFXPDuration(nbegin);
+		}
+	    }
+	    subtitles[sbegin] = {i:sbegin, o: send, element: element};	
+	}
+    }
+
+    var currentSubtitle = -1;
+
+    var currentTime = video.currentTime;
+
+    if (typeof currentTime == "undefined") {
+	throw new Error("currentTime is not supported by the Video element");
+    } else {
+	setInterval(function() {
+		var currentTime = video.currentTime;
+		if (!video.paused) {
+		    var subtitle = -1;
+		    for (s in subtitles) {
+			if (s > currentTime)
+			    break;
+			subtitle = s;
+		    }
+		    if (subtitle != -1) {
+			if (subtitle != currentSubtitle) {
+			    div.replaceChild(subtitles[subtitle].element, old);
+			    old = subtitles[subtitle].element;
+			    currentSubtitle=subtitle;
+			} else if (subtitles[subtitle].o < currentTime) {
+			    div.replaceChild(empty, old);
+			    old = empty;
+			}
+		    }
+		}
+	    }, 100);
+    }
+}
+
+
+HTML5Caption_playVideo = function(video, caption) {
+    var xhr = new XMLHttpRequest();
+    var type = caption.getAttribute("type");
+    
+    if (type == "application/ttaf+xml") {
+	xhr.onreadystatechange = function () {
+	    if (this.readyState == 4
+		&& this.status == 200) {
+		
+		if (this.responseXML != null) {
+		    HTML5Caption_playDFXP(this.video, this.responseXML);
+		} else {
+		    throw new Error("Can't read DFXP resource");
+		}
+	    }
+	};
+    } else if (type == "text/x-srt") {
+	xhr.onreadystatechange = function () {
+	    if (this.readyState == 4
+		&& this.status == 200) {
+		if (this.responseText != null) {
+		    // success!
+		    HTML5Caption_playSRT(this.video, this.responseText);
+		} else {
+		    throw new Error("Can't read DFXP resource");
+		}
+	    }
+	};
+    } else {
+	throw new Error("Caption format not supported");
+    }
+    xhr.video = video;
+
+    xhr.open("GET", caption.getAttribute("src"), true);
+    xhr.send("");
+}
+
+
+
+
+function init_captions() {
+    var textElements = document.getElementsByTagName("text");
+    var srtElement = null;
+    var dfxpElement = null;
+
+    for (var i = 0; i < textElements.length; i++) {
+	var e = textElements.item(i);
+	var type = e.getAttribute("type");
+	// @@ should take into account @lang
+	if (type == "application/ttaf+xml" && e.getAttribute("src")) {
+	    dfxpElement = e;
+	} else if (type == "text/x-srt" && e.getAttribute("src")) {
+	    srtElement = e;
+	}
+    }
+
+    if (dfxpElement != null) {
+	HTML5Caption_playVideo(dfxpElement.parentNode, dfxpElement);
+    } else if (srtElement != null) {
+	HTML5Caption_player.playVideo(srtElement.parentNode, srtElement);
+    }
+}
+
+
+// The name of your player.
+
+HTML5Caption.prototype.name = function () {
+  return "HTML5 DFXP Player prototype";
+}
+
+HTML5Caption.prototype.startPlayer = function() {
+    if (-1 == navigator.userAgent.indexOf("Firefox/3.1")) {
+	alert("This player only works in Firefox 3.1.");
+    }
+}
+
+var HTML5Caption_video = null;
+
+HTML5Caption.prototype.startTest = function(test_number, filename, autostart, div) {
+
+	div.innerHTML = '';
+	// Create the object
+	var obj = document.createElement("video");
+	obj.setAttribute("width", "320px");
+	obj.setAttribute("src", "dfxp_movie.ogv");
+	obj.setAttribute("controls", "true");
+	if (autostart) {
+	    obj.setAttribute("autoplay", "true");
+	}
+	
+	// append the object
+	div.appendChild(obj);
+
+	var xhr = new XMLHttpRequest();
+
+	xhr.onreadystatechange = function () {
+	    if (this.readyState == 4
+		&& this.status == 200) {
+		
+		if (this.responseXML != null) {
+		    HTML5Caption_playDFXP(this.video, this.responseXML);
+		} else {
+		    throw new Error("Can't read DFXP resource");
+		}
+	    }
+	};
+	xhr.video = obj;
+
+	xhr.open("GET", filename, true);
+	xhr.send("");
+}
+
+HTML5Caption.prototype.stopTest = function(test_number)
+{
+}
+
+HTML5Caption.prototype.stopPlayer = function()
+{
+}
+    
+addPlayer(new HTML5Caption());
+
--- a/testsuite/web-framework/START.html	Thu Dec 11 20:51:48 2008 +0000
+++ b/testsuite/web-framework/START.html	Thu Dec 11 21:04:36 2008 +0000
@@ -9,6 +9,7 @@
     <script type="text/javascript" src='tests.js'></script>
     <script type="text/javascript" src='NCAM.js'></script>
     <script type="text/javascript" src='JWP.js'></script>
+    <script type="text/javascript" src='HTML5_player.js'></script>
     <script type="text/javascript" src='sample_player.js'></script>
   </head>
   <body onload='init();'>
--- a/testsuite/web-framework/style.css	Thu Dec 11 20:51:48 2008 +0000
+++ b/testsuite/web-framework/style.css	Thu Dec 11 21:04:36 2008 +0000
@@ -54,6 +54,20 @@
                text-align: left;
  }
 
+/* For the HTML5 player */
+div.dfxp { 
+   background-color: black;
+   color: white;
+   padding: 0;
+   margin: 0;
+   min-height: 6ex;
+   font-size: 90%;
+   text-align: center;
+}
+div.dfxp div, div.dfxp p, div.dfxp span {
+   margin: 0;
+   padding: 0;
+}
 
 /* table for tests in the left area */
 div#tables th { border: none;}