      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 "&";
    781         case "\\": return "\\\\";
    782         case '"': return '"';
    783         case "'": return ''';
    784         case "<": return "<";
    785         case ">": return ">";
    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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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'> </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'> </span></a>		<a onclick='__IriSP.MyApiPlayer.share(\'facebook\');' title='partager avec facebook'> <span class='share shareFacebook'> </span></a><a onclick='__IriSP.MyApiPlayer.share(\'twitter\');' title='partager avec twitter'>  <span class='share shareTwitter'> </span></a><a onclick='__IriSP.MyApiPlayer.share(\'myspace\');' title='partager avec Myspace'>  <span class='share shareMySpace'> </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 };
    3542