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