sbin/res/jsdoc/LdtPlayer-release.js
changeset 1072 ac1eacb3aa33
parent 1071 02c04d2c8fd8
child 1073 687133dc13cf
equal deleted inserted replaced
1071:02c04d2c8fd8 1072:ac1eacb3aa33
     1 /* 
       
     2  * 	
       
     3  *	Copyright 2010 Institut de recherche et d'innovation 
       
     4  *	contributor(s) : Samuel Huron 
       
     5  *	 
       
     6  *	contact@iri.centrepompidou.fr
       
     7  *	http://www.iri.centrepompidou.fr 
       
     8  *	 
       
     9  *	This software is a computer program whose purpose is to show and add annotations on a video .
       
    10  *	This software is governed by the CeCILL-C license under French law and
       
    11  *	abiding by the rules of distribution of free software. You can  use, 
       
    12  *	modify and/ or redistribute the software under the terms of the CeCILL-C
       
    13  *	license as circulated by CEA, CNRS and INRIA at the following URL
       
    14  *	"http://www.cecill.info". 
       
    15  *	
       
    16  *	The fact that you are presently reading this means that you have had
       
    17  *	knowledge of the CeCILL-C license and that you accept its terms.
       
    18 */
       
    19 /*! LAB.js (LABjs :: Loading And Blocking JavaScript)
       
    20     v2.0.3 (c) Kyle Simpson
       
    21     MIT License
       
    22 */
       
    23 
       
    24 (function(global){
       
    25 	var _$LAB = global.$LAB,
       
    26 	
       
    27 		// constants for the valid keys of the options object
       
    28 		_UseLocalXHR = "UseLocalXHR",
       
    29 		_AlwaysPreserveOrder = "AlwaysPreserveOrder",
       
    30 		_AllowDuplicates = "AllowDuplicates",
       
    31 		_CacheBust = "CacheBust",
       
    32 		/*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/
       
    33 		_BasePath = "BasePath",
       
    34 		
       
    35 		// stateless variables used across all $LAB instances
       
    36 		root_page = /^[^?#]*\//.exec(location.href)[0],
       
    37 		root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0],
       
    38 		append_to = document.head || document.getElementsByTagName("head"),
       
    39 		
       
    40 		// inferences... ick, but still necessary
       
    41 		opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style),
       
    42 
       
    43 /*!START_DEBUG*/
       
    44 		// console.log() and console.error() wrappers
       
    45 		log_msg = function(){}, 
       
    46 		log_error = log_msg,
       
    47 /*!END_DEBUG*/
       
    48 		
       
    49 		// feature sniffs (yay!)
       
    50 		test_script_elem = document.createElement("script"),
       
    51 		explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29
       
    52 		real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append?
       
    53 		script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
       
    54 		
       
    55 		// XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers)
       
    56 		xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko
       
    57 	;
       
    58 
       
    59 /*!START_DEBUG*/
       
    60 	// define console wrapper functions if applicable
       
    61 	if (global.console && global.console.log) {
       
    62 		if (!global.console.error) global.console.error = global.console.log;
       
    63 		log_msg = function(msg) { global.console.log(msg); };
       
    64 		log_error = function(msg,err) { global.console.error(msg,err); };
       
    65 	}
       
    66 /*!END_DEBUG*/
       
    67 
       
    68 	// test for function
       
    69 	function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; }
       
    70 
       
    71 	// test for array
       
    72 	function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; }
       
    73 
       
    74 	// make script URL absolute/canonical
       
    75 	function canonical_uri(src,base_path) {
       
    76 		var absolute_regex = /^\w+\:\/\//;
       
    77 		
       
    78 		// is `src` is protocol-relative (begins with // or ///), prepend protocol
       
    79 		if (/^\/\/\/?/.test(src)) {
       
    80 			src = location.protocol + src;
       
    81 		}
       
    82 		// is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /)
       
    83 		else if (!absolute_regex.test(src) && src.charAt(0) != "/") {
       
    84 			// prepend `base_path`, if any
       
    85 			src = (base_path || "") + src;
       
    86 		}
       
    87 		// make sure to return `src` as absolute
       
    88 		return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src);
       
    89 	}
       
    90 
       
    91 	// merge `source` into `target`
       
    92 	function merge_objs(source,target) {
       
    93 		for (var k in source) { if (source.hasOwnProperty(k)) {
       
    94 			target[k] = source[k]; // TODO: does this need to be recursive for our purposes?
       
    95 		}}
       
    96 		return target;
       
    97 	}
       
    98 
       
    99 	// does the chain group have any ready-to-execute scripts?
       
   100 	function check_chain_group_scripts_ready(chain_group) {
       
   101 		var any_scripts_ready = false;
       
   102 		for (var i=0; i<chain_group.scripts.length; i++) {
       
   103 			if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) {
       
   104 				any_scripts_ready = true;
       
   105 				chain_group.scripts[i].exec_trigger();
       
   106 				chain_group.scripts[i].exec_trigger = null;
       
   107 			}
       
   108 		}
       
   109 		return any_scripts_ready;
       
   110 	}
       
   111 
       
   112 	// creates a script load listener
       
   113 	function create_script_load_listener(elem,registry_item,flag,onload) {
       
   114 		elem.onload = elem.onreadystatechange = function() {
       
   115 			if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return;
       
   116 			elem.onload = elem.onreadystatechange = null;
       
   117 			onload();
       
   118 		};
       
   119 	}
       
   120 
       
   121 	// script executed handler
       
   122 	function script_executed(registry_item) {
       
   123 		registry_item.ready = registry_item.finished = true;
       
   124 		for (var i=0; i<registry_item.finished_listeners.length; i++) {
       
   125 			registry_item.finished_listeners[i]();
       
   126 		}
       
   127 		registry_item.ready_listeners = [];
       
   128 		registry_item.finished_listeners = [];
       
   129 	}
       
   130 
       
   131 	// make the request for a scriptha
       
   132 	function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) {
       
   133 		// setTimeout() "yielding" prevents some weird race/crash conditions in older browsers
       
   134 		setTimeout(function(){
       
   135 			var script, src = script_obj.real_src, xhr;
       
   136 			
       
   137 			// don't proceed until `append_to` is ready to append to
       
   138 			if ("item" in append_to) { // check if `append_to` ref is still a live node list
       
   139 				if (!append_to[0]) { // `append_to` node not yet ready
       
   140 					// try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()`
       
   141 					setTimeout(arguments.callee,25);
       
   142 					return;
       
   143 				}
       
   144 				// reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
       
   145 				append_to = append_to[0];
       
   146 			}
       
   147 			script = document.createElement("script");
       
   148 			if (script_obj.type) script.type = script_obj.type;
       
   149 			if (script_obj.charset) script.charset = script_obj.charset;
       
   150 			
       
   151 			// should preloading be used for this script?
       
   152 			if (preload_this_script) {
       
   153 				// real script preloading?
       
   154 				if (real_preloading) {
       
   155 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/
       
   156 					registry_item.elem = script;
       
   157 					if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal)
       
   158 						script.preload = true;
       
   159 						script.onpreload = onload;
       
   160 					}
       
   161 					else {
       
   162 						script.onreadystatechange = function(){
       
   163 							if (script.readyState == "loaded") onload();
       
   164 						};
       
   165 					}
       
   166 					script.src = src;
       
   167 					// NOTE: no append to DOM yet, appending will happen when ready to execute
       
   168 				}
       
   169 				// same-domain and XHR allowed? use XHR preloading
       
   170 				else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) {
       
   171 					xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7
       
   172 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/
       
   173 					xhr.onreadystatechange = function() {
       
   174 						if (xhr.readyState == 4) {
       
   175 							xhr.onreadystatechange = function(){}; // fix a memory leak in IE
       
   176 							registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
       
   177 							onload();
       
   178 						}
       
   179 					};
       
   180 					xhr.open("GET",src);
       
   181 					xhr.send();
       
   182 				}
       
   183 				// as a last resort, use cache-preloading
       
   184 				else {
       
   185 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/
       
   186 					script.type = "text/cache-script";
       
   187 					create_script_load_listener(script,registry_item,"ready",function() {
       
   188 						append_to.removeChild(script);
       
   189 						onload();
       
   190 					});
       
   191 					script.src = src;
       
   192 					append_to.insertBefore(script,append_to.firstChild);
       
   193 				}
       
   194 			}
       
   195 			// use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
       
   196 			else if (script_ordered_async) {
       
   197 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/
       
   198 				script.async = false;
       
   199 				create_script_load_listener(script,registry_item,"finished",onload);
       
   200 				script.src = src;
       
   201 				append_to.insertBefore(script,append_to.firstChild);
       
   202 			}
       
   203 			// otherwise, just a normal script element
       
   204 			else {
       
   205 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/
       
   206 				create_script_load_listener(script,registry_item,"finished",onload);
       
   207 				script.src = src;
       
   208 				append_to.insertBefore(script,append_to.firstChild);
       
   209 			}
       
   210 		},0);
       
   211 	}
       
   212 		
       
   213 	// create a clean instance of $LAB
       
   214 	function create_sandbox() {
       
   215 		var global_defaults = {},
       
   216 			can_use_preloading = real_preloading || xhr_or_cache_preloading,
       
   217 			queue = [],
       
   218 			registry = {},
       
   219 			instanceAPI
       
   220 		;
       
   221 		
       
   222 		// global defaults
       
   223 		global_defaults[_UseLocalXHR] = true;
       
   224 		global_defaults[_AlwaysPreserveOrder] = false;
       
   225 		global_defaults[_AllowDuplicates] = false;
       
   226 		global_defaults[_CacheBust] = false;
       
   227 		/*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/
       
   228 		global_defaults[_BasePath] = "";
       
   229 
       
   230 		// execute a script that has been preloaded already
       
   231 		function execute_preloaded_script(chain_opts,script_obj,registry_item) {
       
   232 			var script;
       
   233 			
       
   234 			function preload_execute_finished() {
       
   235 				if (script != null) { // make sure this only ever fires once
       
   236 					script = null;
       
   237 					script_executed(registry_item);
       
   238 				}
       
   239 			}
       
   240 			
       
   241 			if (registry[script_obj.src].finished) return;
       
   242 			if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true;
       
   243 			
       
   244 			script = registry_item.elem || document.createElement("script");
       
   245 			if (script_obj.type) script.type = script_obj.type;
       
   246 			if (script_obj.charset) script.charset = script_obj.charset;
       
   247 			create_script_load_listener(script,registry_item,"finished",preload_execute_finished);
       
   248 			
       
   249 			// script elem was real-preloaded
       
   250 			if (registry_item.elem) {
       
   251 				registry_item.elem = null;
       
   252 			}
       
   253 			// script was XHR preloaded
       
   254 			else if (registry_item.text) {
       
   255 				script.onload = script.onreadystatechange = null;	// script injection doesn't fire these events
       
   256 				script.text = registry_item.text;
       
   257 			}
       
   258 			// script was cache-preloaded
       
   259 			else {
       
   260 				script.src = script_obj.real_src;
       
   261 			}
       
   262 			append_to.insertBefore(script,append_to.firstChild);
       
   263 
       
   264 			// manually fire execution callback for injected scripts, since events don't fire
       
   265 			if (registry_item.text) {
       
   266 				preload_execute_finished();
       
   267 			}
       
   268 		}
       
   269 	
       
   270 		// process the script request setup
       
   271 		function do_script(chain_opts,script_obj,chain_group,preload_this_script) {
       
   272 			var registry_item,
       
   273 				registry_items,
       
   274 				ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); },
       
   275 				finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); }
       
   276 			;
       
   277 			
       
   278 			script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]);
       
   279 			script_obj.real_src = script_obj.src + 
       
   280 				// append cache-bust param to URL?
       
   281 				(chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "")
       
   282 			;
       
   283 			
       
   284 			if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false};
       
   285 			registry_items = registry[script_obj.src].items;
       
   286 
       
   287 			// allowing duplicates, or is this the first recorded load of this script?
       
   288 			if (chain_opts[_AllowDuplicates] || registry_items.length == 0) {
       
   289 				registry_item = registry_items[registry_items.length] = {
       
   290 					ready:false,
       
   291 					finished:false,
       
   292 					ready_listeners:[ready_cb],
       
   293 					finished_listeners:[finished_cb]
       
   294 				};
       
   295 
       
   296 				request_script(chain_opts,script_obj,registry_item,
       
   297 					// which callback type to pass?
       
   298 					(
       
   299 					 	(preload_this_script) ? // depends on script-preloading
       
   300 						function(){
       
   301 							registry_item.ready = true;
       
   302 							for (var i=0; i<registry_item.ready_listeners.length; i++) {
       
   303 								registry_item.ready_listeners[i]();
       
   304 							}
       
   305 							registry_item.ready_listeners = [];
       
   306 						} :
       
   307 						function(){ script_executed(registry_item); }
       
   308 					),
       
   309 					// signal if script-preloading should be used or not
       
   310 					preload_this_script
       
   311 				);
       
   312 			}
       
   313 			else {
       
   314 				registry_item = registry_items[0];
       
   315 				if (registry_item.finished) {
       
   316 					finished_cb();
       
   317 				}
       
   318 				else {
       
   319 					registry_item.finished_listeners.push(finished_cb);
       
   320 				}
       
   321 			}
       
   322 		}
       
   323 
       
   324 		// creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains
       
   325 		function create_chain() {
       
   326 			var chainedAPI,
       
   327 				chain_opts = merge_objs(global_defaults,{}),
       
   328 				chain = [],
       
   329 				exec_cursor = 0,
       
   330 				scripts_currently_loading = false,
       
   331 				group
       
   332 			;
       
   333 			
       
   334 			// called when a script has finished preloading
       
   335 			function chain_script_ready(script_obj,exec_trigger) {
       
   336 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/
       
   337 				script_obj.ready = true;
       
   338 				script_obj.exec_trigger = exec_trigger;
       
   339 				advance_exec_cursor(); // will only check for 'ready' scripts to be executed
       
   340 			}
       
   341 
       
   342 			// called when a script has finished executing
       
   343 			function chain_script_executed(script_obj,chain_group) {
       
   344 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/
       
   345 				script_obj.ready = script_obj.finished = true;
       
   346 				script_obj.exec_trigger = null;
       
   347 				// check if chain group is all finished
       
   348 				for (var i=0; i<chain_group.scripts.length; i++) {
       
   349 					if (!chain_group.scripts[i].finished) return;
       
   350 				}
       
   351 				// chain_group is all finished if we get this far
       
   352 				chain_group.finished = true;
       
   353 				advance_exec_cursor();
       
   354 			}
       
   355 
       
   356 			// main driver for executing each part of the chain
       
   357 			function advance_exec_cursor() {
       
   358 				while (exec_cursor < chain.length) {
       
   359 					if (is_func(chain[exec_cursor])) {
       
   360 						/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/
       
   361 						try { chain[exec_cursor++](); } catch (err) {
       
   362 							/*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/
       
   363 						}
       
   364 						continue;
       
   365 					}
       
   366 					else if (!chain[exec_cursor].finished) {
       
   367 						if (check_chain_group_scripts_ready(chain[exec_cursor])) continue;
       
   368 						break;
       
   369 					}
       
   370 					exec_cursor++;
       
   371 				}
       
   372 				// we've reached the end of the chain (so far)
       
   373 				if (exec_cursor == chain.length) {
       
   374 					scripts_currently_loading = false;
       
   375 					group = false;
       
   376 				}
       
   377 			}
       
   378 			
       
   379 			// setup next chain script group
       
   380 			function init_script_chain_group() {
       
   381 				if (!group || !group.scripts) {
       
   382 					chain.push(group = {scripts:[],finished:true});
       
   383 				}
       
   384 			}
       
   385 
       
   386 			// API for $LAB chains
       
   387 			chainedAPI = {
       
   388 				// start loading one or more scripts
       
   389 				script:function(){
       
   390 					for (var i=0; i<arguments.length; i++) {
       
   391 						(function(script_obj,script_list){
       
   392 							var splice_args;
       
   393 							
       
   394 							if (!is_array(script_obj)) {
       
   395 								script_list = [script_obj];
       
   396 							}
       
   397 							for (var j=0; j<script_list.length; j++) {
       
   398 								init_script_chain_group();
       
   399 								script_obj = script_list[j];
       
   400 								
       
   401 								if (is_func(script_obj)) script_obj = script_obj();
       
   402 								if (!script_obj) continue;
       
   403 								if (is_array(script_obj)) {
       
   404 									// set up an array of arguments to pass to splice()
       
   405 									splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in
       
   406 									splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array
       
   407 									[].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice()
       
   408 									j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value
       
   409 									continue;
       
   410 								}
       
   411 								if (typeof script_obj == "string") script_obj = {src:script_obj};
       
   412 								script_obj = merge_objs(script_obj,{
       
   413 									ready:false,
       
   414 									ready_cb:chain_script_ready,
       
   415 									finished:false,
       
   416 									finished_cb:chain_script_executed
       
   417 								});
       
   418 								group.finished = false;
       
   419 								group.scripts.push(script_obj);
       
   420 								
       
   421 								do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading));
       
   422 								scripts_currently_loading = true;
       
   423 								
       
   424 								if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait();
       
   425 							}
       
   426 						})(arguments[i],arguments[i]);
       
   427 					}
       
   428 					return chainedAPI;
       
   429 				},
       
   430 				// force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding
       
   431 				wait:function(){
       
   432 					if (arguments.length > 0) {
       
   433 						for (var i=0; i<arguments.length; i++) {
       
   434 							chain.push(arguments[i]);
       
   435 						}
       
   436 						group = chain[chain.length-1];
       
   437 					}
       
   438 					else group = false;
       
   439 					
       
   440 					advance_exec_cursor();
       
   441 					
       
   442 					return chainedAPI;
       
   443 				}
       
   444 			};
       
   445 
       
   446 			// the first chain link API (includes `setOptions` only this first time)
       
   447 			return {
       
   448 				script:chainedAPI.script, 
       
   449 				wait:chainedAPI.wait, 
       
   450 				setOptions:function(opts){
       
   451 					merge_objs(opts,chain_opts);
       
   452 					return chainedAPI;
       
   453 				}
       
   454 			};
       
   455 		}
       
   456 
       
   457 		// API for each initial $LAB instance (before chaining starts)
       
   458 		instanceAPI = {
       
   459 			// main API functions
       
   460 			setGlobalDefaults:function(opts){
       
   461 				merge_objs(opts,global_defaults);
       
   462 				return instanceAPI;
       
   463 			},
       
   464 			setOptions:function(){
       
   465 				return create_chain().setOptions.apply(null,arguments);
       
   466 			},
       
   467 			script:function(){
       
   468 				return create_chain().script.apply(null,arguments);
       
   469 			},
       
   470 			wait:function(){
       
   471 				return create_chain().wait.apply(null,arguments);
       
   472 			},
       
   473 
       
   474 			// built-in queuing for $LAB `script()` and `wait()` calls
       
   475 			// useful for building up a chain programmatically across various script locations, and simulating
       
   476 			// execution of the chain
       
   477 			queueScript:function(){
       
   478 				queue[queue.length] = {type:"script", args:[].slice.call(arguments)};
       
   479 				return instanceAPI;
       
   480 			},
       
   481 			queueWait:function(){
       
   482 				queue[queue.length] = {type:"wait", args:[].slice.call(arguments)};
       
   483 				return instanceAPI;
       
   484 			},
       
   485 			runQueue:function(){
       
   486 				var $L = instanceAPI, len=queue.length, i=len, val;
       
   487 				for (;--i>=0;) {
       
   488 					val = queue.shift();
       
   489 					$L = $L[val.type].apply(null,val.args);
       
   490 				}
       
   491 				return $L;
       
   492 			},
       
   493 
       
   494 			// rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB
       
   495 			noConflict:function(){
       
   496 				global.$LAB = _$LAB;
       
   497 				return instanceAPI;
       
   498 			},
       
   499 
       
   500 			// create another clean instance of $LAB
       
   501 			sandbox:function(){
       
   502 				return create_sandbox();
       
   503 			}
       
   504 		};
       
   505 
       
   506 		return instanceAPI;
       
   507 	}
       
   508 
       
   509 	// create the main instance of $LAB
       
   510 	global.$LAB = create_sandbox();
       
   511 
       
   512 
       
   513 	/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
       
   514 	   NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?).
       
   515 	   
       
   516 	   The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does 
       
   517 	   proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked 
       
   518 	   document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. 
       
   519 	   For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or 
       
   520 	   fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs.
       
   521 	*/ 
       
   522 	(function(addEvent,domLoaded,handler){
       
   523 		if (document.readyState == null && document[addEvent]){
       
   524 			document.readyState = "loading";
       
   525 			document[addEvent](domLoaded,handler = function(){
       
   526 				document.removeEventListener(domLoaded,handler,false);
       
   527 				document.readyState = "complete";
       
   528 			},false);
       
   529 		}
       
   530 	})("addEventListener","DOMContentLoaded");
       
   531 
       
   532 })(this);/*
       
   533   mustache.js — Logic-less templates in JavaScript
       
   534 
       
   535   See http://mustache.github.com/ for more info.
       
   536 */
       
   537 
       
   538 var Mustache = function() {
       
   539   var Renderer = function() {};
       
   540 
       
   541   Renderer.prototype = {
       
   542     otag: "{{",
       
   543     ctag: "}}",
       
   544     pragmas: {},
       
   545     buffer: [],
       
   546     pragmas_implemented: {
       
   547       "IMPLICIT-ITERATOR": true
       
   548     },
       
   549     context: {},
       
   550 
       
   551     render: function(template, context, partials, in_recursion) {
       
   552       // reset buffer & set context
       
   553       if(!in_recursion) {
       
   554         this.context = context;
       
   555         this.buffer = []; // TODO: make this non-lazy
       
   556       }
       
   557 
       
   558       // fail fast
       
   559       if(!this.includes("", template)) {
       
   560         if(in_recursion) {
       
   561           return template;
       
   562         } else {
       
   563           this.send(template);
       
   564           return;
       
   565         }
       
   566       }
       
   567 
       
   568       template = this.render_pragmas(template);
       
   569       var html = this.render_section(template, context, partials);
       
   570       if(in_recursion) {
       
   571         return this.render_tags(html, context, partials, in_recursion);
       
   572       }
       
   573 
       
   574       this.render_tags(html, context, partials, in_recursion);
       
   575     },
       
   576 
       
   577     /*
       
   578       Sends parsed lines
       
   579     */
       
   580     send: function(line) {
       
   581       if(line !== "") {
       
   582         this.buffer.push(line);
       
   583       }
       
   584     },
       
   585 
       
   586     /*
       
   587       Looks for %PRAGMAS
       
   588     */
       
   589     render_pragmas: function(template) {
       
   590       // no pragmas
       
   591       if(!this.includes("%", template)) {
       
   592         return template;
       
   593       }
       
   594 
       
   595       var that = this;
       
   596       var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
       
   597             this.ctag, "g");
       
   598       return template.replace(regex, function(match, pragma, options) {
       
   599         if(!that.pragmas_implemented[pragma]) {
       
   600           throw({message: 
       
   601             "This implementation of mustache doesn't understand the '" +
       
   602             pragma + "' pragma"});
       
   603         }
       
   604         that.pragmas[pragma] = {};
       
   605         if(options) {
       
   606           var opts = options.split("=");
       
   607           that.pragmas[pragma][opts[0]] = opts[1];
       
   608         }
       
   609         return "";
       
   610         // ignore unknown pragmas silently
       
   611       });
       
   612     },
       
   613 
       
   614     /*
       
   615       Tries to find a partial in the curent scope and render it
       
   616     */
       
   617     render_partial: function(name, context, partials) {
       
   618       name = this.trim(name);
       
   619       if(!partials || partials[name] === undefined) {
       
   620         throw({message: "unknown_partial '" + name + "'"});
       
   621       }
       
   622       if(typeof(context[name]) != "object") {
       
   623         return this.render(partials[name], context, partials, true);
       
   624       }
       
   625       return this.render(partials[name], context[name], partials, true);
       
   626     },
       
   627 
       
   628     /*
       
   629       Renders inverted (^) and normal (#) sections
       
   630     */
       
   631     render_section: function(template, context, partials) {
       
   632       if(!this.includes("#", template) && !this.includes("^", template)) {
       
   633         return template;
       
   634       }
       
   635 
       
   636       var that = this;
       
   637       // CSW - Added "+?" so it finds the tighest bound, not the widest
       
   638       var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
       
   639               "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
       
   640               "\\s*", "mg");
       
   641 
       
   642       // for each {{#foo}}{{/foo}} section do...
       
   643       return template.replace(regex, function(match, type, name, content) {
       
   644         var value = that.find(name, context);
       
   645         if(type == "^") { // inverted section
       
   646           if(!value || that.is_array(value) && value.length === 0) {
       
   647             // false or empty list, render it
       
   648             return that.render(content, context, partials, true);
       
   649           } else {
       
   650             return "";
       
   651           }
       
   652         } else if(type == "#") { // normal section
       
   653           if(that.is_array(value)) { // Enumerable, Let's loop!
       
   654             return that.map(value, function(row) {
       
   655               return that.render(content, that.create_context(row),
       
   656                 partials, true);
       
   657             }).join("");
       
   658           } else if(that.is_object(value)) { // Object, Use it as subcontext!
       
   659             return that.render(content, that.create_context(value),
       
   660               partials, true);
       
   661           } else if(typeof value === "function") {
       
   662             // higher order section
       
   663             return value.call(context, content, function(text) {
       
   664               return that.render(text, context, partials, true);
       
   665             });
       
   666           } else if(value) { // boolean section
       
   667             return that.render(content, context, partials, true);
       
   668           } else {
       
   669             return "";
       
   670           }
       
   671         }
       
   672       });
       
   673     },
       
   674 
       
   675     /*
       
   676       Replace {{foo}} and friends with values from our view
       
   677     */
       
   678     render_tags: function(template, context, partials, in_recursion) {
       
   679       // tit for tat
       
   680       var that = this;
       
   681 
       
   682       var new_regex = function() {
       
   683         return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
       
   684           that.ctag + "+", "g");
       
   685       };
       
   686 
       
   687       var regex = new_regex();
       
   688       var tag_replace_callback = function(match, operator, name) {
       
   689         switch(operator) {
       
   690         case "!": // ignore comments
       
   691           return "";
       
   692         case "=": // set new delimiters, rebuild the replace regexp
       
   693           that.set_delimiters(name);
       
   694           regex = new_regex();
       
   695           return "";
       
   696         case ">": // render partial
       
   697           return that.render_partial(name, context, partials);
       
   698         case "{": // the triple mustache is unescaped
       
   699           return that.find(name, context);
       
   700         default: // escape the value
       
   701           return that.escape(that.find(name, context));
       
   702         }
       
   703       };
       
   704       var lines = template.split("\n");
       
   705       for(var i = 0; i < lines.length; i++) {
       
   706         lines[i] = lines[i].replace(regex, tag_replace_callback, this);
       
   707         if(!in_recursion) {
       
   708           this.send(lines[i]);
       
   709         }
       
   710       }
       
   711 
       
   712       if(in_recursion) {
       
   713         return lines.join("\n");
       
   714       }
       
   715     },
       
   716 
       
   717     set_delimiters: function(delimiters) {
       
   718       var dels = delimiters.split(" ");
       
   719       this.otag = this.escape_regex(dels[0]);
       
   720       this.ctag = this.escape_regex(dels[1]);
       
   721     },
       
   722 
       
   723     escape_regex: function(text) {
       
   724       // thank you Simon Willison
       
   725       if(!arguments.callee.sRE) {
       
   726         var specials = [
       
   727           '/', '.', '*', '+', '?', '|',
       
   728           '(', ')', '[', ']', '{', '}', '\\'
       
   729         ];
       
   730         arguments.callee.sRE = new RegExp(
       
   731           '(\\' + specials.join('|\\') + ')', 'g'
       
   732         );
       
   733       }
       
   734       return text.replace(arguments.callee.sRE, '\\$1');
       
   735     },
       
   736 
       
   737     /*
       
   738       find `name` in current `context`. That is find me a value
       
   739       from the view object
       
   740     */
       
   741     find: function(name, context) {
       
   742       name = this.trim(name);
       
   743 
       
   744       // Checks whether a value is thruthy or false or 0
       
   745       function is_kinda_truthy(bool) {
       
   746         return bool === false || bool === 0 || bool;
       
   747       }
       
   748 
       
   749       var value;
       
   750       if(is_kinda_truthy(context[name])) {
       
   751         value = context[name];
       
   752       } else if(is_kinda_truthy(this.context[name])) {
       
   753         value = this.context[name];
       
   754       }
       
   755 
       
   756       if(typeof value === "function") {
       
   757         return value.apply(context);
       
   758       }
       
   759       if(value !== undefined) {
       
   760         return value;
       
   761       }
       
   762       // silently ignore unkown variables
       
   763       return "";
       
   764     },
       
   765 
       
   766     // Utility methods
       
   767 
       
   768     /* includes tag */
       
   769     includes: function(needle, haystack) {
       
   770       return haystack.indexOf(this.otag + needle) != -1;
       
   771     },
       
   772 
       
   773     /*
       
   774       Does away with nasty characters
       
   775     */
       
   776     escape: function(s) {
       
   777       s = String(s === null ? "" : s);
       
   778       return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
       
   779         switch(s) {
       
   780         case "&": return "&amp;";
       
   781         case "\\": return "\\\\";
       
   782         case '"': return '&quot;';
       
   783         case "'": return '&#39;';
       
   784         case "<": return "&lt;";
       
   785         case ">": return "&gt;";
       
   786         default: return s;
       
   787         }
       
   788       });
       
   789     },
       
   790 
       
   791     // by @langalex, support for arrays of strings
       
   792     create_context: function(_context) {
       
   793       if(this.is_object(_context)) {
       
   794         return _context;
       
   795       } else {
       
   796         var iterator = ".";
       
   797         if(this.pragmas["IMPLICIT-ITERATOR"]) {
       
   798           iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
       
   799         }
       
   800         var ctx = {};
       
   801         ctx[iterator] = _context;
       
   802         return ctx;
       
   803       }
       
   804     },
       
   805 
       
   806     is_object: function(a) {
       
   807       return a && typeof a == "object";
       
   808     },
       
   809 
       
   810     is_array: function(a) {
       
   811       return Object.prototype.toString.call(a) === '[object Array]';
       
   812     },
       
   813 
       
   814     /*
       
   815       Gets rid of leading and trailing whitespace
       
   816     */
       
   817     trim: function(s) {
       
   818       return s.replace(/^\s*|\s*$/g, "");
       
   819     },
       
   820 
       
   821     /*
       
   822       Why, why, why? Because IE. Cry, cry cry.
       
   823     */
       
   824     map: function(array, fn) {
       
   825       if (typeof array.map == "function") {
       
   826         return array.map(fn);
       
   827       } else {
       
   828         var r = [];
       
   829         var l = array.length;
       
   830         for(var i = 0; i < l; i++) {
       
   831           r.push(fn(array[i]));
       
   832         }
       
   833         return r;
       
   834       }
       
   835     }
       
   836   };
       
   837 
       
   838   return({
       
   839     name: "mustache.js",
       
   840     version: "0.3.1-dev",
       
   841 
       
   842     /*
       
   843       Turns a template and view into HTML
       
   844     */
       
   845     to_html: function(template, view, partials, send_fun) {
       
   846       var renderer = new Renderer();
       
   847       if(send_fun) {
       
   848         renderer.send = send_fun;
       
   849       }
       
   850       renderer.render(template, view, partials);
       
   851       if(!send_fun) {
       
   852         return renderer.buffer.join("\n");
       
   853       }
       
   854     }
       
   855   });
       
   856 }();
       
   857 // Underscore.js 1.2.3
       
   858 // (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc.
       
   859 // Underscore is freely distributable under the MIT license.
       
   860 // Portions of Underscore are inspired or borrowed from Prototype,
       
   861 // Oliver Steele's Functional, and John Resig's Micro-Templating.
       
   862 // For all details and documentation:
       
   863 // http://documentcloud.github.com/underscore
       
   864 (function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
       
   865 c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c,
       
   866 h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.concat,H=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,I=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&
       
   867 define.amd?define("underscore",function(){return b}):s._=b;b.VERSION="1.2.3";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a,
       
   868 c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,
       
   869 c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c,
       
   870 b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});return e};var D=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,
       
   871 d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,
       
   872 computed:b})});return e.value};b.shuffle=function(a){var c=[],b;j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=
       
   873 function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-
       
   874 1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},
       
   875 []);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,
       
   876 c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
       
   877 var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
       
   878 c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
       
   879 a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=G.apply([a],arguments);return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=
       
   880 function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=I||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,
       
   881 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===
       
   882 Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)==
       
   883 "[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),function(c){J(c,
       
   884 b[c]=a[c])})};var K=0;b.uniqueId=function(a){var b=K++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,
       
   885 "'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},J=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);H.call(a,this._wrapped);return u(c.apply(b,
       
   886 a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this);
       
   887 /* main file */
       
   888 
       
   889 if ( window.IriSP === undefined && window.__IriSP === undefined ) { 
       
   890 	var IriSP = {}; 
       
   891 	var __IriSP = IriSP; /* for backward compatibility */
       
   892 }
       
   893 
       
   894 IriSP.loadLibs = function( libs, config, metadata_url, callback ) {
       
   895     // Localize jQuery variable
       
   896 		IriSP.jQuery = null;
       
   897     var $L = $LAB.script(libs.jQuery).script(libs.swfObject)
       
   898                 .script(libs.jQueryUI)
       
   899                                    
       
   900     if (config.player.type === "jwplayer") {
       
   901       // load our popcorn.js lookalike
       
   902       $L = $L.script(libs.jwplayer);
       
   903     } else {
       
   904       // load the real popcorn
       
   905       $L = $L.script(libs.popcorn).script(libs["popcorn.code"]);
       
   906       if (config.player.type === "youtube") {
       
   907         $L = $L.script(libs["popcorn.youtube"]);
       
   908       } 
       
   909       if (config.player.type === "vimeo")
       
   910         $L = $L.script(libs["popcorn.vimeo"]);
       
   911       
       
   912       /* do nothing for html5 */
       
   913     }       
       
   914     
       
   915     /* widget specific requirements */
       
   916     for (var idx in config.gui.widgets) {
       
   917       if (config.gui.widgets[idx].type === "PolemicWidget") {        
       
   918         $L.script(libs.raphael);
       
   919       }
       
   920     }
       
   921     
       
   922     // same for modules
       
   923     /*
       
   924     for (var idx in config.modules) {
       
   925       if (config.modules[idx].type === "PolemicWidget")
       
   926         $L.script(libs.raphaelJs);
       
   927     }
       
   928     */
       
   929 
       
   930     $L.wait(function() {
       
   931       IriSP.jQuery = window.jQuery.noConflict( true );
       
   932       IriSP._ = window._.noConflict();
       
   933       IriSP.underscore = IriSP._;
       
   934       
       
   935       var css_link_jquery = IriSP.jQuery( "<link>", { 
       
   936         rel: "stylesheet", 
       
   937         type: "text/css", 
       
   938         href: libs.cssjQueryUI,
       
   939         'class': "dynamic_css"
       
   940       } );
       
   941       var css_link_custom = IriSP.jQuery( "<link>", { 
       
   942         rel: "stylesheet", 
       
   943         type: "text/css", 
       
   944         href: config.gui.css,
       
   945         'class': "dynamic_css"
       
   946       } );
       
   947       
       
   948       css_link_jquery.appendTo('head');
       
   949       css_link_custom.appendTo('head');
       
   950           
       
   951       IriSP.setupDataLoader();
       
   952       IriSP.__dataloader.get(metadata_url, 
       
   953           function(data) {
       
   954             /* save the data so that we could re-use it to
       
   955                configure the video
       
   956             */
       
   957             IriSP.__jsonMetadata = data;
       
   958             callback.call(window) });
       
   959     });
       
   960 };
       
   961 IriSP.annotation_template = "{{! template for an annotation displayed in a segmentWidget }}<div title='{{divTitle}}' id='{{id}}'	class='Ldt-iri-chapter' 	style='left: {{startPixel}}px;          width: {{pxWidth}}px;          background-color:#{{hexa_color}};' 	></div>";
       
   962 IriSP.annotationWidget_template = "{{! template for the annotation widget }}<div class='Ldt-AnnotationsWidget'>  <!-- ugly div because we want to have a double border -->  <div class='Ldt-Annotation-DoubleBorder'>      <div class='Ldt-AnnotationContent'>        <div class='Ldt-AnnotationShareIcons'>         <a class='Ldt-fbShare' href=''><img src='{{img_dir}}/facebook.png' alt='share on facebook'></img></a>         <a class='Ldt-TwShare' href=''><img src='{{img_dir}}/twitter.png' alt='share on twitter'></img></a>         <a class='Ldt-GplusShare' href=''><img src='{{img_dir}}/google.png' alt='share on google+'></img></a>      </div>		  <div class='Ldt-SaTitle'></div>	  	<div class='Ldt-SaDescription'></div>    </div>  </div></div>";
       
   963 IriSP.annotation_loading_template = "{{! template shown while the annotation widget is loading }}<div id='Ldt-load-container'><div id='Ldt-loader'>&nbsp;</div> Chargement... </div>";
       
   964 IriSP.arrowWidget_template = "<div class='Ldt-arrowWidget'></div>";
       
   965 IriSP.overlay_marker_template = "{{! the template for the small bars which is z-indexed over our segment widget }}<div class='Ldt-SegmentPositionMarker' style='background-color: #F7268E;'></div>";
       
   966 IriSP.player_template = "{{! template for the radio player }}<div class='Ldt-controler demo'>	<div class='Ldt-LeftPlayerControls'>    <div class='Ldt-button Ldt-CtrlPlay'></div>		<div class='Ldt-button Ldt-CtrlAnnotate'></div>    <div class='Ldt-button Ldt-CtrlSearch'></div>	</div>		<div class='Ldt-RightPlayerControls'>    <div class='Ldt-Time'>      <div class='Ldt-ElapsedTime'></div>      <div class='Ldt-TimeSeparator'>/</div>      <div class='Ldt-TotalTime'></div>    </div>		<div class='Ldt-button Ldt-CtrlSound'></div>	</div></div>";
       
   967 IriSP.search_template = "{{! template for the search container }}<div class='LdtSearchContainer'	style='margin-left: {{margin_left}}; position: absolute; margin-top: -60px;'>	<div class='LdtSearch'		style='display: none; background-color: #EEE; width: 165px; boder: 1px; border-color: #CFCFCF; position: absolute; text-align: center;'>		<input class='LdtSearchInput'			style='margin-top: 2px; margin-bottom: 2px;' />	</div></div><div class='cleaner'></div>";
       
   968 IriSP.share_template = "{{! social network sharing template }}<a onclick='__IriSP.MyApiPlayer.share(\'delicious\');' title='partager avec delicious'><span class='share shareDelicious'>&nbsp;</span></a>		<a onclick='__IriSP.MyApiPlayer.share(\'facebook\');' title='partager avec facebook'> <span class='share shareFacebook'>&nbsp;</span></a><a onclick='__IriSP.MyApiPlayer.share(\'twitter\');' title='partager avec twitter'>  <span class='share shareTwitter'>&nbsp;</span></a><a onclick='__IriSP.MyApiPlayer.share(\'myspace\');' title='partager avec Myspace'>  <span class='share shareMySpace'>&nbsp;</span></a>";
       
   969 IriSP.sliderWidget_template = "{{! template for the slider widget - it's composed of two divs we one overlayed on top    of the other }}<div class='Ldt-sliderBackground'></div><div class='Ldt-sliderForeground'></div><div class='Ldt-sliderPositionMarker'></div>";
       
   970 IriSP.tooltip_template = "{{! template used by the jquery ui tooltip }}<div class='Ldt-tooltip'>  <div class='title'>{{title}}</div>  <div class='time'>{{begin}} : {{end}} </div>  <div class='description'>{{description}}</div></div>";
       
   971 IriSP.tooltipWidget_template = "{{! template for the tooltip widget }}<div class='tip'>	<div class='tipcolor' style='height:10px;width:10px'></div>	<div class='tiptext'></div>";
       
   972 IriSP.tweetWidget_template = "{{! template for the tweet widget }}<div class='Ldt-tweetWidget'>  <div class='Ldt-tweet-DoubleBorder'>      <img src='{{img_dir}}/minimize.png' class='Ldt-tweetWidgetKeepOpen' alt='dont minimize automatically'></img>      <img src='{{img_dir}}/minimize.png' class='Ldt-tweetWidgetMinimize' alt='minimize window'></img>      <div class='Ldt-tweetAvatar'></div>      <img src='{{img_dir}}/profile_arrow.png' class='Ldt-tweetAvatar-profileArrow'></img>      <div class='Ldt-tweetContents'></div>      <a href='' target='_blank' class='Ldt-Retweet'><div class='Ldt-RetweetIcon'></div> - Retweet </a>      <a href='' target='_blank' class='Ldt-TweetReply'><div class='Ldt-TweetReplyIcon'></div> - Reply</a>  </div></div>";/* wrapper that simulates popcorn.js because
       
   973    popcorn is a bit unstable at the time */
       
   974 
       
   975 IriSP.PopcornReplacement = {
       
   976   msgPump : {} /* used by jquery to receive and send messages */
       
   977 };
       
   978 
       
   979 IriSP.PopcornReplacement.media = { 
       
   980   "paused": true,
       
   981   "muted": false
       
   982 };
       
   983 
       
   984 IriSP.PopcornReplacement.listen = function(msg, callback) {
       
   985 //  IriSP.jQuery(IriSP.PopcornReplacement.msgPump).bind(msg, function(event, rest) { callback(rest); });
       
   986   if (!IriSP.PopcornReplacement.msgPump.hasOwnProperty(msg))
       
   987     IriSP.PopcornReplacement.msgPump[msg] = [];
       
   988 
       
   989   IriSP.PopcornReplacement.msgPump[msg].push(callback);
       
   990 };
       
   991 
       
   992 IriSP.PopcornReplacement.trigger = function(msg, params) {
       
   993 //  IriSP.jQuery(IriSP.PopcornReplacement.msgPump).trigger(msg, params);
       
   994   
       
   995   if (!IriSP.PopcornReplacement.msgPump.hasOwnProperty(msg))
       
   996     return;
       
   997 
       
   998   var d = IriSP.PopcornReplacement.msgPump[msg];
       
   999   for(var entry in d) {
       
  1000     d[entry].call(window, params);
       
  1001   }
       
  1002 
       
  1003 };
       
  1004 
       
  1005 IriSP.PopcornReplacement.guid = function(prefix) {
       
  1006   var str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
       
  1007       var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
       
  1008       return v.toString(16);
       
  1009    });
       
  1010 
       
  1011   return prefix + str;
       
  1012 };
       
  1013 
       
  1014 IriSP.PopcornReplacement.__initApi = function() {
       
  1015   IriSP.PopcornReplacement.trigger("loadedmetadata"); // we've done more than loading metadata of course,
       
  1016                                                       // but popcorn doesn't need to know more.
       
  1017   IriSP.PopcornReplacement.media.muted = jwplayer(IriSP.PopcornReplacement._container).getMute();
       
  1018 };
       
  1019 
       
  1020 IriSP.PopcornReplacement.jwplayer = function(container, options) {
       
  1021   IriSP.PopcornReplacement._container = container.slice(1); //eschew the '#'
       
  1022   options.events = {
       
  1023       onReady: IriSP.PopcornReplacement.__initApi,
       
  1024       onTime: IriSP.PopcornReplacement.__timeHandler,
       
  1025       onPlay: IriSP.PopcornReplacement.__playHandler,
       
  1026       onPause: IriSP.PopcornReplacement.__pauseHandler,
       
  1027       onSeek: IriSP.PopcornReplacement.__seekHandler 
       
  1028       }
       
  1029     
       
  1030   jwplayer(IriSP.PopcornReplacement._container).setup(options);
       
  1031   IriSP.PopcornReplacement.media.duration = options.duration;
       
  1032   return IriSP.PopcornReplacement;
       
  1033 };
       
  1034 
       
  1035 IriSP.PopcornReplacement.currentTime = function(time) {
       
  1036   if (typeof(time) === "undefined") {
       
  1037       return jwplayer(IriSP.PopcornReplacement._container).getPosition();            
       
  1038   } else {
       
  1039      var currentTime = +time;
       
  1040      jwplayer( IriSP.PopcornReplacement._container ).seek( currentTime );
       
  1041      IriSP.PopcornReplacement.trigger("seeked");
       
  1042      return jwplayer(IriSP.PopcornReplacement._container).getPosition();            
       
  1043   }
       
  1044 };
       
  1045 
       
  1046 IriSP.PopcornReplacement.play = function() {
       
  1047       IriSP.PopcornReplacement.media.paused = false;
       
  1048       IriSP.PopcornReplacement.trigger("play");
       
  1049 //      IriSP.PopcornReplacement.trigger("playing");
       
  1050       jwplayer( IriSP.PopcornReplacement._container ).play();
       
  1051 };
       
  1052     
       
  1053 IriSP.PopcornReplacement.pause = function() {
       
  1054       if ( !IriSP.PopcornReplacement.media.paused ) {
       
  1055         IriSP.PopcornReplacement.media.paused = true;
       
  1056         IriSP.PopcornReplacement.trigger( "pause" );
       
  1057         jwplayer( IriSP.PopcornReplacement._container ).pause();
       
  1058       }
       
  1059 };
       
  1060 
       
  1061 IriSP.PopcornReplacement.muted = function(val) {
       
  1062   if (typeof(val) !== "undefined") {
       
  1063 
       
  1064     if (jwplayer(IriSP.PopcornReplacement._container).getMute() !== val) {
       
  1065       if (val) {
       
  1066         jwplayer(IriSP.PopcornReplacement._container).setMute(true);
       
  1067         IriSP.PopcornReplacement.media.muted = true;
       
  1068       } else {
       
  1069         jwplayer( IriSP.PopcornReplacement._container ).setMute(false);
       
  1070         IriSP.PopcornReplacement.media.muted = false;
       
  1071       }
       
  1072 
       
  1073       IriSP.PopcornReplacement.trigger( "volumechange" );
       
  1074     }
       
  1075     
       
  1076     return jwplayer( IriSP.PopcornReplacement._container ).getMute();
       
  1077   } else {
       
  1078     return jwplayer( IriSP.PopcornReplacement._container ).getMute();
       
  1079   }
       
  1080 };
       
  1081 
       
  1082 IriSP.PopcornReplacement.mute = IriSP.PopcornReplacement.muted;
       
  1083 
       
  1084 IriSP.PopcornReplacement.__codes = [];
       
  1085 IriSP.PopcornReplacement.code = function(options) {
       
  1086   IriSP.PopcornReplacement.__codes.push(options);
       
  1087   return IriSP.PopcornReplacement;
       
  1088 };
       
  1089 
       
  1090 IriSP.PopcornReplacement.__runCode = function() {
       
  1091   var currentTime = jwplayer(IriSP.PopcornReplacement._container).getPosition();
       
  1092   var i = 0;
       
  1093   for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) {
       
  1094     var c = IriSP.PopcornReplacement.__codes[i];
       
  1095     if (currentTime == c.start) {
       
  1096       c.onStart();
       
  1097     }
       
  1098     
       
  1099     if (currentTime == c.end) {
       
  1100       c.onEnd();
       
  1101     }
       
  1102 
       
  1103   }
       
  1104 };
       
  1105 
       
  1106 /* called everytime the player updates itself 
       
  1107    (onTime event)
       
  1108  */
       
  1109 
       
  1110 IriSP.PopcornReplacement.__timeHandler = function(event) {
       
  1111   var pos = event.position;
       
  1112 
       
  1113   var i = 0;
       
  1114   for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) {
       
  1115      var c = IriSP.PopcornReplacement.__codes[i];
       
  1116      
       
  1117      if (pos >= c.start && pos < c.end && 
       
  1118          pos - 0.1 <= c.start) {       
       
  1119         c.onStart();
       
  1120      }
       
  1121  
       
  1122      if (pos > c.start && pos > c.end && 
       
  1123          pos - 0.1 <= c.end) {
       
  1124          console.log("eonedn");
       
  1125         c.onEnd();
       
  1126      }
       
  1127    
       
  1128   }
       
  1129  
       
  1130   IriSP.PopcornReplacement.trigger("timeupdate");
       
  1131 };
       
  1132 
       
  1133 IriSP.PopcornReplacement.__seekHandler = function(event) {
       
  1134   var i = 0;
       
  1135   
       
  1136   for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) {
       
  1137      var c = IriSP.PopcornReplacement.__codes[i];
       
  1138     
       
  1139      if (event.position >= c.start && event.position < c.end) {        
       
  1140         c.onEnd();
       
  1141      }         
       
  1142    }
       
  1143 
       
  1144    for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) {
       
  1145      var c = IriSP.PopcornReplacement.__codes[i];
       
  1146 
       
  1147      if (typeof(event.offset) === "undefined")
       
  1148        event.offset = 0;
       
  1149            
       
  1150      if (event.offset >= c.start && event.offset < c.end) { 
       
  1151        c.onStart();
       
  1152      }
       
  1153      
       
  1154    }
       
  1155 
       
  1156   IriSP.PopcornReplacement.trigger("timeupdate");
       
  1157 };
       
  1158 
       
  1159 
       
  1160 IriSP.PopcornReplacement.__playHandler = function(event) {
       
  1161   IriSP.PopcornReplacement.media.paused = false;
       
  1162   IriSP.PopcornReplacement.trigger("play");
       
  1163 };
       
  1164 
       
  1165 IriSP.PopcornReplacement.__pauseHandler = function(event) {
       
  1166   IriSP.PopcornReplacement.media.paused = true;
       
  1167   IriSP.PopcornReplacement.trigger("pause");
       
  1168 };
       
  1169 
       
  1170 IriSP.PopcornReplacement.roundTime = function() {
       
  1171   var currentTime = IriSP.PopcornReplacement.currentTime();
       
  1172   return Math.round(currentTime);
       
  1173 };
       
  1174 /* utils.js - various utils that don't belong anywhere else */
       
  1175 
       
  1176 /* trace function, for debugging */
       
  1177 
       
  1178 IriSP.traceNum = 0;
       
  1179 IriSP.trace = function( msg, value ) {
       
  1180 /*
       
  1181 	if( IriSP.config.gui.debug === true ) {
       
  1182 		IriSP.traceNum += 1;
       
  1183 		IriSP.jQuery( "<div>"+IriSP.traceNum+" - "+msg+" : "+value+"</div>" ).appendTo( "#Ldt-output" );
       
  1184 	}
       
  1185 */
       
  1186 };
       
  1187 
       
  1188 /* used in callbacks - because in callbacks we lose "this",
       
  1189    we need to have a special function which wraps "this" in 
       
  1190    a closure. This way, the 
       
  1191 */   
       
  1192 IriSP.wrap = function (obj, fn) {
       
  1193   return function() {    
       
  1194     var args = Array.prototype.slice.call(arguments, 0);
       
  1195     return fn.apply(obj, args);
       
  1196   }
       
  1197 }
       
  1198 
       
  1199 /* convert a time to a percentage in the media */
       
  1200 IriSP.timeToPourcent = function(time, timetotal){
       
  1201 	var time = Math.abs(time);
       
  1202   var timetotal = Math.abs(timetotal);
       
  1203   
       
  1204 	return Math.floor((time/timetotal) * 100);
       
  1205 };
       
  1206 
       
  1207 IriSP.padWithZeros = function(num) {
       
  1208   if (Math.abs(num) < 10) {
       
  1209     return "0" + num.toString();
       
  1210   } else {
       
  1211     return num.toString();
       
  1212   }
       
  1213 };
       
  1214 /* convert a number of seconds to a tuple of the form 
       
  1215    [hours, minutes, seconds]
       
  1216 */
       
  1217 IriSP.secondsToTime = function(secs) {  
       
  1218   var hours = Math.abs(parseInt( secs / 3600 ) % 24);
       
  1219   var minutes = Math.abs(parseInt( secs / 60 ) % 60);
       
  1220   var seconds = parseFloat(Math.abs(secs % 60).toFixed(0));
       
  1221   
       
  1222   var toString_fn = function() {
       
  1223     var ret = "";
       
  1224     if (hours > 0)
       
  1225        ret = IriSP.padWithZeros(this.hours) + ":";
       
  1226     ret += IriSP.padWithZeros(this.minutes) + ":" + IriSP.padWithZeros(this.seconds);
       
  1227 
       
  1228     return ret;
       
  1229   }
       
  1230   return {"hours" : hours, "minutes" : minutes, "seconds" : seconds, toString: toString_fn};
       
  1231 };
       
  1232 
       
  1233 IriSP.secondsToString
       
  1234 
       
  1235 /* format a tweet - replaces @name by a link to the profile, #hashtag, etc. */
       
  1236 IriSP.formatTweet = function(tweet) {
       
  1237   /*
       
  1238     an array of arrays which hold a regexp and its replacement.
       
  1239   */
       
  1240   var regExps = [
       
  1241     /* copied from http://codegolf.stackexchange.com/questions/464/shortest-url-regex-match-in-javascript/480#480 */
       
  1242     [/((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi, "<a href='$1'>$1</a>"],
       
  1243     [/@(\w+)/gi, "<a href='http://twitter.com/$1'>@$1</a>"], // matches a @handle
       
  1244     [/#(\w+)/gi, "<a href='http://twitter.com/search?q=%23$1'>#$1</a>"],// matches a hashtag
       
  1245     [/(\+\+)/gi, "<span class='Ldt-PolemicPlusPlus'>$1</span>"],
       
  1246     [/(--)/gi, "<span class='Ldt-PolemicMinusMinus'>$1</span>"],
       
  1247     [/(==)/gi, "<span class='Ldt-PolemicEqualEqual'>$1</span>"],
       
  1248     [/(\?\?)/gi, "<span class='Ldt-PolemicQuestion'>$1</span>"]
       
  1249   ]; 
       
  1250 
       
  1251   var i = 0;
       
  1252   for(i = 0; i < regExps.length; i++) {
       
  1253      tweet = tweet.replace(regExps[i][0], regExps[i][1]);
       
  1254   }
       
  1255   
       
  1256   return tweet;
       
  1257 };
       
  1258 
       
  1259 IriSP.countProperties = function(obj) {
       
  1260     var count = 0;
       
  1261 
       
  1262     for(var prop in obj) {
       
  1263         if(obj.hasOwnProperty(prop))
       
  1264                 ++count;
       
  1265     }
       
  1266 
       
  1267     return count;
       
  1268 };
       
  1269 
       
  1270 // conversion de couleur Decimal vers HexaDecimal || 000 si fff
       
  1271 IriSP.DEC_HEXA_COLOR = function (dec) {
       
  1272 	 var hexa='0123456789ABCDEF';
       
  1273    var hex='';
       
  1274 	 var tmp;
       
  1275 	 while (dec>15){
       
  1276 		  tmp = dec-(Math.floor(dec/16))*16;
       
  1277 		  hex = hexa.charAt(tmp)+hex;
       
  1278 		  dec = Math.floor(dec/16);
       
  1279 	 }
       
  1280 	 hex = hexa.charAt(dec)+hex;	 
       
  1281 	 return(hex);
       
  1282 };
       
  1283 
       
  1284 /* shortcut to have global variables in templates */
       
  1285 IriSP.templToHTML = function(template, values) {
       
  1286   var params = IriSP.jQuery.extend(IriSP.default_templates_vars, values);
       
  1287   return Mustache.to_html(template, params);
       
  1288 };
       
  1289 
       
  1290 /* we need to be stricter than encodeURIComponent,
       
  1291    because of twitter
       
  1292 */  
       
  1293 IriSP.encodeURI = function(str) {
       
  1294   return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').  
       
  1295                                  replace(/\)/g, '%29').replace(/\*/g, '%2A');  
       
  1296 }  
       
  1297 
       
  1298 IriSP.__guidCounter = 0;
       
  1299 IriSP.guid = function(prefix) {
       
  1300   IriSP.__guidCounter += 1;
       
  1301   return prefix + IriSP.__guidCounter;
       
  1302 };
       
  1303 /* for ie compatibility
       
  1304 if (Object.prototype.__defineGetter__&&!Object.defineProperty) {
       
  1305    Object.defineProperty=function(obj,prop,desc) {
       
  1306       if ("get" in desc) obj.__defineGetter__(prop,desc.get);
       
  1307       if ("set" in desc) obj.__defineSetter__(prop,desc.set);
       
  1308    }
       
  1309 }
       
  1310 */
       
  1311 /* data.js - this file deals with how the players gets and sends data */
       
  1312 
       
  1313 IriSP.DataLoader = function() {
       
  1314   this._cache = {};
       
  1315   
       
  1316   /*
       
  1317     A structure to hold callbacks for specific urls. We need it because
       
  1318     ajax calls are asynchronous, so it means that sometimes we ask
       
  1319     multiple times for a ressource because the first call hasn't been
       
  1320     received yet.
       
  1321   */
       
  1322   this._callbacks = {};
       
  1323 };
       
  1324 
       
  1325 IriSP.DataLoader.prototype.get = function(url, callback) {
       
  1326 
       
  1327   var base_url = url.split("&")[0]
       
  1328   if (this._cache.hasOwnProperty(base_url)) {
       
  1329     callback(this._cache[base_url]);
       
  1330   } else {  
       
  1331     if (!this._callbacks.hasOwnProperty(base_url)) {
       
  1332       this._callbacks[base_url] = [];
       
  1333       this._callbacks[base_url].push(callback);   
       
  1334       /* we need a closure because this gets lost when it's called back */
       
  1335   
       
  1336       // uncomment you don't want to use caching.
       
  1337       // IriSP.jQuery.get(url, callback);
       
  1338       
       
  1339       var func = function(data) {
       
  1340                   this._cache[base_url] = data;                                
       
  1341                   var i = 0;
       
  1342                   
       
  1343                   for (i = 0; i < this._callbacks[base_url].length; i++) {
       
  1344                     this._callbacks[base_url][i](this._cache[base_url]);                                  
       
  1345                   }
       
  1346       };
       
  1347       
       
  1348       /* automagically choose between json and jsonp */
       
  1349       if (url.indexOf(document.location.hostname) === -1 &&
       
  1350           url.indexOf("http://") !== -1 /* not a relative url */ ) {
       
  1351         // we contacting a foreign domain, use JSONP
       
  1352 
       
  1353         IriSP.jQuery.get(url, {}, IriSP.wrap(this, func), "jsonp");
       
  1354       } else {
       
  1355 
       
  1356         // otherwise, hey, whatever rows your boat
       
  1357         IriSP.jQuery.get(url, IriSP.wrap(this, func));
       
  1358       }
       
  1359     
       
  1360     } else {
       
  1361       /* simply push the callback - it'll get called when the ressource
       
  1362          has been received */
       
  1363       
       
  1364       this._callbacks[base_url].push(callback);   
       
  1365    
       
  1366     }
       
  1367   }
       
  1368 }
       
  1369 
       
  1370 /* the base abstract "class" */
       
  1371 IriSP.Serializer = function(DataLoader, url) {
       
  1372   this._DataLoader = DataLoader;
       
  1373   this._url = url;
       
  1374   this._data = [];
       
  1375 };
       
  1376 
       
  1377 IriSP.Serializer.prototype.serialize = function(data) { };
       
  1378 IriSP.Serializer.prototype.deserialize = function(data) {};
       
  1379 
       
  1380 IriSP.Serializer.prototype.currentMedia = function() {  
       
  1381 };
       
  1382 
       
  1383 IriSP.Serializer.prototype.sync = function(callback) {  
       
  1384   callback.call(this, this._data);  
       
  1385 };
       
  1386 
       
  1387 IriSP.SerializerFactory = function(DataLoader) {
       
  1388   this._dataloader = DataLoader;
       
  1389 };
       
  1390 
       
  1391 IriSP.SerializerFactory.prototype.getSerializer = function(metadataOptions) {
       
  1392   /* This function returns serializer set-up with the correct
       
  1393      configuration - takes a metadata struct describing the metadata source
       
  1394   */
       
  1395   
       
  1396   if (metadataOptions === undefined)
       
  1397     /* return an empty serializer */
       
  1398     return IriSP.Serializer("", "");
       
  1399             
       
  1400   switch(metadataOptions.type) {
       
  1401     case "json":
       
  1402       return new IriSP.JSONSerializer(this._dataloader, metadataOptions.src);
       
  1403       break;
       
  1404     
       
  1405     case "dummy": /* only used for unit testing - not defined in production */
       
  1406       return new IriSP.MockSerializer(this._dataloader, metadataOptions.src);
       
  1407       break;
       
  1408     
       
  1409     case "empty":
       
  1410       return new IriSP.Serializer("", "empty");
       
  1411       break;
       
  1412       
       
  1413     default:      
       
  1414       return undefined;
       
  1415   }
       
  1416 };
       
  1417 /* site.js - all our site-dependent config : player chrome, cdn locations, etc...*/
       
  1418 
       
  1419 IriSP.lib = { 
       
  1420 		jQuery : "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js",
       
  1421 		jQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/jquery-ui.js",
       
  1422 		jQueryToolTip : "http://cdn.jquerytools.org/1.2.4/all/jquery.tools.min.js",
       
  1423 		swfObject : "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js",
       
  1424 		cssjQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/themes/base/jquery-ui.css",
       
  1425     popcorn : "/mdp/src/js/libs/popcorn.js",
       
  1426     jwplayer : "/mdp/src/js/libs/jwplayer.js",
       
  1427     popcornReplacement: "/mdp/src/js/libs/pop.js",
       
  1428     raphael: "/mdp/src/js/libs/raphael.js",
       
  1429     "popcorn.mediafragment" : "/mdp/src/js/libs/popcorn.mediafragment.js",
       
  1430     "popcorn.code" : "/mdp/src/js/libs/popcorn.code.js",
       
  1431     "popcorn.jwplayer": "/mdp/src/js/libs/popcorn.jwplayer.js",
       
  1432     "popcorn.youtube": "/mdp/src/js/libs/popcorn.youtube.js"
       
  1433 };
       
  1434 
       
  1435 //Player Configuration 
       
  1436 IriSP.config = undefined;
       
  1437 
       
  1438 IriSP.widgetsDefaults = {
       
  1439   "LayoutManager" : {spacer_div_height : "0px" },
       
  1440   "PlayerWidget" : {},
       
  1441   "AnnotationsWidget": {
       
  1442     "share_text" : "I'm watching ",     
       
  1443     "fb_link" : "http://www.facebook.com/share.php?u=",
       
  1444     "tw_link" : "http://twitter.com/home?status=",
       
  1445     "gplus_link" : ""
       
  1446     },
       
  1447   "TweetsWidget" : {
       
  1448       default_profile_picture : "https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png",
       
  1449       tweet_display_period: 10000 // how long do we show a tweet ?
       
  1450   },
       
  1451   "SliderWidget" : {
       
  1452       minimize_period: 850 // how long does the slider stays maximized after the user leaves the zone ?
       
  1453   },
       
  1454   "Main" : {
       
  1455       autoplay: true
       
  1456   }
       
  1457   
       
  1458 };
       
  1459 
       
  1460 IriSP.paths = {
       
  1461 //  "imgs": "/tweetlive/res/metadataplayer/src/css/imgs"
       
  1462   "imgs": "/mdp/src/css/imgs"
       
  1463 };
       
  1464 IriSP.default_templates_vars = {
       
  1465   "img_dir" : IriSP.paths.imgs 
       
  1466 };
       
  1467 
       
  1468 /* ui.js - ui related functions */
       
  1469 
       
  1470 /* FIXME: use an sharing library */
       
  1471 IriSP.LdtShareTool = IriSP.share_template; /* the contents come from share.html */
       
  1472 
       
  1473 IriSP.createPlayerChrome = function(){
       
  1474 	var width = IriSP.config.gui.width;
       
  1475 	var height = IriSP.config.gui.height;
       
  1476 	var heightS = IriSP.config.gui.height-20;
       
  1477 	
       
  1478 	// AUDIO  */
       
  1479 	// PB dans le html : ; 
       
  1480 	IriSP.trace( "__IriSP.createMyHtml",IriSP.config.gui.container );
       
  1481 
       
  1482 	
       
  1483 	/* FIXME : factor this in another file */
       
  1484 	if( IriSP.config.gui.mode=="radio" ){
       
  1485 
       
  1486 		IriSP.jQuery( "#"+IriSP.config.gui.container ).before(IriSP.search_template);
       
  1487 		var radioPlayer = Mustache.to_html(IriSP.radio_template, {"share_template" : IriSP.share_template});
       
  1488 		IriSP.jQuery(radioPlayer).appendTo("#"+IriSP.config.gui.container);
       
  1489 
       
  1490 		// special tricks for IE 7
       
  1491 		if (IriSP.jQuery.browser.msie==true && IriSP.jQuery.browser.version=="7.0"){
       
  1492 			//LdtSearchContainer
       
  1493 			//__IriSP.jQuery("#LdtPlayer").attr("margin-top","50px");
       
  1494 			IriSP.jQuery("#Ldt-Root").css("padding-top","25px");
       
  1495 			IriSP.trace("__IriSP.createHtml","IE7 SPECIAL ");
       
  1496 		}
       
  1497 	} else if(IriSP.config.gui.mode=="video") {
       
  1498 	
       
  1499 		var videoPlayer = Mustache.to_html(IriSP.video_template, {"share_template" : IriSP.share_template, "heightS" : heightS});
       
  1500 		IriSP.jQuery(videoPlayer).appendTo("#"+IriSP.config.gui.container);
       
  1501 	}
       
  1502 	
       
  1503 	IriSP.jQuery("#Ldt-Annotations").width(width-(75*2));
       
  1504 	IriSP.jQuery("#Ldt-Show-Arrow-container").width(width-(75*2));
       
  1505 	IriSP.jQuery("#Ldt-ShowAnnotation-audio").width(width-10);
       
  1506 	IriSP.jQuery("#Ldt-ShowAnnotation-video").width(width-10);
       
  1507 	IriSP.jQuery("#Ldt-SaKeyword").width(width-10);
       
  1508 	IriSP.jQuery("#Ldt-controler").width(width-10);
       
  1509 	IriSP.jQuery("#Ldt-Control").attr("z-index","100");
       
  1510 	IriSP.jQuery("#Ldt-controler").hide();
       
  1511 	
       
  1512 	IriSP.jQuery(IriSP.annotation_loading_template).appendTo("#Ldt-ShowAnnotation-audio");
       
  1513 
       
  1514 	if(IriSP.config.gui.mode=='radio'){
       
  1515 		IriSP.jQuery("#Ldt-load-container").attr("width",IriSP.config.gui.width);
       
  1516 	}
       
  1517 	// Show or not the output
       
  1518 	if(IriSP.config.gui.debug===true){
       
  1519 		IriSP.jQuery("#Ldt-output").show();
       
  1520 	} else {
       
  1521 		IriSP.jQuery("#Ldt-output").hide();
       
  1522 	}
       
  1523 	
       
  1524 };
       
  1525 
       
  1526 
       
  1527 /* create the buttons and the slider   */
       
  1528 IriSP.createInterface = function( width, height, duration ) {
       
  1529 		
       
  1530 		IriSP.jQuery( "#Ldt-controler" ).show();
       
  1531 		//__IriSP.jQuery("#Ldt-Root").css('display','visible');
       
  1532 		IriSP.trace( "__IriSP.createInterface" , width+","+height+","+duration+"," );
       
  1533 		
       
  1534 		IriSP.jQuery( "#Ldt-ShowAnnotation").click( function () { 
       
  1535 			 //__IriSP.jQuery(this).slideUp(); 
       
  1536 		} );
       
  1537 
       
  1538 		var LdtpPlayerY = IriSP.jQuery("#Ldt-PlaceHolder").attr("top");
       
  1539 		var LdtpPlayerX = IriSP.jQuery("#Ldt-PlaceHolder").attr("left");
       
  1540 		
       
  1541 		IriSP.jQuery( "#slider-range-min" ).slider( { //range: "min",
       
  1542 			value: 0,
       
  1543 			min: 1,
       
  1544 			max: duration/1000,//1:54:52.66 = 3600+3240+
       
  1545 			step: 0.1,
       
  1546 			slide: function(event, ui) {
       
  1547 				
       
  1548 				//__IriSP.jQuery("#amount").val(ui.value+" s");
       
  1549 				//player.sendEvent('SEEK', ui.value)
       
  1550 				IriSP.MyApiPlayer.seek(ui.value);
       
  1551 				//changePageUrlOffset(ui.value);
       
  1552 				//player.sendEvent('PAUSE')
       
  1553 			}
       
  1554 		} );
       
  1555 		
       
  1556 		IriSP.trace("__IriSP.createInterface","ICI");
       
  1557 		IriSP.jQuery("#amount").val(IriSP.jQuery("#slider-range-min").slider("value")+" s");
       
  1558 		IriSP.jQuery(".Ldt-Control1 button:first").button({
       
  1559 			icons: {
       
  1560 				primary: 'ui-icon-play'
       
  1561 			},
       
  1562 			text: false
       
  1563 		}).next().button({
       
  1564 			icons: {
       
  1565 				primary: 'ui-icon-seek-next'
       
  1566 			},
       
  1567 			 text: false
       
  1568 		});
       
  1569 		IriSP.jQuery(".Ldt-Control2 button:first").button({
       
  1570 			icons: {
       
  1571 				primary: 'ui-icon-search'//,
       
  1572 				//secondary: 'ui-icon-volume-off'
       
  1573 			},
       
  1574 			text: false
       
  1575 		}).next().button({
       
  1576 			icons: {
       
  1577 				primary: 'ui-icon-volume-on'
       
  1578 			},
       
  1579 			 text: false
       
  1580 		});
       
  1581 
       
  1582 		// /!\ PB A MODIFIER 
       
  1583 		//__IriSP.MyTags.draw();
       
  1584 		IriSP.trace("__IriSP.createInterface","ICI2");
       
  1585 		IriSP.jQuery( "#ldt-CtrlPlay" ).attr( "style", "background-color:#CD21C24;" );
       
  1586 		
       
  1587 		IriSP.jQuery( "#Ldt-load-container" ).hide();
       
  1588 		
       
  1589 		if( IriSP.config.gui.mode=="radio" & IriSP.jQuery.browser.msie != true ) {
       
  1590 			IriSP.jQuery( "#Ldtplayer1" ).attr( "height", "0" );
       
  1591 		}
       
  1592 		IriSP.trace( "__IriSP.createInterface" , "3" );
       
  1593 
       
  1594 		IriSP.trace( "__IriSP.createInterface", "END" );
       
  1595 		
       
  1596 	};
       
  1597 /* the widget classes and definitions */
       
  1598 
       
  1599 IriSP.Widget = function(Popcorn, config, Serializer) {
       
  1600 
       
  1601   if (config === undefined || config === null) {
       
  1602     config = {}
       
  1603   }
       
  1604   
       
  1605   this._Popcorn = Popcorn;
       
  1606   this._config = config;  
       
  1607   this._serializer = Serializer;
       
  1608   
       
  1609   if (config.hasOwnProperty("container")) {
       
  1610      this._id = config.container;
       
  1611      this.selector = IriSP.jQuery("#" + this._id);
       
  1612   }
       
  1613 
       
  1614   if (config.hasOwnProperty("spacer")) {
       
  1615      this._spacerId = config.spacer;
       
  1616      this.spacer = IriSP.jQuery("#" + this._spacerId);
       
  1617   }
       
  1618 
       
  1619 
       
  1620   if (config.hasOwnProperty("width")) {
       
  1621      // this.width and not this._width because we consider it public.
       
  1622      this.width = config.width;     
       
  1623   }
       
  1624   
       
  1625   if (config.hasOwnProperty("height")) {    
       
  1626      this.height = config.height;     
       
  1627   }
       
  1628   
       
  1629   if (config.hasOwnProperty("heightmax")) {
       
  1630      this.heightmax = config.heightmax;     
       
  1631   }
       
  1632 
       
  1633   if (config.hasOwnProperty("widthmax")) {
       
  1634      this.widthmax = config.widthmax;     
       
  1635   }
       
  1636   
       
  1637 };
       
  1638 
       
  1639 IriSP.Widget.prototype.draw = function() {
       
  1640   /* implemented by "sub-classes" */  
       
  1641 };
       
  1642 
       
  1643 IriSP.Widget.prototype.redraw = function() {
       
  1644   /* implemented by "sub-classes" */  
       
  1645 };
       
  1646 /* modules are non-graphical entities, similar to widgets */
       
  1647 
       
  1648 IriSP.Module = function(Popcorn, config, Serializer) {
       
  1649 
       
  1650   if (config === undefined || config === null) {
       
  1651     config = {}
       
  1652   }
       
  1653   
       
  1654   this._Popcorn = Popcorn;
       
  1655   this._config = config;  
       
  1656   this._serializer = Serializer;
       
  1657 };
       
  1658 /* layout.js - very basic layout management */
       
  1659 
       
  1660 /*
       
  1661   a layout manager manages a div and the layout of objects
       
  1662   inside it.
       
  1663 */
       
  1664 
       
  1665 IriSP.LayoutManager = function(options) {
       
  1666     this._Popcorn = null;
       
  1667     this._widgets = [];
       
  1668     
       
  1669     this._div = "LdtPlayer";
       
  1670     this._width = 640;
       
  1671     
       
  1672     if (options === undefined) {
       
  1673       options = {};
       
  1674     };
       
  1675     
       
  1676     if (options.hasOwnProperty('container')) {
       
  1677       this._div = options.container;
       
  1678     }
       
  1679 
       
  1680     if (options.hasOwnProperty('width')) {
       
  1681       this._width = options.width;
       
  1682     }    
       
  1683     
       
  1684     if (options.hasOwnProperty('height')) {
       
  1685       this._height = options.height;
       
  1686     } 
       
  1687     
       
  1688     /* this is a shortcut */
       
  1689     this.selector = IriSP.jQuery("#" + this._div);
       
  1690     
       
  1691     this.selector.css("width", this._width);
       
  1692     
       
  1693     if (this._height !== undefined)
       
  1694       this.selector.css("height", this._height);
       
  1695 };
       
  1696 
       
  1697 /* we need this special setter because of a chicken and egg problem :
       
  1698    we want the manager to use popcorn but the popcorn div will be managed
       
  1699    by the manager. So we need a way to set the instance the manager uses
       
  1700 */
       
  1701    
       
  1702 IriSP.LayoutManager.prototype.setPopcornInstance = function(popcorn) {
       
  1703     this._Popcorn = popcorn;
       
  1704 }
       
  1705 
       
  1706 /* stem is a string to append to the id of the widget */
       
  1707 IriSP.LayoutManager.prototype.createDiv = function(stem) {
       
  1708     if (typeof(stem) === "undefined")
       
  1709        stem = "";
       
  1710 
       
  1711     var newDiv = IriSP.guid(this._div + "_widget_" + stem + "_");
       
  1712     var spacerDiv = IriSP.guid("LdtPlayer_spacer_");
       
  1713     this._widgets.push(newDiv);
       
  1714 
       
  1715     var divTempl = "<div id='{{id}}' style='width: {{width}}px; position: relative;'></div";
       
  1716     var spacerTempl = "<div id='{{spacer_id}}' style='width: {{width}}px; position: relative; height: {{spacer_div_height}};'></div";
       
  1717     
       
  1718     var divCode = Mustache.to_html(divTempl, {id: newDiv, width: this._width});
       
  1719     var spacerCode = Mustache.to_html(spacerTempl, {spacer_id: spacerDiv, width: this._width,
       
  1720                                                     spacer_div_height: IriSP.widgetsDefaults.LayoutManager.spacer_div_height });
       
  1721 
       
  1722     this.selector.append(divCode);
       
  1723     this.selector.append(spacerCode);
       
  1724 
       
  1725     return [newDiv, spacerDiv];
       
  1726 };
       
  1727 /* init.js - initialization and configuration of Popcorn and the widgets
       
  1728 exemple json configuration:
       
  1729  
       
  1730  */
       
  1731 
       
  1732 IriSP.setupDataLoader = function() {
       
  1733   /* we set it up separately because we need to
       
  1734      get data at the very beginning, for instance when
       
  1735      setting up the video */
       
  1736   IriSP.__dataloader = new IriSP.DataLoader();
       
  1737 };
       
  1738 
       
  1739 IriSP.configurePopcorn = function (layoutManager, options) {
       
  1740     var pop;
       
  1741     var ret = layoutManager.createDiv(); 
       
  1742     var containerDiv = ret[0];
       
  1743     
       
  1744     switch(options.type) {
       
  1745       /*
       
  1746         todo : dynamically create the div/video tag which
       
  1747         will contain the video.
       
  1748       */
       
  1749       case "html5":
       
  1750            var tmpId = Popcorn.guid("video"); 
       
  1751            IriSP.jQuery("#" + containerDiv).append("<video src='" + options.file + "' id='" + tmpId + "'></video>");
       
  1752 
       
  1753            if (options.hasOwnProperty("width"))
       
  1754              IriSP.jQuery("#" + containerDiv).css("width", options.width);
       
  1755            
       
  1756            if (options.hasOwnProperty("height"))
       
  1757              IriSP.jQuery("#" + containerDiv).css("height", options.height);
       
  1758 
       
  1759            pop = Popcorn("#" + tmpId);
       
  1760         break;
       
  1761         
       
  1762       case "jwplayer":
       
  1763           var opts = IriSP.jQuery.extend({}, options);
       
  1764           delete opts.container;
       
  1765 
       
  1766           if (options.provider === "rtmp") {
       
  1767             /* exit if we can't access the metadata */
       
  1768             if (typeof(IriSP.__jsonMetadata) === "undefined") {
       
  1769                 break;
       
  1770             };
       
  1771 
       
  1772 
       
  1773             // the json format is totally illogical
       
  1774             opts.streamer = IriSP.__jsonMetadata["medias"][0]["meta"]["item"]["value"];
       
  1775             var source = IriSP.__jsonMetadata["medias"][0]["href"];
       
  1776 
       
  1777             // the source if a full url but jwplayer wants an url relative to the
       
  1778             // streamer url, so we've got to remove the common part.
       
  1779             opts.file = source.slice(opts.streamer.length);
       
  1780           } else {
       
  1781             /* other providers type, video for instance -
       
  1782                pass everything as is */
       
  1783           }
       
  1784 
       
  1785           pop = IriSP.PopcornReplacement.jwplayer("#" + containerDiv, opts);
       
  1786         break;
       
  1787       
       
  1788       case "youtube":
       
  1789           var opts = IriSP.jQuery.extend({}, options);
       
  1790           delete opts.container;
       
  1791           opts.controls = 0;
       
  1792           opts.autostart = false;
       
  1793           templ = "width: {{width}}px; height: {{height}}px;";
       
  1794           var str = Mustache.to_html(templ, {width: opts.width, height: opts.height});    
       
  1795           // Popcorn.youtube wants us to specify the size of the player in the style attribute of its container div.
       
  1796           IriSP.jQuery("#" + containerDiv).attr("style", str);
       
  1797           
       
  1798           pop = Popcorn.youtube("#" + containerDiv, opts.video, opts);
       
  1799         break;
       
  1800         
       
  1801       default:
       
  1802         pop = undefined;
       
  1803     };
       
  1804     
       
  1805     return pop;
       
  1806 };
       
  1807 
       
  1808 IriSP.configureWidgets = function (popcornInstance, layoutManager, guiOptions) {
       
  1809  
       
  1810   var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader);
       
  1811   var params = {width: guiOptions.width, height: guiOptions.height};
       
  1812 
       
  1813   var ret_widgets = [];
       
  1814   var index;
       
  1815   
       
  1816   for (index = 0; index < guiOptions.widgets.length; index++) {    
       
  1817     var widgetConfig = guiOptions.widgets[index];
       
  1818     var widget = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, widgetConfig);
       
  1819     ret_widgets.push(widget);
       
  1820    
       
  1821   };
       
  1822 
       
  1823   return ret_widgets;
       
  1824 };
       
  1825 
       
  1826 IriSP.configureModules = function (popcornInstance, modulesList) {
       
  1827  
       
  1828   var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader);
       
  1829   var ret_modules = [];
       
  1830   var index;
       
  1831   
       
  1832   for (index = 0; index < modulesList.length; index++) {    
       
  1833     var moduleConfig = modulesList[index];
       
  1834     
       
  1835     var serializer = serialFactory.getSerializer(moduleConfig.metadata);
       
  1836     var module = new IriSP[moduleConfig.type](popcornInstance, moduleConfig, serializer);    
       
  1837     ret_modules.push(module);
       
  1838   };
       
  1839 
       
  1840   return ret_modules;
       
  1841 };
       
  1842 
       
  1843 IriSP.instantiateWidget = function(popcornInstance, serialFactory, layoutManager, widgetConfig) {
       
  1844     /* create div returns us a container for the widget and a spacer */
       
  1845     var ret = layoutManager.createDiv(widgetConfig.type);        
       
  1846     var container = ret[0];
       
  1847     var spacer = ret[1];
       
  1848 
       
  1849     var arr = IriSP.jQuery.extend({}, widgetConfig);
       
  1850     arr.container = container;
       
  1851     arr.spacer = spacer;
       
  1852     
       
  1853     var serializer = serialFactory.getSerializer(widgetConfig.metadata);    
       
  1854     
       
  1855     if (typeof serializer == "undefined")   
       
  1856       debugger;
       
  1857     
       
  1858     // instantiate the object passed as a string
       
  1859     var widget = new IriSP[widgetConfig.type](popcornInstance, arr, serializer);    
       
  1860     
       
  1861     if (widgetConfig.hasOwnProperty("requires")) {
       
  1862       // also create the widgets this one depends on.
       
  1863       // the dependency widget is available in the parent widget context as
       
  1864       // this.WidgetName (for instance, this.TipWidget);
       
  1865       
       
  1866       var i = 0;
       
  1867       for(i = 0; i < widgetConfig.requires.length; i++) {
       
  1868         var widgetName = widgetConfig.requires[i]["type"];
       
  1869         widget[widgetName] = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, widgetConfig.requires[i]);
       
  1870       }
       
  1871     }       
       
  1872      
       
  1873     serializer.sync(IriSP.wrap(widget, function() { this.draw(); }));
       
  1874     return widget;
       
  1875 };
       
  1876 /* mediafragment module */
       
  1877 
       
  1878 IriSP.MediaFragment = function(Popcorn, config, Serializer) {
       
  1879   IriSP.Module.call(this, Popcorn, config, Serializer);
       
  1880 
       
  1881   this.mutex = false; /* a mutex because we access the url from two different functions */
       
  1882 
       
  1883   this._Popcorn.listen( "loadedmetadata", IriSP.wrap(this, IriSP.MediaFragment.advanceTime));
       
  1884   this._Popcorn.listen( "pause", IriSP.wrap(this, IriSP.MediaFragment.updateTime));
       
  1885   this._Popcorn.listen( "seeked", IriSP.wrap(this, IriSP.MediaFragment.updateTime));
       
  1886   this._Popcorn.listen( "IriSP.PolemicTweet.click", IriSP.wrap(this, IriSP.MediaFragment.updateAnnotation));
       
  1887   this._Popcorn.listen( "IriSP.SegmentsWidget.click", IriSP.wrap(this, IriSP.MediaFragment.updateAnnotation));
       
  1888 };
       
  1889 
       
  1890 IriSP.MediaFragment.advanceTime = function() {
       
  1891              var url = window.location.href;
       
  1892 
       
  1893               if ( url.split( "#" )[ 1 ] != null ) {
       
  1894                   pageoffset = url.split( "#" )[1];
       
  1895 
       
  1896                   if ( pageoffset.substring(0, 2) === "t=") {
       
  1897                     // timecode 
       
  1898                     if ( pageoffset.substring( 2 ) != null ) {
       
  1899                     var offsettime = pageoffset.substring( 2 );
       
  1900                     this._Popcorn.currentTime( parseFloat( offsettime ) );
       
  1901                     }
       
  1902                   } else if ( pageoffset.substring(0, 2) === "a=") {
       
  1903                     // annotation
       
  1904                     var annotationId = pageoffset.substring( 2 );
       
  1905 
       
  1906                     // there's no better way than that because
       
  1907                     // of possible race conditions
       
  1908                     this._serializer.sync(IriSP.wrap(this, function() {
       
  1909                           IriSP.MediaFragment.lookupAnnotation.call(this, annotationId); 
       
  1910                           }));
       
  1911                   }
       
  1912               }
       
  1913 };
       
  1914 
       
  1915 IriSP.MediaFragment.updateTime = function() {
       
  1916   if (this.mutex === true) {
       
  1917     return;
       
  1918   }
       
  1919 
       
  1920   var history = window.history;
       
  1921   if ( !history.pushState ) {
       
  1922     return false;
       
  1923   }
       
  1924   
       
  1925   splitArr = window.location.href.split( "#" )
       
  1926   history.replaceState( {}, "", splitArr[0] + "#t=" + this._Popcorn.currentTime().toFixed( 2 ) );
       
  1927 };
       
  1928 
       
  1929 
       
  1930 IriSP.MediaFragment.updateAnnotation = function(annotationId) {
       
  1931   var _this = this;
       
  1932   this.mutex = true;
       
  1933 
       
  1934   var history = window.history;
       
  1935   if ( !history.pushState ) {
       
  1936     return false;
       
  1937   }
       
  1938   
       
  1939   splitArr = window.location.href.split( "#" )
       
  1940   history.replaceState( {}, "", splitArr[0] + "#a=" + annotationId);
       
  1941  
       
  1942   window.setTimeout(function() { _this.mutex = false }, 50);
       
  1943 };
       
  1944 
       
  1945 // lookup and seek to the beginning of an annotation
       
  1946 IriSP.MediaFragment.lookupAnnotation = function(annotationId) {
       
  1947   var annotation = undefined;
       
  1948   var annotations = this._serializer._data.annotations;
       
  1949 
       
  1950   var i;
       
  1951   for (i = 0; i < annotations.length; i++) {
       
  1952       if (annotations[i].id === annotationId) {
       
  1953         annotation = annotations[i];
       
  1954         break;
       
  1955       }
       
  1956   }
       
  1957 
       
  1958   if (typeof(annotation) !== "undefined") {
       
  1959     this._Popcorn.currentTime(annotation.begin / 1000);
       
  1960   }
       
  1961 };
       
  1962 IriSP.AnnotationsWidget = function(Popcorn, config, Serializer) {
       
  1963   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  1964   
       
  1965 };
       
  1966 
       
  1967 
       
  1968 IriSP.AnnotationsWidget.prototype = new IriSP.Widget();
       
  1969 
       
  1970 IriSP.AnnotationsWidget.prototype.clear = function() {
       
  1971     this.selector.find(".Ldt-SaTitle").text("");
       
  1972     this.selector.find(".Ldt-SaDescription").text("");
       
  1973     this.selector.find(".Ldt-SaKeywordText").text("");
       
  1974 };
       
  1975 
       
  1976 IriSP.AnnotationsWidget.prototype.displayAnnotation = function(annotation) {   
       
  1977 
       
  1978     var title = annotation.content.title;
       
  1979     var description = annotation.content.description;
       
  1980     var keywords =  "" // FIXME;
       
  1981     var begin = +annotation.begin / 1000;
       
  1982     var end = +annotation.end / 1000;
       
  1983     var duration = +this._serializer.currentMedia().meta["dc:duration"];
       
  1984     
       
  1985     var title_templ = "{{title}} - ( {{begin}} - {{end}} )";
       
  1986     var endstr = Mustache.to_html(title_templ, {title: title, begin: IriSP.secondsToTime(begin), end: IriSP.secondsToTime(end)});
       
  1987 
       
  1988     this.selector.find(".Ldt-SaTitle").text(endstr);
       
  1989     this.selector.find(".Ldt-SaDescription").text(description);
       
  1990     
       
  1991     // update sharing buttons
       
  1992     var defaults = IriSP.widgetsDefaults.AnnotationsWidget;
       
  1993     var text = defaults.share_text;
       
  1994     var fb_link = defaults.fb_link;
       
  1995     var tw_link = defaults.tw_link;
       
  1996     var gplus_link = defaults.gplus_link;
       
  1997     var url = document.location.href + "#a=" + annotation.id;
       
  1998     this.selector.find(".Ldt-fbShare").attr("href", fb_link + IriSP.encodeURI(text) + IriSP.encodeURI(url));
       
  1999     this.selector.find(".Ldt-TwShare").attr("href", tw_link + IriSP.encodeURI(text) + IriSP.encodeURI(url));
       
  2000     this.selector.find(".Ldt-GplusShare").attr("href", fb_link + IriSP.encodeURI(text) + IriSP.encodeURI(url));
       
  2001 };
       
  2002 
       
  2003 IriSP.AnnotationsWidget.prototype.clearWidget = function() {
       
  2004 
       
  2005     
       
  2006     /* retract the pane between two annotations */
       
  2007     this.selector.find(".Ldt-SaTitle").text("");
       
  2008     this.selector.find(".Ldt-SaDescription").text("");
       
  2009     this.selector.find(".Ldt-SaKeywordText").html("");
       
  2010     this.selector.find(".Ldt-ShowAnnotation").slideUp();
       
  2011 };
       
  2012 
       
  2013 IriSP.AnnotationsWidget.prototype.draw = function() {
       
  2014   var _this = this;
       
  2015 
       
  2016   var annotationMarkup = IriSP.templToHTML(IriSP.annotationWidget_template);
       
  2017 	this.selector.append(annotationMarkup);
       
  2018   var view;
       
  2019 
       
  2020   if (typeof(this._serializer._data.views) !== "undefined" && this._serializer._data.views !== null)
       
  2021      view = this._serializer._data.views[0];
       
  2022 
       
  2023   var view_type = "";
       
  2024 
       
  2025   if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) {
       
  2026           view_type = view.annotation_types[0];
       
  2027   }
       
  2028  
       
  2029   var annotations = this._serializer._data.annotations;
       
  2030   var i;
       
  2031   
       
  2032 	for (i in annotations) {    
       
  2033     var annotation = annotations[i];
       
  2034     var begin = Math.round((+ annotation.begin) / 1000);
       
  2035     var end = Math.round((+ annotation.end) / 1000);
       
  2036 
       
  2037     if (view_type != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
       
  2038           && annotation.meta["id-ref"] != view_type) {
       
  2039         continue;
       
  2040     }
       
  2041 
       
  2042 
       
  2043     var conf = {start: begin, end: end, 
       
  2044                 onStart: 
       
  2045                        function(annotation) { 
       
  2046                         return function() { 
       
  2047                             _this.displayAnnotation(annotation); 
       
  2048                           
       
  2049                         } }(annotation),
       
  2050                 onEnd: 
       
  2051                        function() { _this.clearWidget.call(_this); }
       
  2052                 };
       
  2053     this._Popcorn = this._Popcorn.code(conf);                                             
       
  2054   }
       
  2055 
       
  2056 };
       
  2057 IriSP.ArrowWidget = function(Popcorn, config, Serializer) {
       
  2058   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  2059 
       
  2060   this._oldAnnotation = null;
       
  2061   
       
  2062 };
       
  2063 
       
  2064 
       
  2065 IriSP.ArrowWidget.prototype = new IriSP.Widget();
       
  2066 
       
  2067 IriSP.ArrowWidget.prototype.clear = function() {
       
  2068 
       
  2069 };
       
  2070 
       
  2071 IriSP.ArrowWidget.prototype.clearWidget = function() {
       
  2072 };
       
  2073 
       
  2074 IriSP.ArrowWidget.prototype.draw = function() {
       
  2075   var templ = Mustache.to_html(IriSP.arrowWidget_template, {});
       
  2076   this.selector.append(templ);
       
  2077   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
       
  2078 };
       
  2079 
       
  2080 IriSP.ArrowWidget.prototype.timeUpdateHandler = function(percents) {
       
  2081   var currentTime = this._Popcorn.currentTime();
       
  2082   var currentAnnotation = this._serializer.currentAnnotations(currentTime)[0]; // FIXME : use the others ?
       
  2083 
       
  2084   /* move the arrow only if the current annotation changes */
       
  2085   if (currentAnnotation != this._oldAnnotation) {
       
  2086     var begin = (+ currentAnnotation.begin) / 1000;
       
  2087     var end = (+ currentAnnotation.end) / 1000;
       
  2088 
       
  2089     var duration = +this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  2090     var middle_time = (begin + end) / 2;
       
  2091     var percents = Math.floor((middle_time / duration) * 100);
       
  2092 
       
  2093     // we need to apply a fix because the arrow has a certain length
       
  2094     // it's half the length of the arrow (27 / 2). We need to convert
       
  2095     // it in percents though.
       
  2096     var totalWidth = this.selector.width();
       
  2097     var correction = ((27 / 2) / totalWidth) * 100;
       
  2098     var corrected_percents = percents - correction;
       
  2099 
       
  2100     /* don't move out of the screen */
       
  2101     if (corrected_percents <= 0)
       
  2102       corrected_percents = 0;
       
  2103 
       
  2104     this.selector.children(".Ldt-arrowWidget").animate({"left" : corrected_percents + "%"});
       
  2105 
       
  2106     this._oldAnnotation = currentAnnotation;
       
  2107   }
       
  2108 }
       
  2109 IriSP.PlayerWidget = function(Popcorn, config, Serializer) {
       
  2110   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  2111   
       
  2112   this._searchBlockOpen = false;
       
  2113   this._searchLastValue = "";
       
  2114 };
       
  2115 
       
  2116 IriSP.PlayerWidget.prototype = new IriSP.Widget();
       
  2117 
       
  2118 IriSP.PlayerWidget.prototype.draw = function() {
       
  2119   var self = this;
       
  2120   var width = this.width;
       
  2121 	var height = this.height;
       
  2122 	var heightS = this.height-20;
       
  2123 	  
       
  2124 	var Player_templ = Mustache.to_html(IriSP.player_template, {"share_template" : IriSP.share_template});
       
  2125   this.selector.append(Player_templ);		
       
  2126 	
       
  2127   this.selector.children(".Ldt-controler").show();
       
  2128     
       
  2129   // handle clicks by the user on the video.
       
  2130   this._Popcorn.listen("play", IriSP.wrap(this, this.playButtonUpdater));
       
  2131   this._Popcorn.listen("pause", IriSP.wrap(this, this.playButtonUpdater));
       
  2132   
       
  2133   this._Popcorn.listen("volumechange", IriSP.wrap(this, this.muteButtonUpdater));
       
  2134 
       
  2135   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeDisplayUpdater));
       
  2136   this._Popcorn.listen("IriSP.search.matchFound", IriSP.wrap(this, this.searchMatch));
       
  2137   this._Popcorn.listen("IriSP.search.noMatchFound", IriSP.wrap(this, this.searchNoMatch));
       
  2138   
       
  2139   
       
  2140   this.selector.find(".Ldt-CtrlPlay").click(function() { self.playHandler.call(self); });
       
  2141   this.selector.find(".Ldt-CtrlNext").click(function() { });
       
  2142   this.selector.find(".Ldt-CtrlSearch").click(function() { self.searchButtonHandler.call(self); });
       
  2143   
       
  2144   this.selector.find('.Ldt-CtrlSound').click(function() { self.muteHandler.call(self); } );
       
  2145 
       
  2146   this.selector.find(".Ldt-CtrlPlay").attr( "style", "background-color:#CD21C24;" );
       
  2147   
       
  2148   var searchButtonPos = this.selector.find(".Ldt-CtrlSearch").position();
       
  2149   var searchBox = Mustache.to_html(IriSP.search_template, {margin_left : searchButtonPos.left + "px"});
       
  2150   this.selector.append(searchBox);
       
  2151   
       
  2152   // trigger an IriSP.PlayerWidget.MouseOver to the widgets that are interested (i.e : sliderWidget)
       
  2153   this.selector.hover(function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOver"); }, 
       
  2154                       function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOut"); });
       
  2155  
       
  2156   this.muteButtonUpdater(); /* some player - jwplayer notable - save the state of the mute button between sessions */
       
  2157 };
       
  2158 
       
  2159 /* Update the elasped time div */
       
  2160 IriSP.PlayerWidget.prototype.timeDisplayUpdater = function() {
       
  2161   
       
  2162   if (this._previousSecond === undefined)
       
  2163     this._previousSecond = this._Popcorn.roundTime();
       
  2164   
       
  2165   else {
       
  2166     /* we're still in the same second, so it's not necessary to update time */
       
  2167     if (this._Popcorn.roundTime() == this._previousSecond)
       
  2168       return;
       
  2169       
       
  2170   }
       
  2171   
       
  2172   // we get it at each call because it may change.
       
  2173   var duration = +this._serializer.currentMedia().meta["dc:duration"] / 1000; 
       
  2174   var totalTime = IriSP.secondsToTime(duration);
       
  2175   var elapsedTime = IriSP.secondsToTime(this._Popcorn.currentTime());
       
  2176   
       
  2177   this.selector.find(".Ldt-ElapsedTime").html(elapsedTime.toString());
       
  2178   this.selector.find(".Ldt-TotalTime").html(totalTime.toString());
       
  2179   this._previousSecond = this._Popcorn.roundTime();
       
  2180 };
       
  2181 
       
  2182 /* update the icon of the button - separate function from playHandler
       
  2183    because in some cases (for instance, when the user directly clicks on
       
  2184    the jwplayer window) we have to change the icon without playing/pausing
       
  2185 */
       
  2186 IriSP.PlayerWidget.prototype.playButtonUpdater = function() {
       
  2187   var status = this._Popcorn.media.paused;
       
  2188   
       
  2189   if ( status == true ){        
       
  2190     this.selector.find(".Ldt-CtrlPlay").attr("title", "Play");
       
  2191    
       
  2192     // we use templToHTML because it has some predefined
       
  2193     // vars like where to get the images
       
  2194     var templ = IriSP.templToHTML("url({{img_dir}}/play_sprite.png)");
       
  2195     this.selector.find(".Ldt-CtrlPlay").css("background-image", templ);
       
  2196 
       
  2197   } else {
       
  2198     this.selector.find(".Ldt-CtrlPlay").attr("title", "Pause");
       
  2199 
       
  2200     // we use templToHTML because it has some predefined
       
  2201     // vars like where to get the images
       
  2202     var templ = IriSP.templToHTML("url({{img_dir}}/pause_sprite.png)");
       
  2203     this.selector.find(".Ldt-CtrlPlay").css("background-image", templ);
       
  2204   }  
       
  2205 
       
  2206   return;
       
  2207 };
       
  2208 
       
  2209 
       
  2210 IriSP.PlayerWidget.prototype.playHandler = function() {
       
  2211   var status = this._Popcorn.media.paused;
       
  2212   
       
  2213   if ( status == true ){        
       
  2214     this._Popcorn.play();   
       
  2215   } else {
       
  2216     this._Popcorn.pause();
       
  2217   }  
       
  2218 };
       
  2219 
       
  2220 IriSP.PlayerWidget.prototype.muteHandler = function() {
       
  2221   if (!this._Popcorn.muted()) {    
       
  2222       this._Popcorn.mute(true);
       
  2223     } else {
       
  2224       this._Popcorn.mute(false);
       
  2225     }
       
  2226 };
       
  2227 
       
  2228 IriSP.PlayerWidget.prototype.muteButtonUpdater = function() {
       
  2229   var status = this._Popcorn.media.muted;
       
  2230   
       
  2231   if ( status == true ){        
       
  2232     this.selector.find(".Ldt-CtrlSound").attr("title", "Unmute");
       
  2233    
       
  2234     // we use templToHTML because it has some predefined
       
  2235     // vars like where to get the images
       
  2236     var templ = IriSP.templToHTML("url({{img_dir}}/sound_sprite.png)");
       
  2237     this.selector.find(".Ldt-CtrlSound").css("background-image", templ);
       
  2238 
       
  2239   } else {
       
  2240     this.selector.find(".Ldt-CtrlSound").attr("title", "Mute");
       
  2241 
       
  2242     // we use templToHTML because it has some predefined
       
  2243     // vars like where to get the images
       
  2244     var templ = IriSP.templToHTML("url({{img_dir}}/mute_sprite.png)");
       
  2245     this.selector.find(".Ldt-CtrlSound").css("background-image", templ);
       
  2246   }  
       
  2247 
       
  2248   return;
       
  2249 };
       
  2250 
       
  2251 
       
  2252 IriSP.PlayerWidget.prototype.searchButtonHandler = function() {
       
  2253     var self = this;
       
  2254 
       
  2255     /* show the search field if it is not shown */
       
  2256   	if ( this._searchBlockOpen == false ) {      
       
  2257       this.selector.find(".LdtSearch").show(100);
       
  2258       
       
  2259       this.selector.find(".LdtSearchInput").css('background-color','#fff');
       
  2260       this.selector.find(".LdtSearchInput").focus();
       
  2261       this.selector.find(".LdtSearchInput").attr('value', this._searchLastValue);      
       
  2262       this._Popcorn.trigger("IriSP.search", this._searchLastValue); // trigger the search to make it more natural.
       
  2263       
       
  2264       this._searchBlockOpen = true;           
       
  2265       this.selector.find(".LdtSearchInput").bind('keyup', null, function() { self.searchHandler.call(self); } );
       
  2266       
       
  2267       // we need this variable because some widget can find a match in
       
  2268       // their data while at the same time other's don't. As we want the
       
  2269       // search field to become green when there's a match, we need a 
       
  2270       // variable to remember that we had one.
       
  2271       this._positiveMatch = false;
       
  2272 
       
  2273       // tell the world the field is open
       
  2274       this._Popcorn.trigger("IriSP.search.open");
       
  2275       
       
  2276 	} else {
       
  2277       this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value');
       
  2278       this.selector.find(".LdtSearchInput").attr('value','');
       
  2279       this.selector.find(".LdtSearch").hide(100);
       
  2280       
       
  2281       // unbind the watcher event.
       
  2282       this.selector.find(".LdtSearchInput").unbind('keypress set');
       
  2283       this._searchBlockOpen = false;
       
  2284 
       
  2285       this._positiveMatch = false;
       
  2286       
       
  2287       this._Popcorn.trigger("IriSP.search.closed");
       
  2288   }
       
  2289 };
       
  2290 
       
  2291 /* this handler is called whenever the content of the search
       
  2292    field changes */
       
  2293 IriSP.PlayerWidget.prototype.searchHandler = function() {
       
  2294   this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value');
       
  2295   this._positiveMatch = false;
       
  2296   
       
  2297   // do nothing if the search field is empty, instead of highlighting everything.
       
  2298   if (this._searchLastValue == "") {
       
  2299     this._Popcorn.trigger("IriSP.search.cleared");
       
  2300     this.selector.find(".LdtSearchInput").css('background-color','');
       
  2301   } else {
       
  2302     this._Popcorn.trigger("IriSP.search", this._searchLastValue);
       
  2303   }
       
  2304 };
       
  2305 
       
  2306 /*
       
  2307   handler for the IriSP.search.found message, which is sent by some views when they
       
  2308   highlight a match.
       
  2309 */
       
  2310 IriSP.PlayerWidget.prototype.searchMatch = function() {
       
  2311   this._positiveMatch = true;
       
  2312   this.selector.find(".LdtSearchInput").css('background-color','#e1ffe1');
       
  2313 }
       
  2314 
       
  2315 /* the same, except that no value could be found */
       
  2316 IriSP.PlayerWidget.prototype.searchNoMatch = function() {
       
  2317   if (this._positiveMatch !== true)
       
  2318     this.selector.find(".LdtSearchInput").css('background-color', "#d62e3a");
       
  2319 }
       
  2320 
       
  2321 /* 
       
  2322  *   
       
  2323  *  Copyright 2010 Institut de recherche et d'innovation 
       
  2324  *  contributor(s) : Samuel Huron 
       
  2325  *   
       
  2326  *  contact@iri.centrepompidou.fr
       
  2327  *  http://www.iri.centrepompidou.fr 
       
  2328  *   
       
  2329  *  This software is a computer program whose purpose is to show and add annotations on a video .
       
  2330  *  This software is governed by the CeCILL-C license under French law and
       
  2331  *  abiding by the rules of distribution of free software. You can  use, 
       
  2332  *  modify and/ or redistribute the software under the terms of the CeCILL-C
       
  2333  *  license as circulated by CEA, CNRS and INRIA at the following URL
       
  2334  *  "http://www.cecill.info". 
       
  2335  *  
       
  2336  *  The fact that you are presently reading this means that you have had
       
  2337  *  knowledge of the CeCILL-C license and that you accept its terms.
       
  2338 */
       
  2339 // CHART TIMELINE / VERSION PROTOTYPE  ::
       
  2340 
       
  2341 IriSP.PolemicWidget = function(Popcorn, config, Serializer) {
       
  2342   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  2343  
       
  2344   this.userPol    = new Array();
       
  2345   this.userNoPol  = new Array();
       
  2346   this.userst      = new Array();
       
  2347   this.numberOfTweet = 0;
       
  2348   this.Users;
       
  2349   this.TweetPolemic;
       
  2350   this.yMax        = this.height; 
       
  2351   this.PaperSlider;
       
  2352   this.heightOfChart;
       
  2353   this.tweets  = new Array();
       
  2354   this.svgElements = {};
       
  2355   
       
  2356   // Make and define the Raphael area
       
  2357   this.paper = Raphael(document.getElementById(this._id), config.width, config.height);
       
  2358   
       
  2359   this.oldSearchMatches = [];
       
  2360 
       
  2361   // event handlers
       
  2362   this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) { this.searchHandler(searchString); }));
       
  2363   this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.searchFieldClosedHandler));
       
  2364   this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.searchFieldClearedHandler));
       
  2365 
       
  2366 };
       
  2367 
       
  2368 IriSP.PolemicWidget.prototype = new IriSP.Widget();
       
  2369   
       
  2370 IriSP.PolemicWidget.prototype.draw = function() {
       
  2371   
       
  2372     // variable 
       
  2373     // yMax
       
  2374     
       
  2375     var self = this;
       
  2376     var yCoef        = 2;             // coef for height of 1 tweet 
       
  2377     var frameSize     = 5;             // frame size 
       
  2378     var margin         = 1;            // marge between frame
       
  2379     var lineSize      = this.width;        // timeline pixel width 
       
  2380     var nbrframes     = lineSize/frameSize;     // frame numbers
       
  2381     var numberOfTweet   = 0;            // number of tweet overide later 
       
  2382     var duration      = +this._serializer.currentMedia().meta["dc:duration"];      // timescale width 
       
  2383     var frameLength   = lineSize / frameSize;    // frame timescale  
       
  2384     var timeline;
       
  2385     var colors  = new Array("","#1D973D","#C5A62D","#CE0A15","#036AAE","#585858");
       
  2386     
       
  2387     // array 
       
  2388     //var tweets  = new Array();
       
  2389     var element = new Array();
       
  2390     var cluster = new Array();
       
  2391     var frames  = new Array(frameLength);
       
  2392     var slices  = new Array();
       
  2393     
       
  2394     
       
  2395     // Classes =======================================================================
       
  2396     var Frames = function(){
       
  2397       
       
  2398       var Myclusters;
       
  2399       var x;
       
  2400       var y;
       
  2401       var width;
       
  2402       var height;
       
  2403     };
       
  2404     Frames = function(json){
       
  2405       // make my clusters
       
  2406       // ou Frame vide 
       
  2407     };
       
  2408     Frames.prototype.draw = function(){
       
  2409     };
       
  2410     Frames.prototype.zoom = function(){
       
  2411     };
       
  2412     Frames.prototype.inside = function(){
       
  2413     };
       
  2414     var Clusters = function(){
       
  2415       var Object;
       
  2416       var yDist;
       
  2417       var x;
       
  2418       var y;
       
  2419       var width;
       
  2420       var height;
       
  2421     };
       
  2422     Clusters = function(json){
       
  2423       // make my object
       
  2424     };
       
  2425     var Tweet = function(){
       
  2426     };
       
  2427     // Classes =======================================================================
       
  2428 
       
  2429     // Refactoring (parametere) ************************************************************
       
  2430     // color translastion
       
  2431     var qTweet_0  =0;
       
  2432     var qTweet_Q  =0;
       
  2433     var qTweet_REF=0;
       
  2434     var qTweet_OK =0;
       
  2435     var qTweet_KO =0;
       
  2436     function colorTranslation(value){
       
  2437       if(value == "Q"){
       
  2438         qTweet_Q+=1;
       
  2439         return 2;
       
  2440       }else if(value =="REF"){
       
  2441         qTweet_REF+=1;
       
  2442         return 4;
       
  2443       }else if(value =="OK"){
       
  2444         qTweet_OK+=1;
       
  2445         return 1;
       
  2446       }else if(value =="KO"){
       
  2447         qTweet_KO+=1;
       
  2448         return 3;
       
  2449       }else if(value ==""){
       
  2450         qTweet_0+=1;
       
  2451         return 5;
       
  2452       }
       
  2453     }
       
  2454     
       
  2455 
       
  2456       this._serializer.sync(function(data) { loaded_callback.call(self, data) });
       
  2457       
       
  2458       function loaded_callback (json) {
       
  2459 
       
  2460         // get current view (the first ???)
       
  2461         view = json.views[0];
       
  2462         
       
  2463         // the tweets are by definition of the second annotation type FIXME ?
       
  2464         tweet_annot_type = null;
       
  2465         if(typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) {
       
  2466           tweet_annot_type = view.annotation_types[1];
       
  2467         }
       
  2468       
       
  2469       for(var i = 0; i < json.annotations.length; i++) {
       
  2470         var item = json.annotations[i];        
       
  2471         var MyTime  = Math.floor(item.begin/duration*lineSize);
       
  2472         var Myframe = Math.floor(MyTime/lineSize*frameLength);
       
  2473 
       
  2474         if (typeof(item.meta) !== "undefined" 
       
  2475           && typeof(item.meta["id-ref"]) !== "undefined"
       
  2476           && item.meta["id-ref"] === tweet_annot_type) {
       
  2477             
       
  2478           var MyTJson = JSON.parse(item.meta['dc:source']['content']);
       
  2479           
       
  2480             if (item.content['polemics'] != undefined 
       
  2481             && item.content['polemics'][0] != null) {
       
  2482             
       
  2483               // a tweet can have many polemics at the same time.
       
  2484               for(var j=0; j<item.content['polemics'].length; j++){
       
  2485                   
       
  2486                   this.tweets[numberOfTweet] = {
       
  2487                         id:i,
       
  2488                         qualification:colorTranslation(item.content['polemics'][j]),
       
  2489                         yIndicator:MyTime,
       
  2490                         yframe:Myframe,
       
  2491                         title:item.content['title'],
       
  2492                         timeframe:item.begin,
       
  2493                         userId: MyTJson.id,
       
  2494                         userScreenName: MyTJson.screen_name,
       
  2495                         tsource:MyTJson,
       
  2496                         cinecast_id: item.id
       
  2497                         };
       
  2498                   numberOfTweet+=1;
       
  2499                   
       
  2500               }
       
  2501           }
       
  2502           else {
       
  2503             this.tweets[numberOfTweet] = {
       
  2504                   id:i,
       
  2505                   qualification:colorTranslation(""),
       
  2506                   yIndicator:MyTime,
       
  2507                   yframe:Myframe,
       
  2508                   title:item.content['title'],
       
  2509                   timeframe:item.begin,
       
  2510                   userId: MyTJson.id,
       
  2511                   userScreenName: MyTJson.screen_name,
       
  2512                   tsource:MyTJson,
       
  2513                   cinecast_id: item.id
       
  2514             };
       
  2515             numberOfTweet+=1;
       
  2516           }
       
  2517           
       
  2518         } 
       
  2519       };  
       
  2520       
       
  2521        DrawTweets.call (this); // FIXME: ugly.
       
  2522        
       
  2523       };      
       
  2524 
       
  2525     // tweet Drawing (in raphael) 
       
  2526     function DrawTweets (){
       
  2527     // GROUPES TWEET ============================================
       
  2528     // Count nbr of cluster and tweet in a frame an save int in "frames"
       
  2529       numberOfTweet = this.tweets.length;
       
  2530       for(var i=0; i<nbrframes; i++) {  
       
  2531         for(var j=0; j<numberOfTweet; j++) {  
       
  2532         
       
  2533           if (i==this.tweets[j].yframe){
       
  2534             
       
  2535             var k = this.tweets[j].qualification;
       
  2536             
       
  2537             // make array for frame cluster
       
  2538             if(frames[i]==undefined){
       
  2539               frames[i] = {id:i,
       
  2540                      qualifVol:new Array(),
       
  2541                      mytweetsID:new Array()
       
  2542                     };
       
  2543             }
       
  2544             // add my tweet to frame
       
  2545             frames[i].mytweetsID.push(this.tweets[j]);
       
  2546             
       
  2547             // count opinion by frame
       
  2548             if( frames[i].qualifVol[k] == undefined){
       
  2549               frames[i].qualifVol[k] = 1;
       
  2550             }else{
       
  2551               frames[i].qualifVol[k] += 1;
       
  2552             }
       
  2553             
       
  2554           }
       
  2555         }
       
  2556       }
       
  2557     
       
  2558     // GROUPES TWEET ============================================    
       
  2559     // max of tweet by Frame 
       
  2560       var max = 0; 
       
  2561       for(var i = 0; i < nbrframes; i++) {
       
  2562         var moy  = 0;
       
  2563         for (var j = 0; j < 6; j++) {    
       
  2564           if (frames[i] != undefined) {
       
  2565             if (frames[i].qualifVol[j] != undefined) {
       
  2566               moy += frames[i].qualifVol[j];
       
  2567             }
       
  2568           }
       
  2569         }
       
  2570         
       
  2571         if (moy > max) {
       
  2572           max = moy;
       
  2573         }
       
  2574       }
       
  2575     
       
  2576       var tweetDrawed = new Array();
       
  2577       var TweetHeight = 5;
       
  2578       
       
  2579       // DRAW  TWEETS ============================================
       
  2580       for(var i = 0; i < nbrframes; i++) {
       
  2581         var addEheight = 5;
       
  2582         if (frames[i] != undefined){                
       
  2583           // by type 
       
  2584           
       
  2585           for (var j = 6; j > -1; j--) {
       
  2586             if (frames[i].qualifVol[j] != undefined) {
       
  2587               // show tweet by type 
       
  2588               for (var k = 0; k < frames[i].mytweetsID.length; k++) {
       
  2589               
       
  2590                 if (frames[i].mytweetsID[k].qualification == j) {                
       
  2591                   var x = i * frameSize;
       
  2592                   var y = this.heightmax - addEheight;
       
  2593                   
       
  2594                   if (this.yMax > y) {
       
  2595                     this.yMax = y;
       
  2596                   }
       
  2597                   
       
  2598                   var e = this.paper.rect(x, y, frameSize - margin, TweetHeight /* height */)
       
  2599                                     .attr({stroke:"#00","stroke-width":0.1,  fill: colors[j]});  
       
  2600                   
       
  2601                   addEheight += TweetHeight;
       
  2602                   
       
  2603                   e.color = colors[j];
       
  2604                   e.time = frames[i].mytweetsID[k].timeframe;
       
  2605                   e.title = frames[i].mytweetsID[k].title;
       
  2606                   e.id = frames[i].mytweetsID[k].cinecast_id;
       
  2607 
       
  2608                   this.svgElements[e.id] = e;
       
  2609 
       
  2610                   /*
       
  2611                   e.mouseover(function(element) { return function (event) {
       
  2612                         // event.clientX and event.clientY are to raphael what event.pageX and pageY are to jquery.                        
       
  2613                         self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), event.clientX - 106, event.clientY - 160);
       
  2614                         element.displayed = true;
       
  2615                   }}(e)).mouseout(function(element) { return function () {                          
       
  2616                           self.TooltipWidget.hide.call(self.TooltipWidget);
       
  2617                   }}(e)).mousedown(function () {
       
  2618                     self._Popcorn.currentTime(this.time/1000);
       
  2619                     self._Popcorn.trigger("IriSP.PolemicTweet.click", this.id); 
       
  2620                   });
       
  2621                   */
       
  2622                   
       
  2623                   IriSP.jQuery(e.node).mouseenter(function(element) { return function (event) {                        
       
  2624                         // event.clientX and event.clientY are to raphael what event.pageX and pageY are to jquery.                        
       
  2625                         self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), event.pageX - 106, event.pageY - 160);
       
  2626                         element.displayed = true;
       
  2627                   }}(e)).mousedown(function(element) { return function () {                    
       
  2628                     self._Popcorn.currentTime(element.time/1000);
       
  2629                     self._Popcorn.trigger("IriSP.PolemicTweet.click", element.id); 
       
  2630                     }
       
  2631                   }(e));                  
       
  2632                   
       
  2633                   IriSP.jQuery(e.node).attr('id', 't' + k + '');
       
  2634                   IriSP.jQuery(e.node).attr('title', frames[i].mytweetsID[k].title);
       
  2635                   IriSP.jQuery(e.node).attr('begin',  frames[i].mytweetsID[k].timeframe);                  
       
  2636                 }
       
  2637               }
       
  2638             }
       
  2639           }
       
  2640         }
       
  2641 
       
  2642       }    
       
  2643       // DRAW UI :: resize border and bgd      
       
  2644       this.paperBackground = this.paper.rect(0, 0, this.width, this.heightmax).attr({fill:"#F8F8F8","stroke-width":0.1,opacity: 1});  
       
  2645 
       
  2646       // outer borders
       
  2647       this.outerBorders   = [];
       
  2648       this.outerBorders.push(this.paper.rect(0, this.height - 1, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1}));  
       
  2649       this.outerBorders.push(this.paper.rect(0, 0, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1}));  
       
  2650 
       
  2651       // inner borders
       
  2652       this.innerBorders   = [];
       
  2653       this.innerBorders.push(this.paper.rect(1, this.height - 2, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1}));  
       
  2654       this.innerBorders.push(this.paper.rect(1, 1, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1}));  
       
  2655       this.innerBorders.push(this.paper.rect(1, 1, 1, this.height - 2).attr({fill:"#d0d1d1",stroke: "none",opacity: 0.8}));  
       
  2656       this.innerBorders.push(this.paper.rect(this.width - 2, 1, 1, this.height - 2).attr({fill:"#efefef",stroke: "none",opacity: 1}));  
       
  2657 
       
  2658 
       
  2659 
       
  2660       this.paperSlider   = this.paper.rect(0, 0, 0, this.heightmax).attr({fill:"#D4D5D5", stroke: "none", opacity: 1});
       
  2661       
       
  2662       // the small white line displayed over the slider.
       
  2663       this.sliderTip = this.paper.rect(0, 0, 1, this.heightmax).attr({fill:"#fc00ff", stroke: "none", opacity: 1});
       
  2664       // decalage 
       
  2665       // tweetSelection = this.paper.rect(-100,-100,5,5).attr({fill:"#fff",stroke: "none",opacity: 1});  
       
  2666       
       
  2667       
       
  2668       this.paperSlider.toBack();
       
  2669       this.paperBackground.toBack();
       
  2670       this.sliderTip.toFront();
       
  2671     }
       
  2672     
       
  2673     this.selector.mouseleave(IriSP.wrap(this, function() { self.TooltipWidget.hide.call(self.TooltipWidget); }));
       
  2674     this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater));
       
  2675 }
       
  2676 
       
  2677 IriSP.PolemicWidget.prototype.sliderUpdater = function() {
       
  2678 
       
  2679     var time = +this._Popcorn.currentTime();
       
  2680     var duration = +this._serializer.currentMedia().meta["dc:duration"];
       
  2681     
       
  2682     this.paperSlider.attr("width", time * (this.width / (duration / 1000)));
       
  2683         
       
  2684     this.sliderTip.attr("x", time * (this.width / (duration / 1000)));
       
  2685 };
       
  2686     
       
  2687 IriSP.PolemicWidget.prototype.searchHandler = function(searchString) {
       
  2688   if (searchString == "")
       
  2689     return;
       
  2690 
       
  2691   var matches = this._serializer.searchTweetsOccurences(searchString);
       
  2692 
       
  2693   if (IriSP.countProperties(matches) > 0) {
       
  2694     this._Popcorn.trigger("IriSP.search.matchFound");
       
  2695   } else {
       
  2696     this._Popcorn.trigger("IriSP.search.noMatchFound");
       
  2697   }
       
  2698 
       
  2699   for (var id in matches) {
       
  2700     if (this.svgElements.hasOwnProperty(id)) {
       
  2701       var e = this.svgElements[id];
       
  2702       this.svgElements[id].attr({fill: "#fc00ff"});
       
  2703     }
       
  2704   }
       
  2705 
       
  2706   // clean up the blocks that were in the previous search
       
  2707   // but who aren't in the current one.
       
  2708   for (var id in this.oldSearchMatches) {
       
  2709     if (!matches.hasOwnProperty(id)) {
       
  2710       var e = this.svgElements[id];
       
  2711       e.attr({fill: e.color});
       
  2712     }
       
  2713   }
       
  2714   
       
  2715   this.oldSearchMatches = matches;
       
  2716 };
       
  2717 
       
  2718 IriSP.PolemicWidget.prototype.searchFieldClearedHandler = function() {
       
  2719   // clean up the blocks that were in the previous search
       
  2720   // but who aren't in the current one.
       
  2721   for (var id in this.oldSearchMatches) {
       
  2722       var e = this.svgElements[id];
       
  2723       e.attr({fill: e.color});
       
  2724   }
       
  2725  
       
  2726 };
       
  2727 
       
  2728 IriSP.PolemicWidget.prototype.searchFieldClosedHandler = function() {
       
  2729   // clean up the blocks that were in the previous search
       
  2730   // but who aren't in the current one.
       
  2731   for (var id in this.oldSearchMatches) {
       
  2732       var e = this.svgElements[id];
       
  2733       e.attr({fill: e.color});
       
  2734   }
       
  2735  
       
  2736 };
       
  2737    
       
  2738 IriSP.SegmentsWidget = function(Popcorn, config, Serializer) {
       
  2739 
       
  2740   var self = this;
       
  2741   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  2742   this.oldSearchMatches = [];
       
  2743 
       
  2744   // event handlers
       
  2745   this._Popcorn.listen("IriSP.search", function(searchString) { self.searchHandler.call(self, searchString); });
       
  2746   this._Popcorn.listen("IriSP.search.closed", function() { self.searchFieldClosedHandler.call(self); });
       
  2747   this._Popcorn.listen("IriSP.search.cleared", function() { self.searchFieldClearedHandler.call(self); });
       
  2748 };
       
  2749 
       
  2750 IriSP.SegmentsWidget.prototype = new IriSP.Widget();
       
  2751 
       
  2752 /* Get the width of a segment, in pixels. */
       
  2753 IriSP.SegmentsWidget.prototype.segmentToPixel = function(annotation) {  
       
  2754   var begin = Math.round((+ annotation.begin) / 1000);
       
  2755   var end = Math.round((+ annotation.end) / 1000);    
       
  2756   var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  2757   
       
  2758   var startPourcent 	= IriSP.timeToPourcent(begin, duration);
       
  2759   var startPixel = Math.floor(this.selector.parent().width() * (startPourcent / 100));
       
  2760   
       
  2761   var endPourcent 	= Math.floor(IriSP.timeToPourcent(end, duration) - startPourcent);
       
  2762   var endPixel = Math.floor(this.selector.parent().width() * (endPourcent / 100));
       
  2763   
       
  2764   return endPixel;
       
  2765 };
       
  2766 
       
  2767 /* compute the total length of a group of segments */
       
  2768 IriSP.SegmentsWidget.prototype.segmentsLength = function(segmentsList) {
       
  2769   var self = this;
       
  2770   var total = 0;
       
  2771   
       
  2772   for (var i = 0; i < segmentsList.length; i++)
       
  2773     total += self.segmentToPixel(segmentsList[i].annotation);
       
  2774   
       
  2775   return total;  
       
  2776 };
       
  2777 
       
  2778 IriSP.SegmentsWidget.prototype.draw = function() {
       
  2779 
       
  2780   var self = this;
       
  2781   var annotations = this._serializer._data.annotations;
       
  2782 
       
  2783   this.selector.addClass("Ldt-SegmentsWidget");
       
  2784   this.selector.append(Mustache.to_html(IriSP.overlay_marker_template));
       
  2785           
       
  2786   var view_type = this._serializer.getNonTweetIds()[0];    
       
  2787   
       
  2788   this.positionMarker = this.selector.children(":first");
       
  2789   
       
  2790   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.positionUpdater));
       
  2791   
       
  2792   
       
  2793   var i = 0;
       
  2794   
       
  2795   var segments_annotations = [];
       
  2796   
       
  2797   for (i = 0; i < annotations.length; i++) {
       
  2798     var annotation = annotations[i];
       
  2799 
       
  2800     /* filter the annotations whose type is not the one we want */
       
  2801     if (view_type != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
       
  2802           && annotation.meta["id-ref"] != view_type) {
       
  2803         continue;
       
  2804     }
       
  2805 
       
  2806     segments_annotations.push(annotation);
       
  2807   }
       
  2808     
       
  2809   var totalWidth = this.selector.width() - segments_annotations.length;
       
  2810   var lastSegment = IriSP.underscore.max(segments_annotations, function(annotation) { return annotation.end; });
       
  2811   
       
  2812   for (i = 0; i < segments_annotations.length; i++) {
       
  2813   
       
  2814     var annotation = segments_annotations[i];
       
  2815     var begin = (+ annotation.begin);
       
  2816     var end = (+ annotation.end);
       
  2817     var duration = this._serializer.currentMedia().meta["dc:duration"];
       
  2818     var id = annotation.id;
       
  2819         
       
  2820     var startPixel = Math.floor(this.selector.parent().width() * (begin / duration));
       
  2821 
       
  2822     var endPixel = Math.floor(this.selector.parent().width() * (end / duration));
       
  2823     
       
  2824     if (annotation.id !== lastSegment.id) 
       
  2825       var pxWidth = endPixel - startPixel -1;
       
  2826     else
       
  2827       /* the last segment has no segment following it */
       
  2828       var pxWidth = endPixel - startPixel;
       
  2829  
       
  2830     var divTitle = (annotation.content.title + " - " + annotation.content.description).substr(0,55);
       
  2831 
       
  2832     if (typeof(annotation.content.color) !== "undefined")
       
  2833       var color = annotation.content.color;
       
  2834     else
       
  2835       var color = annotation.color;
       
  2836     
       
  2837     var hexa_color = IriSP.DEC_HEXA_COLOR(color);
       
  2838 
       
  2839     if (hexa_color === "FFCC00")
       
  2840       hexa_color = "333";
       
  2841     if (hexa_color.length == 4)
       
  2842       hexa_color = hexa_color + '00';
       
  2843     
       
  2844     var annotationTemplate = Mustache.to_html(IriSP.annotation_template,
       
  2845         {"divTitle" : divTitle, "id" : id, "startPixel" : startPixel,
       
  2846         "pxWidth" : pxWidth, "hexa_color" : hexa_color,
       
  2847         "seekPlace" : Math.round(begin/1000)});
       
  2848 
       
  2849         
       
  2850     this.selector.append(annotationTemplate);
       
  2851     
       
  2852     /* add a special class to the last segment and change its border */
       
  2853     if (annotation.id === lastSegment.id) {
       
  2854         this.selector.find("#" + id).addClass("Ldt-lastSegment");        
       
  2855         this.selector.find(".Ldt-lastSegment").css("border-color", "#" + hexa_color);        
       
  2856     }
       
  2857 
       
  2858     IriSP.jQuery("#" + id).fadeTo(0, 0.3);
       
  2859 
       
  2860     IriSP.jQuery("#" + id).mouseover(
       
  2861     /* we wrap the handler in another function because js's scoping
       
  2862        rules are function-based - otherwise, the internal vars like
       
  2863        divTitle are preserved but they are looked-up from the draw
       
  2864        method scope, so after that the loop is run, so they're not
       
  2865        preserved */
       
  2866     (function(divTitle) { 
       
  2867      return function(event) {
       
  2868           IriSP.jQuery(this).animate({opacity: 0.6}, 5);
       
  2869           var offset = IriSP.jQuery(this).offset();
       
  2870           var correction = IriSP.jQuery(this).outerWidth() / 2;
       
  2871 
       
  2872           var offset_x = offset.left + correction - 106;
       
  2873           if (offset_x < 0)
       
  2874             offset_x = 0;
       
  2875                     
       
  2876           self.TooltipWidget.show(divTitle, color, offset_x, event.pageY - 160);
       
  2877     } })(divTitle)).mouseout(function(){
       
  2878       IriSP.jQuery(this).animate({opacity: 0.3}, 5);
       
  2879       self.TooltipWidget.hide();
       
  2880     });
       
  2881 
       
  2882     IriSP.jQuery("#" + id).click(function(_this, annotation) {
       
  2883                                     return function() { _this.clickHandler(annotation)};
       
  2884                                  }(this, annotation));
       
  2885   }
       
  2886 };
       
  2887 
       
  2888 /* restores the view after a search */
       
  2889 IriSP.SegmentsWidget.prototype.clear = function() {
       
  2890   this.selector.children(".Ldt-iri-chapter").animate({opacity:0.3}, 100);
       
  2891 };
       
  2892 
       
  2893 IriSP.SegmentsWidget.prototype.clickHandler = function(annotation) {
       
  2894   this._Popcorn.trigger("IriSP.SegmentsWidget.click", annotation.id);
       
  2895   var begin = (+ annotation.begin) / 1000;
       
  2896   this._Popcorn.currentTime(Math.round(begin));
       
  2897 };
       
  2898 
       
  2899 IriSP.SegmentsWidget.prototype.searchHandler = function(searchString) {
       
  2900 
       
  2901   if (searchString == "")
       
  2902     return;
       
  2903 
       
  2904   var matches = this._serializer.searchOccurences(searchString);
       
  2905 
       
  2906   if (IriSP.countProperties(matches) > 0) {
       
  2907     this._Popcorn.trigger("IriSP.search.matchFound");
       
  2908   } else {
       
  2909     this._Popcorn.trigger("IriSP.search.noMatchFound");
       
  2910   }
       
  2911 
       
  2912   // un-highlight all the blocks
       
  2913   this.selector.children(".Ldt-iri-chapter").css("opacity", 0.1);
       
  2914  
       
  2915   // then highlight the ones with matches.
       
  2916   for (var id in matches) {
       
  2917     var factor = 0.5 + matches[id] * 0.2;
       
  2918     this.selector.find("#"+id).dequeue();
       
  2919     this.selector.find("#"+id).animate({opacity:factor}, 200);
       
  2920   }
       
  2921 
       
  2922  
       
  2923   this.oldSearchMatches = matches;
       
  2924 };
       
  2925 
       
  2926 IriSP.SegmentsWidget.prototype.searchFieldClearedHandler = function() {
       
  2927   this.clear();
       
  2928 };
       
  2929 
       
  2930 IriSP.SegmentsWidget.prototype.searchFieldClosedHandler = function() {
       
  2931   this.clear();
       
  2932 };
       
  2933 
       
  2934 IriSP.SegmentsWidget.prototype.positionUpdater = function() {  
       
  2935   var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  2936   var time = this._Popcorn.currentTime();
       
  2937   //var position 	= ((time / duration) * 100).toFixed(2);
       
  2938   var position 	= ((time / duration) * 100).toFixed(2);
       
  2939 
       
  2940   this.positionMarker.css("left", position + "%");  
       
  2941 };
       
  2942 IriSP.SliderWidget = function(Popcorn, config, Serializer) {
       
  2943   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  2944 };
       
  2945 
       
  2946 IriSP.SliderWidget.prototype = new IriSP.Widget();
       
  2947 
       
  2948 IriSP.SliderWidget.prototype.draw = function() {
       
  2949   var self = this;
       
  2950 
       
  2951   this.selector.append(Mustache.to_html(IriSP.sliderWidget_template, {}));
       
  2952   this.selector.addClass("Ldt-SliderMinimized");
       
  2953 
       
  2954   this.sliderBackground = this.selector.find(".Ldt-sliderBackground");
       
  2955   this.sliderForeground = this.selector.find(".Ldt-sliderForeground");
       
  2956   this.positionMarker = this.selector.find(".Ldt-sliderPositionMarker");
       
  2957 
       
  2958 
       
  2959   // a special variable to stop methods from tinkering
       
  2960   // with the positionMarker when the user is dragging it
       
  2961   this.draggingOngoing = false;
       
  2962 
       
  2963   // another special variable used by the timeout handler to
       
  2964   // open or close the slider.
       
  2965   this.sliderMaximized = false;
       
  2966   this.timeOutId = null;
       
  2967 
       
  2968   
       
  2969   this.positionMarker.draggable({axis: "x",
       
  2970   start: IriSP.wrap(this, this.positionMarkerDraggingStartedHandler),
       
  2971   stop: IriSP.wrap(this, this.positionMarkerDraggedHandler),
       
  2972   containment: "parent"
       
  2973   });
       
  2974   this.positionMarker.css("position", "absolute");
       
  2975   
       
  2976   this.sliderBackground.click(function(event) { self.backgroundClickHandler.call(self, event); });
       
  2977   this.sliderForeground.click(function(event) { self.foregroundClickHandler.call(self, event); });
       
  2978 
       
  2979   this.selector.hover(IriSP.wrap(this, this.mouseOverHandler), IriSP.wrap(this, this.mouseOutHandler));
       
  2980 
       
  2981   // update the positions
       
  2982   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater));
       
  2983 
       
  2984   // special messages :
       
  2985   this._Popcorn.listen("IriSP.PlayerWidget.MouseOver", IriSP.wrap(this, this.mouseOverHandler));
       
  2986   this._Popcorn.listen("IriSP.PlayerWidget.MouseOut", IriSP.wrap(this, this.mouseOutHandler));
       
  2987 };
       
  2988 
       
  2989 /* update the slider and the position marker as time passes */
       
  2990 IriSP.SliderWidget.prototype.sliderUpdater = function() {
       
  2991   if(this.draggingOngoing || this._disableUpdate)
       
  2992     return;
       
  2993   
       
  2994   var time = this._Popcorn.currentTime();
       
  2995 
       
  2996   var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  2997   var percent = ((time / duration) * 100).toFixed(2);
       
  2998   
       
  2999   /* we do these complicated calculations to center exactly
       
  3000      the position Marker */
       
  3001   var pixels_to_percents = 100 / this.selector.width(); /* how much is a pixel in percents */
       
  3002   var positionMarker_width = this.positionMarker.width();
       
  3003   var correction = (pixels_to_percents * positionMarker_width) / 2;
       
  3004 
       
  3005   var newPos = percent - correction;
       
  3006   if (newPos <= 0)
       
  3007     newPos = 0;
       
  3008   
       
  3009 	this.sliderForeground.css("width", percent + "%");
       
  3010 	this.positionMarker.css("left", newPos + "%");
       
  3011 
       
  3012 };
       
  3013 
       
  3014 IriSP.SliderWidget.prototype.backgroundClickHandler = function(event) {
       
  3015   /* this piece of code is a little bit convoluted - here's how it works :
       
  3016      we want to handle clicks on the progress bar and convert those to seeks in the media.
       
  3017      However, jquery only gives us a global position, and we want a number of pixels relative
       
  3018      to our container div, so we get the parent position, and compute an offset to this position,
       
  3019      and finally compute the progress ratio in the media.
       
  3020      Finally we multiply this ratio with the duration to get the correct time
       
  3021   */
       
  3022 
       
  3023   var parentOffset = this.sliderBackground.parent().offset();
       
  3024   var width = this.sliderBackground.width();
       
  3025   var relX = event.pageX - parentOffset.left;
       
  3026 
       
  3027   var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  3028   var newTime = ((relX / width) * duration).toFixed(2);
       
  3029 
       
  3030   this._Popcorn.currentTime(newTime);
       
  3031 };
       
  3032 
       
  3033 /* same function as the previous one, except that it handles clicks
       
  3034    on the foreground element */
       
  3035 IriSP.SliderWidget.prototype.foregroundClickHandler = function(event) {
       
  3036   var parentOffset = this.sliderForeground.parent().offset();
       
  3037   var width = this.sliderBackground.width();
       
  3038   var relX = event.pageX - parentOffset.left;
       
  3039 
       
  3040   var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  3041   var newTime = ((relX / width) * duration).toFixed(2);
       
  3042 
       
  3043   this._Popcorn.currentTime(newTime);
       
  3044 };
       
  3045 
       
  3046 /* handles mouse over the slider */
       
  3047 IriSP.SliderWidget.prototype.mouseOverHandler = function(event) {
       
  3048   
       
  3049   if (this.timeOutId !== null) {
       
  3050     window.clearTimeout(this.timeOutId);
       
  3051   }
       
  3052  
       
  3053   this.sliderMaximized = true;
       
  3054 
       
  3055   this.sliderBackground.animate({"height": "9px"}, 100);
       
  3056   this.sliderForeground.animate({"height": "9px"}, 100);
       
  3057   this.positionMarker.animate({"height": "9px", "width": "9px"}, 100);
       
  3058   //this.positionMarker.css("margin-top", "-4px");
       
  3059   
       
  3060 //  this.selector.removeClass("Ldt-SliderMinimized");
       
  3061 //  this.selector.addClass("Ldt-SliderMaximized");
       
  3062 };
       
  3063 
       
  3064 /* handles when the mouse leaves the slider */
       
  3065 IriSP.SliderWidget.prototype.mouseOutHandler = function(event) {
       
  3066 
       
  3067   this.timeOutId = window.setTimeout(IriSP.wrap(this, this.minimizeOnTimeout),
       
  3068                                      IriSP.widgetsDefaults.SliderWidget.minimize_period);
       
  3069 };
       
  3070 
       
  3071 IriSP.SliderWidget.prototype.minimizeOnTimeout = function(event) {
       
  3072   this.sliderBackground.animate({"height": "5px"}, 100);
       
  3073   this.sliderForeground.animate({"height": "5px"}, 100);
       
  3074   this.positionMarker.animate({"height": "5px", "width": "5px"}, 100);
       
  3075   this.positionMarker.css("margin-top", "0px");
       
  3076   this.sliderMinimized = true;
       
  3077   
       
  3078 //  this.selector.removeClass("Ldt-SliderMaximized");
       
  3079 //  this.selector.addClass("Ldt-SliderMinimized");
       
  3080 
       
  3081 };
       
  3082 
       
  3083 // called when the user starts dragging the position indicator
       
  3084 IriSP.SliderWidget.prototype.positionMarkerDraggingStartedHandler = function(event, ui) {  
       
  3085   this.draggingOngoing = true;
       
  3086 };
       
  3087 
       
  3088 IriSP.SliderWidget.prototype.positionMarkerDraggedHandler = function(event, ui) {   
       
  3089   this._disableUpdate = true; // disable slider position updates while dragging is ongoing.
       
  3090   window.setTimeout(IriSP.wrap(this, function() { this._disableUpdate = false; }), 500);
       
  3091 
       
  3092   var parentOffset = this.sliderForeground.parent().offset();
       
  3093   var width = this.sliderBackground.width();
       
  3094   var relX = event.pageX - parentOffset.left;
       
  3095 
       
  3096   var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000;
       
  3097   var newTime = ((relX / width) * duration).toFixed(2);
       
  3098 
       
  3099   this._Popcorn.currentTime(newTime);
       
  3100   
       
  3101   this.draggingOngoing = false;
       
  3102 };
       
  3103 
       
  3104 /* this widget displays a small tooltip */
       
  3105 IriSP.TooltipWidget = function(Popcorn, config, Serializer) {
       
  3106   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3107   this._shown = false;
       
  3108   this._displayedText = "";
       
  3109   this._hideTimeout = -1;
       
  3110 };
       
  3111 
       
  3112 
       
  3113 IriSP.TooltipWidget.prototype = new IriSP.Widget();
       
  3114 
       
  3115 IriSP.TooltipWidget.prototype.draw = function() {
       
  3116   var templ = Mustache.to_html(IriSP.tooltipWidget_template);
       
  3117 
       
  3118   this.selector.append(templ);
       
  3119   this.hide();
       
  3120 
       
  3121 };
       
  3122 
       
  3123 IriSP.TooltipWidget.prototype.clear = function() {
       
  3124 	this.selector.find(".tiptext").text("");
       
  3125 };
       
  3126 
       
  3127 IriSP.TooltipWidget.prototype.show = function(text, color, x, y) {
       
  3128 
       
  3129   if (this._displayedText == text)
       
  3130     return;
       
  3131   
       
  3132   this.selector.find(".tipcolor").css("background-color", color);
       
  3133   this._displayedText = text;
       
  3134 	this.selector.find(".tiptext").text(text);
       
  3135   //this.selector.find(".tip").css("left", x).css("top", y);  
       
  3136   this.selector.find(".tip").css("left", x).css("top", y);
       
  3137   this.selector.find(".tip").show();
       
  3138   this._shown = true;
       
  3139 };
       
  3140 
       
  3141 IriSP.TooltipWidget.prototype.hide = function() {                                                   
       
  3142   this.selector.find(".tip").hide();
       
  3143   this._shown = false;  
       
  3144 };/* a widget that displays tweet - used in conjunction with the polemicWidget */
       
  3145 
       
  3146 IriSP.TweetsWidget = function(Popcorn, config, Serializer) {
       
  3147   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3148 
       
  3149   this._displayingTweet = false;
       
  3150   this._timeoutId = undefined;  
       
  3151 };
       
  3152 
       
  3153 
       
  3154 IriSP.TweetsWidget.prototype = new IriSP.Widget();
       
  3155 
       
  3156 
       
  3157 IriSP.TweetsWidget.prototype.drawTweet = function(annotation) {
       
  3158     
       
  3159     var title = IriSP.formatTweet(annotation.content.title);
       
  3160     var img = annotation.content.img.src;
       
  3161     if (typeof(img) === "undefined" || img === "" || img === "None") {
       
  3162       img = IriSP.widgetsDefaults.TweetsWidget.default_profile_picture;
       
  3163     }
       
  3164 
       
  3165     var imageMarkup = IriSP.templToHTML("<img src='{{src}}' alt='user image'></img>", 
       
  3166                                        {src : img});
       
  3167     
       
  3168     if (typeof(annotation.meta["dc:source"].content) !== "undefined") {
       
  3169       var tweetContents = JSON.parse(annotation.meta["dc:source"].content);
       
  3170       var creator = tweetContents.user.screen_name;
       
  3171       var real_name = tweetContents.user.name;
       
  3172 
       
  3173       imageMarkup = IriSP.templToHTML("<a href='http://twitter.com/{{creator}}'><img src='{{src}}' alt='user image'></img></a>", 
       
  3174                                        {src : img, creator: creator});
       
  3175             
       
  3176       var formatted_date = new Date(tweetContents.created_at).toLocaleDateString();
       
  3177       title = IriSP.templToHTML("<a class='Ldt-tweet_userHandle' href='http://twitter.com/{{creator}}'>@{{creator}}</a> - " + 
       
  3178                                 "<div class='Ldt-tweet_realName'>{{real_name}}</div>" +
       
  3179                                 "<div class='Ldt-tweet_tweetContents'>{{{ contents }}}</div>" +
       
  3180                                 "<div class='Ldt-tweet_date'>{{ date }}</div>", 
       
  3181                                 {creator: creator, real_name: real_name, contents : title, date : formatted_date});
       
  3182 
       
  3183       this.selector.find(".Ldt-TweetReply").attr("href", "http://twitter.com/home?status=@" + creator + ":%20");
       
  3184 
       
  3185 
       
  3186       var rtText = Mustache.to_html("http://twitter.com/home?status=RT @{{creator}}: {{text}}",
       
  3187                                     {creator: creator, text: IriSP.encodeURI(annotation.content.title)});
       
  3188       this.selector.find(".Ldt-Retweet").attr("href", rtText);
       
  3189     }
       
  3190 
       
  3191     this.selector.find(".Ldt-tweetContents").html(title);
       
  3192     this.selector.find(".Ldt-tweetAvatar").html(imageMarkup);
       
  3193     this.selector.show("blind", 250); 
       
  3194 };
       
  3195 
       
  3196 IriSP.TweetsWidget.prototype.displayTweet = function(annotation) {
       
  3197   if (this._displayingTweet === false) {
       
  3198     this._displayingTweet = true;
       
  3199   } else {
       
  3200     window.clearTimeout(this._timeoutId);
       
  3201   }
       
  3202 
       
  3203   this.drawTweet(annotation);
       
  3204 
       
  3205   var time = this._Popcorn.currentTime();  
       
  3206   this._timeoutId = window.setTimeout(IriSP.wrap(this, this.clearPanel), IriSP.widgetsDefaults.TweetsWidget.tweet_display_period);
       
  3207 };
       
  3208 
       
  3209 
       
  3210 IriSP.TweetsWidget.prototype.clearPanel = function() {  
       
  3211     this._displayingTweet = false;
       
  3212     this._timeoutId = undefined;
       
  3213     this.closePanel();
       
  3214     
       
  3215 };
       
  3216 
       
  3217 IriSP.TweetsWidget.prototype.closePanel = function() {
       
  3218     if (this._timeoutId != undefined) {
       
  3219       /* we're called from the "close window" link */
       
  3220       /* cancel the timeout */
       
  3221       window.clearTimeout(this._timeoutId);
       
  3222       this._timeoutId = null;
       
  3223     }
       
  3224     
       
  3225     this.selector.hide("blind", 400);
       
  3226     
       
  3227 };
       
  3228 
       
  3229 /* cancel the timeout if the user clicks on the keep panel open button */
       
  3230 IriSP.TweetsWidget.prototype.keepPanel = function() {
       
  3231     if (this._timeoutId != undefined) {
       
  3232       /* we're called from the "close window" link */
       
  3233       /* cancel the timeout */
       
  3234       window.clearTimeout(this._timeoutId);
       
  3235       this._timeoutId = null;
       
  3236     }
       
  3237 };
       
  3238 
       
  3239 IriSP.TweetsWidget.prototype.draw = function() {
       
  3240   var _this = this;
       
  3241   
       
  3242   var tweetMarkup = IriSP.templToHTML(IriSP.tweetWidget_template, {"share_template" : IriSP.share_template});
       
  3243   this.selector.append(tweetMarkup);
       
  3244   this.selector.hide();
       
  3245   this.selector.find(".Ldt-tweetWidgetMinimize").click(IriSP.wrap(this, this.closePanel));
       
  3246   this.selector.find(".Ldt-tweetWidgetKeepOpen").click(IriSP.wrap(this, this.keepPanel));
       
  3247   
       
  3248   this._Popcorn.listen("IriSP.PolemicTweet.click", IriSP.wrap(this, this.PolemicTweetClickHandler));
       
  3249 };
       
  3250 
       
  3251 IriSP.TweetsWidget.prototype.PolemicTweetClickHandler = function(tweet_id) {  
       
  3252   var index, annotation;
       
  3253   for (index in this._serializer._data.annotations) {
       
  3254     annotation = this._serializer._data.annotations[index];
       
  3255     
       
  3256     if (annotation.id === tweet_id)
       
  3257       break;
       
  3258   }
       
  3259     
       
  3260   if (annotation.id !== tweet_id)
       
  3261       /* we haven't found it */
       
  3262       return;
       
  3263   
       
  3264   this.displayTweet(annotation);
       
  3265   return;
       
  3266 };
       
  3267 
       
  3268 IriSP.JSONSerializer = function(DataLoader, url) {
       
  3269   IriSP.Serializer.call(this, DataLoader, url);
       
  3270 };
       
  3271 
       
  3272 IriSP.JSONSerializer.prototype = new IriSP.Serializer();
       
  3273 
       
  3274 IriSP.JSONSerializer.prototype.serialize = function(data) {
       
  3275   return JSON.stringify(data);
       
  3276 };
       
  3277 
       
  3278 IriSP.JSONSerializer.prototype.deserialize = function(data) {
       
  3279   return JSON.parse(data);
       
  3280 };
       
  3281 
       
  3282 IriSP.JSONSerializer.prototype.sync = function(callback) {
       
  3283   /* we don't have to do much because jQuery handles json for us */
       
  3284 
       
  3285   var self = this;
       
  3286 
       
  3287   var fn = function(data) {      
       
  3288       self._data = data;      
       
  3289       // sort the data too     
       
  3290       self._data["annotations"].sort(function(a, b) 
       
  3291           { var a_begin = +a.begin;
       
  3292             var b_begin = +b.begin;
       
  3293             return a_begin - b_begin;
       
  3294           });
       
  3295      
       
  3296       callback(data);      
       
  3297   };
       
  3298   
       
  3299   this._DataLoader.get(this._url, fn);
       
  3300 };
       
  3301 
       
  3302 IriSP.JSONSerializer.prototype.currentMedia = function() {  
       
  3303   return this._data.medias[0]; /* FIXME: don't hardcode it */
       
  3304 };
       
  3305 
       
  3306 /* this function searches for an annotation which matches title, description and keyword 
       
  3307    "" matches any field. 
       
  3308    Note: it ignores tweets.
       
  3309 */    
       
  3310 IriSP.JSONSerializer.prototype.searchAnnotations = function(title, description, keyword) {
       
  3311     /* we can have many types of annotations. We want search to only look for regular segments */
       
  3312     /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either
       
  3313        null or undefined.
       
  3314     */
       
  3315     var view;
       
  3316 
       
  3317     if (typeof(this._data.views) !== "undefined" && this._data.views !== null)
       
  3318        view = this._data.views[0];
       
  3319 
       
  3320     var searchViewType = "";
       
  3321 
       
  3322     if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) {
       
  3323             searchViewType = view.annotation_types[0];
       
  3324     }
       
  3325 
       
  3326     var filterfn = function(annotation) {
       
  3327       if( searchViewType  != "" && 
       
  3328           typeof(annotation.meta) !== "undefined" && 
       
  3329           typeof(annotation.meta["id-ref"]) !== "undefined" &&
       
  3330           annotation.meta["id-ref"] !== searchViewType) {
       
  3331         return true; // don't pass
       
  3332       } else {
       
  3333           return false;
       
  3334       }
       
  3335     };
       
  3336 
       
  3337     return this.searchAnnotationsFilter(title, description, keyword, filterfn);
       
  3338 
       
  3339 };
       
  3340 
       
  3341 /* only look for tweets */
       
  3342 IriSP.JSONSerializer.prototype.searchTweets = function(title, description, keyword) {
       
  3343     /* we can have many types of annotations. We want search to only look for regular segments */
       
  3344     /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either
       
  3345        null or undefined.
       
  3346     */
       
  3347     var view;
       
  3348 
       
  3349     if (typeof(this._data.views) !== "undefined" && this._data.views !== null)
       
  3350        view = this._data.views[0];
       
  3351 
       
  3352     var searchViewType = "";
       
  3353 
       
  3354     if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) {
       
  3355             searchViewType = view.annotation_types[0];
       
  3356     }
       
  3357 
       
  3358     var filterfn = function(annotation) {
       
  3359       if( searchViewType  != "" && 
       
  3360           typeof(annotation.meta) !== "undefined" && 
       
  3361           typeof(annotation.meta["id-ref"]) !== "undefined" &&
       
  3362           annotation.meta["id-ref"] !== searchViewType) {
       
  3363         return false; // pass
       
  3364       } else {
       
  3365           return true;
       
  3366       }
       
  3367     };
       
  3368 
       
  3369     return this.searchAnnotationsFilter(title, description, keyword, filterfn);
       
  3370 
       
  3371 };
       
  3372 
       
  3373 /*
       
  3374   the previous function call this one, which is more general:
       
  3375  */    
       
  3376 IriSP.JSONSerializer.prototype.searchAnnotationsFilter = function(title, description, keyword, filter) {
       
  3377 
       
  3378     var rTitle;
       
  3379     var rDescription;
       
  3380     var rKeyword;
       
  3381     /* match anything if given the empty string */
       
  3382     if (title == "")
       
  3383       title = ".*";
       
  3384     if (description == "")
       
  3385       description = ".*";
       
  3386     if (keyword == "")
       
  3387       keyword = ".*";
       
  3388     
       
  3389     rTitle = new RegExp(title, "i");  
       
  3390     rDescription = new RegExp(description, "i");  
       
  3391     rKeyword = new RegExp(keyword, "i");  
       
  3392     
       
  3393     var ret_array = [];
       
  3394     
       
  3395     var i;
       
  3396     for (i in this._data.annotations) {
       
  3397       var annotation = this._data.annotations[i];
       
  3398       
       
  3399       /* filter the annotations whose type is not the one we want */
       
  3400       if (filter(annotation)) {
       
  3401           continue;
       
  3402       }
       
  3403       
       
  3404       if (rTitle.test(annotation.content.title) && 
       
  3405           rDescription.test(annotation.content.description)) {
       
  3406           /* FIXME : implement keyword support */
       
  3407           ret_array.push(annotation);
       
  3408       }
       
  3409     }
       
  3410     
       
  3411     return ret_array;
       
  3412 };
       
  3413 
       
  3414 /* breaks a string in words and searches each of these words. Returns an array
       
  3415    of objects with the id of the annotation and its number of occurences.
       
  3416    
       
  3417    FIXME: optimize ? seems to be n^2 in the worst case.
       
  3418 */
       
  3419 IriSP.JSONSerializer.prototype.searchOccurences = function(searchString) {
       
  3420   var ret = { };
       
  3421   var keywords = searchString.split(/\s+/);
       
  3422   
       
  3423   for (var i in keywords) {
       
  3424     var keyword = keywords[i];
       
  3425     
       
  3426     // search this keyword in descriptions and title
       
  3427     var found_annotations = []
       
  3428     found_annotations = found_annotations.concat(this.searchAnnotations(keyword, "", ""));
       
  3429     found_annotations = found_annotations.concat(this.searchAnnotations("", keyword, ""));
       
  3430     
       
  3431     for (var j in found_annotations) {
       
  3432       var current_annotation = found_annotations[j];
       
  3433       
       
  3434       if (!ret.hasOwnProperty(current_annotation.id)) {
       
  3435         ret[current_annotation.id] = 1;
       
  3436       } else {
       
  3437         ret[current_annotation.id] += 1;
       
  3438       }
       
  3439       
       
  3440     }
       
  3441 
       
  3442   };
       
  3443   
       
  3444   return ret;
       
  3445 };
       
  3446 
       
  3447 /* breaks a string in words and searches each of these words. Returns an array
       
  3448    of objects with the id of the annotation and its number of occurences.
       
  3449    
       
  3450    FIXME: optimize ? seems to be n^2 in the worst case.
       
  3451 */
       
  3452 IriSP.JSONSerializer.prototype.searchTweetsOccurences = function(searchString) {
       
  3453   var ret = { };
       
  3454   var keywords = searchString.split(/\s+/);
       
  3455   
       
  3456   for (var i in keywords) {
       
  3457     var keyword = keywords[i];
       
  3458     
       
  3459     // search this keyword in descriptions and title
       
  3460     var found_annotations = []
       
  3461     found_annotations = found_annotations.concat(this.searchTweets(keyword, "", ""));
       
  3462     found_annotations = found_annotations.concat(this.searchTweets("", keyword, ""));
       
  3463     
       
  3464     for (var j in found_annotations) {
       
  3465       var current_annotation = found_annotations[j];
       
  3466       
       
  3467       if (!ret.hasOwnProperty(current_annotation.id)) {
       
  3468         ret[current_annotation.id] = 1;
       
  3469       } else {
       
  3470         ret[current_annotation.id] += 1;
       
  3471       }
       
  3472       
       
  3473     }
       
  3474 
       
  3475   };
       
  3476   
       
  3477   return ret;
       
  3478 };
       
  3479 
       
  3480 /* takes the currentTime and returns all the annotations that are displayable at the moment 
       
  3481    NB: only takes account the first type of annotations - ignores tweets 
       
  3482    currentTime is in seconds.
       
  3483  */
       
  3484 
       
  3485 IriSP.JSONSerializer.prototype.currentAnnotations = function(currentTime) {
       
  3486   var view;
       
  3487   var currentTimeMs = 1000 * currentTime;
       
  3488 
       
  3489   if (typeof(this._data.views) !== "undefined" && this._data.views !== null)
       
  3490      view = this._data.views[0];
       
  3491 
       
  3492   var view_type = "";
       
  3493 
       
  3494   if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length >= 1) {
       
  3495           view_type = view.annotation_types[0];
       
  3496   }
       
  3497 
       
  3498   var ret_array = [];
       
  3499   
       
  3500   var i;
       
  3501  
       
  3502   for (i in this._data.annotations) {
       
  3503     var annotation = this._data.annotations[i];
       
  3504     
       
  3505     if (annotation.meta["id-ref"] === view_type && annotation.begin <= currentTimeMs && annotation.end >= currentTimeMs)
       
  3506       ret_array.push(annotation);
       
  3507   }
       
  3508 
       
  3509   return ret_array;
       
  3510 };
       
  3511 
       
  3512 
       
  3513 /* this function returns a list of ids of tweet lines */
       
  3514 IriSP.JSONSerializer.prototype.getTweetIds = function() {
       
  3515   if (typeof(this._data.lists) === "undefined" || this._data.lists === null)
       
  3516     return [];
       
  3517 
       
  3518   var tweetsId = [];
       
  3519   
       
  3520   /* first get the list containing the tweets */
       
  3521   var tweets = IriSP.underscore.filter(this._data.lists, function(entry) { return entry.id.indexOf("tweet") !== -1 });
       
  3522   
       
  3523   // FIXME: collect tweets from multiple sources ?
       
  3524   tweetsId = IriSP.underscore.pluck(tweets[0].items, "id-ref");
       
  3525 
       
  3526   return tweetsId;
       
  3527 };
       
  3528 
       
  3529 /* this function returns a list of lines which are not tweet lines */
       
  3530 IriSP.JSONSerializer.prototype.getNonTweetIds = function() {
       
  3531   if (typeof(this._data.lists) === "undefined" || this._data.lists === null)
       
  3532     return [];
       
  3533   
       
  3534   /* get all the ids */
       
  3535   var ids = IriSP.underscore.map(this._data.lists, function(entry) {                                                         
       
  3536                                                          return IriSP.underscore.pluck(entry.items, "id-ref"); });
       
  3537                                                          
       
  3538   var illegal_values = this.getTweetIds();
       
  3539   return IriSP.underscore.difference(ids, illegal_values);
       
  3540   
       
  3541 };