New timing computation algorithm: calculate specified, relative and active time
authorPhilippe Le Hégaret <plh@w3.org>
Mon, 26 Jan 2009 17:32:08 +0000
changeset 79 5e1789a83ea1
parent 78 30574776c59e
child 80 0cdac6d1c9c4
New timing computation algorithm: calculate specified, relative and active time
Handle nested elements
Fixed HTML5Caption_toSeconds to take into account frames
Added support for the XHTML style attribute
Fixed bidi-override
Changed style namespace to follow the DFXP test suite
Now transforming into XHTML instead of HTML
Added a debug mode
testsuite/web-framework/HTML5_player.js
--- a/testsuite/web-framework/HTML5_player.js	Wed Jan 21 20:20:16 2009 +0000
+++ b/testsuite/web-framework/HTML5_player.js	Mon Jan 26 17:32:08 2009 +0000
@@ -24,12 +24,39 @@
     };
 }
 
+var DFXP_NS = "http://www.w3.org/2006/10/ttaf1";
+var XHTML_NS = "http://www.w3.org/1999/xhtml";
+var DFXP_NS_Parameter = "http://www.w3.org/2006/10/ttaf1#parameter";
+var DFXP_NS_Style = "http://www.w3.org/2006/10/ttaf1#styling";
+var DFXP_NS_Style_Extensions = "http://www.w3.org/2006/10/ttaf1#style-extension";
+var DFXP_NS_Metadata = "http://www.w3.org/2006/10/ttaf1#metadata";
+var DFXP_NS_Metadata_Extensions = "http://www.w3.org/2006/10/ttaf1#metadata-extension";
+
+var DFXP_TIME_CONTAINER_PAR = 1;
+var DFXP_TIME_CONTAINER_SEQ = 2;
+
+var HTML5Caption_debug = false;
+
 HTML5Caption_toSeconds = function(t) {
     var s = 0.0;
-    if(t) {
+    if (t) {
 	var p = t.split(':');
-	for(i=0;i<p.length;i++)
-	    s = s * 60 + parseFloat(p[i].replace(',', '.'));
+	
+	switch (p.length) {
+	case 0:
+	case 1:
+	case 2:
+	    break;
+	case 3:
+	    for (var i=0; i < 3; i++)
+		s = s * 60 + parseFloat(p[i].replace(',', '.'));
+	    break;
+	case 4:
+	    for (var i=0; i < 3; i++)
+		s = s * 60 + parseFloat(p[i].replace(',', '.'));
+	    // @@ ignore frames
+	    break;
+	}
     }
     return s;
 }
@@ -93,99 +120,111 @@
     }
 }
 
-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) {
+HTML5Caption_convertDFXP2HTMLAttributes = function(dfxpElement, htmlElement) {
     var v;
-    v = dp.getAttribute("style");
+    
+    // that's a little extension of my own to support the style
+    // attribute like (x)HTML
+    v = dfxpElement.getAttributeNS(XHTML_NS, "style");
+    if (v != "") {
+	htmlElement.style.cssText = v;
+    }
 
-    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;
+    v = dfxpElement.getAttribute("style");
+
+    if (v != null && v != "") {
+	var p = v.split(' ');
+	switch (p.length) {
+	case 1:
+	    var dfxpElementRef = dfxpElement.ownerDocument.getElementById(v);
+	    
+	    if (dfxpElementRef == null) {
+		// getElementById doesn't work, let's try something else
+		var styles = dfxpElement.ownerDocument.getElementsByTagNameNS(DFXP_NS, "style");
+
+		for (var i = 0; i < styles.length; i++) {
+		    var s = styles.item(i);
+		    var id = s.getAttribute("xml:id");
+		    if (id == v) {
+			dfxpElementRef = s;
+			break;
+		    }
 		}
 	    }
+	    break;
+	default:
+	    if (HTML5Caption_debug) alert("@@TODO IDREFS");
 	}
-	if (el != null) {
-	    HTML5Caption_transDFXPAttributes(el, ht);	    
+	if (dfxpElementRef != null) {
+	    HTML5Caption_convertDFXP2HTMLAttributes(dfxpElementRef, htmlElement);	    
 	} else {
-            // the DFXP is invalid. Should fail silently in the future
-	    alert("can't find " + v);
+	    if (HTML5Caption_debug) 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");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "backgroundColor");
     if (v != "") {
-	ht.style.setProperty("color", v, "");
+	htmlElement.style.setProperty("background-color", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "direction");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "color");
     if (v != "") {
-	ht.style.setProperty("direction", v, "");
+	htmlElement.style.setProperty("color", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "display");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "direction");
+    if (v != "") {
+	htmlElement.style.setProperty("direction", v, "");
+    }
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "display");
     if ((v == "") || (v == "auto")) {
-	if (ht.tagName == "SPAN") {
+	if (htmlElement.localName == "span") {
 	    v = "inline";
 	} else {
 	    v = "block";
 	}
     }
-    ht.df_displayValue = v;
+    htmlElement.style.setProperty("display", v, "");
+    htmlElement.df_displayValue = v;
 
-    v = dp.getAttributeNS(DFXP_TTS, "fontFamily");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "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, "");
+	htmlElement.style.setProperty("font-family", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "fontWeight");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "fontSize");
     if (v != "") {
-	ht.style.setProperty("font-weight", v, "");
-    }
-    v = dp.getAttributeNS(DFXP_TTS, "lineHeight");
-    if (v != "") {
-	ht.style.setProperty("line-height", v, "");
+	htmlElement.style.setProperty("font-size", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "opacity");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "fontStyle");
     if (v != "") {
-	ht.style.setProperty("opacity", v, "");
+	htmlElement.style.setProperty("font-style", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "padding");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "fontWeight");
     if (v != "") {
-	ht.style.setProperty("padding", v, "");
+	htmlElement.style.setProperty("font-weight", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "textAlign");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "lineHeight");
+    if (v != "") {
+	htmlElement.style.setProperty("line-height", v, "");
+    }
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "opacity");
+    if (v != "") {
+	htmlElement.style.setProperty("opacity", v, "");
+    }
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "padding");
+    if (v != "") {
+	htmlElement.style.setProperty("padding", v, "");
+    }
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "textAlign");
     if (v != "") {
 	// REVISIT to take into account text direction...
 	if (v == "start") {
-	    ht.style.setProperty("text-align", "left", "");
+	    htmlElement.style.setProperty("text-align", "left", "");
 	} else if (v == "end") {
-	    ht.style.setProperty("text-align", "right", "");
+	    htmlElement.style.setProperty("text-align", "right", "");
 	} else {
-	    ht.style.setProperty("text-align", v, "");
+	    htmlElement.style.setProperty("text-align", v, "");
 	}
     }
-    v = dp.getAttributeNS(DFXP_TTS, "textDecoration");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "textDecoration");
     if (v != "") {
 	if (v == "noUnderline" || v == "noOverline" || v == "noLineThrough") {
 	    // this is not accurate
@@ -193,64 +232,150 @@
 	} else if (v == "lineThrough") {
 	    v = "line-through";
 	}
-	ht.style.setProperty("text-decoration", v, "");
+	htmlElement.style.setProperty("text-decoration", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "unicodeBidi");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "unicodeBidi");
     if (v != "") {
 	if (v == "bidiOverride") {
-	    v = "bidy-override";
+	    v = "bidi-override";
 	}
-	ht.style.setProperty("unicode-bidi", v, "");
+	htmlElement.style.setProperty("unicode-bidi", v, "");
     }
-    v = dp.getAttributeNS(DFXP_TTS, "visibility");
+    v = dfxpElement.getAttributeNS(DFXP_NS_Style, "visibility");
     if (v != "") {
-	ht.style.setProperty("visibility", v, "");
+	htmlElement.style.setProperty("visibility", v, "");
     }
-    v = dp.getAttribute("xml:space");
-    if (v != "") {
+    v = dfxpElement.getAttribute("xml:space");
+    if (v != null && v != "") {
 	if (v == "preserve") {
 	    v = "pre";
-	    ht.spaces = true;
+	    htmlElement.spaces = true;
 	} else {
 	    v = "normal";
 	}
-	ht.style.setProperty("white-space", v, "");
+	htmlElement.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);
+HTML5Caption_convertDFXP2HTML = function(dfxpNode) {
+    var htmlNode = null;
+
+    if (dfxpNode.aDur == 0) {
+	return null;
+    }
+
+    if (dfxpNode.nodeType == 3 || dfxpNode.nodeType == 4) {
+	// TEXT_NODE or CDATA_SECTION_NODE
+	if (dfxpNode.parentNode.localName != "p"
+	    && dfxpNode.parentNode.localName != "span") {
+	    // clean up the tree, we don't need to keep text nodes outside p or span
+	    return null;
+	}
+	if (dfxpNode.parentNode.tContainer == DFXP_TIME_CONTAINER_SEQ) {
+	    // text nodes are always within an "anonymous
+	    // span". if that anonymous span is inside a seq
+	    // container, then its implicit duration is 0, so
+	    // ignore it.
+	    return null;
+	}
+	htmlNode = document.createTextNode(dfxpNode.data);
+    } else if (dfxpNode.nodeType == 1) {
+	// ELEMENT_NODE
+	if (dfxpNode.namespaceURI == DFXP_NS_Metadata_Extensions
+	    || dfxpNode.namespaceURI == DFXP_NS_Metadata) {
+	    // ignore metadata stuff
+	    return null;
+	} else {		
+	    if (dfxpNode.namespaceURI == DFXP_NS) {
+		if (dfxpNode.aDur <= 0) {
+		    // eliminates non-active elements
+		    return null;
+		}
+		var region = dfxpNode.getAttribute("region");
+		if (region == "") region = null;
+		if (dfxpNode.localName == "span") {
+		    if (region != null) return null;
+		    htmlNode = document.createElementNS(XHTML_NS, "span");
+		} else if (dfxpNode.localName == "p") {
+		    if (region != null) return null;
+		    htmlNode = document.createElementNS(XHTML_NS, "p");
+		} else if (dfxpNode.localName == "div") {
+		    if (region != null) return null;
+		    htmlNode = document.createElementNS(XHTML_NS, "div");
+		} else if (dfxpNode.localName == "br") {
+		    htmlNode = document.createElementNS(XHTML_NS, "br");
+		} else if (dfxpNode.localName == "body") {
+		    if (region != null) throw new Error("Region on body element is not supported");
+		    htmlNode = document.createElementNS(XHTML_NS, "div");
+		    htmlNode.className = 'dfxp';
+		} else {
+		    // @@TODO animation
+		    return null;
+		}
+		
+	    } else {
+		// there is something here, but it's not dfxp, let's copy it as-is if XHTML
+		if (dfxpNode.namespaceURI == XHTML_NS) {
+		    try {
+			htmlNode = document.importNode(dfxpNode, true);
+		    } catch (e) {
+			return null;
 		    }
-		    child=child.nextSibling;
 		}
 	    }
-	    return n;
 	}
     }
-    return null;
+        
+    htmlNode.aBegin = dfxpNode.aBegin;
+    htmlNode.aEnd   = dfxpNode.aEnd;
+
+    if (HTML5Caption_debug && htmlNode.nodeType == 1) {
+	htmlNode.setAttribute("relative_begin", dfxpNode.rBegin);
+	htmlNode.setAttribute("relative_end", dfxpNode.rEnd);
+	htmlNode.setAttribute("relative_dur", dfxpNode.rDur);
+	htmlNode.setAttribute("active_begin", dfxpNode.aBegin);
+	htmlNode.setAttribute("active_end", dfxpNode.aEnd);
+	htmlNode.setAttribute("active_dur", dfxpNode.aDur);
+	var container = null;
+	if (dfxpNode.tContainer == DFXP_TIME_CONTAINER_PAR) {
+	    container = "par";
+	} else if (dfxpNode.tContainer == DFXP_TIME_CONTAINER_SEQ) {
+	    container = "seq";
+	}
+	if (container != null) {
+	    htmlNode.setAttribute("timeContainer", container);
+	}
+    }
+
+    if (dfxpNode.tContainer) {
+	HTML5Caption_convertDFXP2HTMLAttributes(dfxpNode, htmlNode);
+	
+	if (HTML5Caption_debug) {
+	    if (htmlNode.localName == "p" || htmlNode.localName == "div" || htmlNode.localName == "span") {
+		htmlNode.appendChild(document.createTextNode("[" + htmlNode.aBegin + "-" + htmlNode.aEnd + "]"));
+	    }
+	}
+	var childNodes = dfxpNode.childNodes;
+	for (var i = 0; i < childNodes.length; i++) {
+	    var r = HTML5Caption_convertDFXP2HTML(childNodes.item(i));
+	    if (r!= null) {
+		htmlNode.appendChild(r);
+	    }
+	}
+    }
+
+    return htmlNode;
 }
 
+var TIME_INDEFINITE            = -1;
+var TIME_INFINITY_AND_BEYOND   = Infinity;
+
 HTML5Caption_convertDFXPDuration = function(d) {
     var i = 0;
-    if (d.indexOf(':') != -1) {
+    if (d ==null || d == "") {
+	return TIME_INDEFINITE;
+    } else if (d.indexOf(':') != -1) {
 	return HTML5Caption_toSeconds(d);
     } else if ((i = d.indexOf('h')) != -1) {
 	return parseFloat(d.substring(0, i)) * 3600;
@@ -267,122 +392,347 @@
     }
 }
 
-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_computeRelativeTimeIntervals = function(dfxpNode) {
+    // we only accept body, div, p, and span
+    if (dfxpNode.namespaceURI != DFXP_NS
+	|| !(dfxpNode.localName == "body"
+	     || dfxpNode.localName == "div"
+	     || dfxpNode.localName == "p"
+	     || dfxpNode.localName == "span")) {
+	if (dfxpNode.parentNode.localName != "p"
+	    && dfxpNode.parentNode.localName != "span") {
+	    // clean up the tree, we don't need to keep nodes outside p or span
+	    dfxpNode.rBegin = 0;
+	    dfxpNode.rEnd   = 0;
+	    dfxpNode.rDur   = 0;
+	} else if (dfxpNode.parentNode.tContainer == DFXP_TIME_CONTAINER_SEQ) {
+	    // nodes are always within an "anonymous span". if that
+	    // anonymous span is inside a seq container, then its
+	    // implicit duration is 0, so ignore it.
+	    dfxpNode.rBegin = 0;
+	    dfxpNode.rEnd   = 0;
+	    dfxpNode.rDur   = 0;
+	} else if (dfxpNode.parentNode.tContainer == DFXP_TIME_CONTAINER_PAR) {	    
+	    dfxpNode.rBegin = 0;
+	    dfxpNode.rEnd   = TIME_INFINITY_AND_BEYOND;
+	    dfxpNode.rDur   = TIME_INFINITY_AND_BEYOND;
 	}
-	HTML5Caption_transDFXPAttributes(parent, ht);
-    }
-    
-}
-
-HTML5Caption_playDFXP = function(video, dfxp) {    
-
-    // @@grabing all paragraph elements. ugly but effective so far...
-    // wouldn't work with WGBH files with multiple languages
-    // REVISIT
-    var paras = dfxp.getElementsByTagNameNS(DFXP, "p");
-
-    if (paras.length == 0) {
-        // fail silently in the future
-	alert("No paragraph in DFXP?!?");
 	return;
     }
 
-    // create the subtitle area
+    // for each node, we're going to compute the time container and
+    // its corresponding relative time interval.
+    //
+    // we'll decorate the tree with the results:
+    //   dfxpNode.tContainer
+    //   dfxpNode.rBegin
+    //   dfxpNode.rEnd
+    //   dfxpNode.rDur
+
+    // first, determine the time container (par|seq)
+
+    var timeContainer = dfxpNode.getAttribute("timeContainer");
+    if (timeContainer == "seq") {
+	dfxpNode.tContainer = DFXP_TIME_CONTAINER_SEQ;
+    } else {
+	// everything else defaults to par
+	dfxpNode.tContainer = DFXP_TIME_CONTAINER_PAR;
+    }
+
+    // Now, calculate the specified time interval, if any
+
+    var begin   = HTML5Caption_convertDFXPDuration(dfxpNode.getAttribute("begin"));
+    var end     = HTML5Caption_convertDFXPDuration(dfxpNode.getAttribute("end"));
+    var dur     = HTML5Caption_convertDFXPDuration(dfxpNode.getAttribute("dur"));
+
+    // Note: we give preference to the specified end attribute over the dur
+    //       attribute if any
+    if (begin != TIME_INDEFINITE) {
+	if (end != TIME_INDEFINITE) {
+	    dur = end - begin;
+	} else if (dur != TIME_INDEFINITE) {
+	    end = begin + dur;
+	}
+    } else {
+	// Children of a par begin by default when the par begins
+	// (equivalent to begin="0s"). Children of a seq begin by
+	// default when the previous child ends its active duration
+	// (equivalent to begin="0s");
+	begin = 0;
+	if (end != TIME_INDEFINITE) {
+	    dur   = end;
+	} else if (dur != TIME_INDEFINITE) {
+	    end   = dur;
+	}
+    }
+
+    if (dur <= 0) {
+	// bogus interval, let's ignore it
+	end = TIME_INDEFINITE;
+	dur = TIME_INDEFINITE;
+    }
+
+    if (dur == TIME_INDEFINITE
+	&& dfxpNode.parentNode.tContainer == DFXP_TIME_CONTAINER_SEQ) {
+	// if the element's parent time container is a sequential time
+	// container, then the implicit duration is equivalent to
+	// zero.
+
+	begin = 0;
+	end = 0;
+	dur = 0;
+    } // else {
+    // if the element's parent time container is a parallel time
+    // container, then the implicit duration is equivalent to the
+    // indefinite duration value
+    // }    
+
+    // set the relative time interval. 
+    // for a par, its' relative to its parent.
+    // for a seq, it's relative to its previous sibling or the its
+    // parent if no previous sibling.
+    dfxpNode.rBegin = begin;  // 0 or higher
+    dfxpNode.rEnd   = end;    // TIME_INDEFINITE or >= begin
+    dfxpNode.rDur   = dur;    // TIME_INDEFINITE or >= 0
+
+    // now calculate the relative time intervals for the children
+    var childNodes = dfxpNode.childNodes;
+
+    for (var i = 0; i < childNodes.length; i++) {
+	var node = childNodes.item(i);
+	HTML5Caption_computeRelativeTimeIntervals(node);
+    }
+
+    // done.
+
+    if (dfxpNode.rEnd == TIME_INDEFINITE) {
+	// we still don't have a relative time interval for the node
+	// so now, we're going to see if we can get one from the children
+
+	if (dfxpNode.tContainer == DFXP_TIME_CONTAINER_PAR) {
+
+	    // The implicit duration ends with the last end of the
+	    // child elements.
+	    var childNodes = dfxpNode.childNodes;
+	    for (var i = 0; i < childNodes.length; i++) {
+		var node = childNodes.item(i);
+		if (node.rEnd > dfxpNode.rEnd) {
+		    dfxpNode.rEnd = node.rEnd;
+		}
+	    }
+	} else { // dfxpNode.tContainer == DFXP_TIME_CONTAINER_SEQ
+
+	    // The implicit duration of a seq ends with the end of the
+	    // last child of the seq.
+
+	    var abort = false;
+	    var totalTime = 0;
+	    var childNodes = dfxpNode.childNodes;
+	    for (var i = 0; !abort && i < childNodes.length; i++) {
+		var node = childNodes.item(i);
+		if (node.rEnd == TIME_INDEFINITE) {
+		    // that's not good. all children must have a
+		    // duration
+		    abort = true;
+		}
+		totalTime += node.rDur + node.rBegin;
+	    }
+	    if (!abort) {
+		dfxpNode.rEnd = totalTime;
+	    }
+	}
+	if (dfxpNode.rEnd != TIME_INDEFINITE) {
+	    dfxpNode.rDur = dfxpNode.rEnd - dfxpNode.rBegin;
+	}
+
+    }
+}
+
+HTML5Caption_computeActiveTimeIntervals = function(dfxpNode) {
+
+    // for each node, we're going to compute the active time
+    // intervals, ie the time intervals relative to the time interval
+    // of the body element
+    //
+    // we'll decorate the tree with the results:
+    //   dfxpNode.aBegin
+    //   dfxpNode.aEnd
+    //   dfxpNode.aDur
+    //
+    // Note that this is a two steps process:
+    //  first, we'll compute the relative time intervals
+    //  second,  we'll compute the active time intervals
+
+    // first, determine the relative time intervals    
+    if (dfxpNode.nodeType == 1
+	&& dfxpNode.namespaceURI == DFXP_NS
+	&& dfxpNode.localName == "body") {
+	HTML5Caption_computeRelativeTimeIntervals(dfxpNode);
+    }
+
+
+    dfxpNode.aBegin = TIME_INDEFINITE;
+    dfxpNode.aEnd   = TIME_INDEFINITE;
+    dfxpNode.aDur   = TIME_INDEFINITE;
+
+    var parentNode = dfxpNode.parentNode;
+
+    // transfer the time intervals from relative to active
+    if (dfxpNode.localName == "body") {
+	dfxpNode.aBegin = dfxpNode.rBegin;
+	dfxpNode.aEnd   = dfxpNode.rEnd;
+    } else if (parentNode.tContainer == DFXP_TIME_CONTAINER_PAR) {
+	if (dfxpNode.rDur != TIME_INDEFINITE) {
+	    // the active time is calculated based on its relative
+	    // time and the active time of its parent
+	    dfxpNode.aBegin = dfxpNode.rBegin + dfxpNode.parentNode.aBegin;	
+	    dfxpNode.aEnd   = dfxpNode.rEnd + dfxpNode.parentNode.aBegin;
+	}
+    } else { // parentNode.tContainer == DFXP_TIME_CONTAINER_SEQ
+	if (dfxpNode.rDur != TIME_INDEFINITE) {
+	    var previousSibling = dfxpNode.previousSibling;
+	    while (previousSibling != null
+		   && !(previousSibling.tContainer)) {
+		// previousSibling with no time container have a duration of 0
+		// so we'll skip them
+		previousSibling = previousSibling.previousSibling;
+	    }
+	    
+	    if (previousSibling != null) {
+		if (previousSibling.aDur != TIME_INDEFINITE) {
+		    // the active time is calculated base on its
+		    // relative time and the active of its previous
+		    // sibling that contains a time
+		    dfxpNode.aBegin = dfxpNode.rBegin + previousSibling.aEnd;
+		    dfxpNode.aEnd   = dfxpNode.rEnd + previousSibling.aEnd;
+		}
+	    } else {
+		if (dfxpNode.rDur != TIME_INDEFINITE) {
+		    // No previous sibling, so the active time is
+		    // calculated based on its relative time and the
+		    // active time of its parent
+		    dfxpNode.aBegin = dfxpNode.rBegin + dfxpNode.parentNode.aBegin;
+		    dfxpNode.aEnd   = dfxpNode.rEnd + dfxpNode.parentNode.aBegin;
+		}
+	    }
+	}
+    }
+    // check that the active time interval is within its parent
+    // and set the active duration
+    if (dfxpNode.aEnd != TIME_INDEFINITE) {
+	if (dfxpNode.aEnd > dfxpNode.parentNode.aEnd) {
+	    // it can't end after its parent
+	    dfxpNode.aEnd = dfxpNode.parentNode.aEnd;
+	}
+	if (dfxpNode.aBegin > dfxpNode.aEnd) {
+	    // it can't begin after its end
+	    dfxpNode.aBegin = TIME_INDEFINITE;
+	    dfxpNode.aEnd = TIME_INDEFINITE;
+	} else {
+	    dfxpNode.aDur = dfxpNode.aEnd - dfxpNode.aBegin;
+	}
+    }
+
+    if (dfxpNode.aDur != TIME_INDEFINITE && dfxpNode.tContainer) {
+	// we have an active time interval, so now calculate the active time
+	// intervals for the children
+	var childNodes = dfxpNode.childNodes;
+	
+	for (var i = 0; i < childNodes.length; i++) {
+	    HTML5Caption_computeActiveTimeIntervals(childNodes.item(i));
+	}
+    }
+}
+    
+HTML5Caption_getSubtitleSetRef = function(htmlElement, set) {    
+
+    if (htmlElement.aBegin >= 0) {
+	if (htmlElement.parentNode != null
+	    && htmlElement.parentNode.aBegin >= 0
+	    && htmlElement.parentNode.aBegin == htmlElement.aBegin
+	    && htmlElement.parentNode.aEnd == htmlElement.aEnd) {
+	    // if it needs to always be displayed when its parent get displayed
+	} else {
+	    // we'll need to do something with this one, so add it	    
+	    set[set.length] = htmlElement;
+	    if (!HTML5Caption_debug) {
+		htmlElement.style.display = "none";
+	    }
+	    htmlElement.df_isInTime      = false;	    
+	}
+    } else {
+	// skip the children
+	return;
+    }
+    
+    var children = htmlElement.childNodes;
+    var length   = children.length;
+    for (var i = 0; i < length; i++) {
+	var child = children.item(i);
+	if (child.nodeType == 1) {
+	    HTML5Caption_getSubtitleSetRef(child, set);
+	}
+    }
+
+}
+
+HTML5Caption_getSubtitleSet = function(htmlElement) {
+    var set = new Array();
+    HTML5Caption_getSubtitleSetRef(htmlElement, set);
+    return set;
+}
+
+HTML5Caption_playDFXP = function(video, dfxpDocument) {    
+
+    dfxpDocument.bodyElement = dfxpDocument.getElementsByTagNameNS(DFXP_NS, "body").item(0);
+
+    // the following function call will decorate the dfxp tree with active durations
+    HTML5Caption_computeActiveTimeIntervals(dfxpDocument.bodyElement);
+
+    // convert the resulting tree into HTML
     // 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 mainDiv = HTML5Caption_convertDFXP2HTML(dfxpDocument.bodyElement);
+
+    if (mainDiv == null) return;
+
     var w = video.getAttribute("width");
     if (w!="") {
 	// the main container gets the size of the video
 	mainDiv.style.setProperty("width", w, "");
     }
-    var div = document.createElement("div");
-
-    HTML5Caption_ParentStyle(paras.item(0).parentNode, div);
-    div.style.display = div.displayValue;
-
-    // initialize the 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);
-	var sbegin  = -1;
-	var send    = -1;
-
-	// TODO: take into account the timeContainers
 
-	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);
-	} else if (begin != null && dur != null) {
-	    sbegin=HTML5Caption_convertDFXPDuration(begin);
-	    send=sbegin + HTML5Caption_convertDFXPDuration(dur);
-	} else if (begin != null) {
-	    sbegin=HTML5Caption_convertDFXPDuration(begin);
-	    var next = paras.item(i+1);
-	    send = 10000000;
-	    if (next != null) {
-		var nbegin=next.getAttribute("begin");
-		if (nbegin!= null) {
-		    send= HTML5Caption_convertDFXPDuration(nbegin);
-		}
-	    }
-	}
-	if (sbegin != -1) {
-	    element.df_begin         = sbegin;
-	    element.df_end           = send;	    
-	    element.style.display = "none";
-	    element.df_isInTime      = false;
-	    div.appendChild(element);
-	}
-    }
-
-    mainDiv.appendChild(div);
+    var subtitles = HTML5Caption_getSubtitleSet(mainDiv);
 
     video.parentNode.insertBefore(mainDiv, video.nextSibling);
 
+    if (HTML5Caption_debug) {
+	alert("We have " + subtitles.length + " in the set " + mainDiv.aBegin + "-" + mainDiv.aEnd);
+	return;
+    }
     var currentTime = video.currentTime;
 
     if (typeof currentTime == "undefined") {
 	throw new Error("currentTime is not supported by the Video element");
     } else {
+	var length = subtitles.length;
 	setInterval(function() {
 		if (!video.paused) {
-		    var currentTime = video.currentTime;
-		    var nodes = div.childNodes;
-		    var length = nodes.length;
+		    currentTime = video.currentTime;
 		    for (var i = 0; i < length; i++) {
-			node = nodes.item(i);
+			node = subtitles[i];
 			// this might get slow if too many subtitles?
 			if (node.df_isInTime) {
-			    if (node.df_end < currentTime
-				|| node.df_begin > currentTime)  {
+			    if (node.aEnd < currentTime
+				|| node.aBegin > currentTime)  {
 				// remove the element from the display since
 				// it's in a node in the past or future
 				node.style.display = "none";
 				node.df_isInTime = false;
 			    }
-			} else if (node.df_begin < currentTime 
-				   && node.df_end > currentTime) {
+			} else if (node.aBegin < currentTime 
+				   && node.aEnd > currentTime) {
 			    node.style.display = node.df_displayValue;
 			    node.df_isInTime = true;
 			}