PageSourceSearch

https://comin-ocw.org/contents/infol1/20131025/metadataplayer/LdtPlayer-core.js

js comin-ocw.org collected 2026-10-05 23:12:17 UTC 107,538 bytes, 2,968 lines download raw bytes

1/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
2    v2.0.3 (c) Kyle Simpson
3    MIT License
4*/
5(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b<a.scripts.length;b++){if(a.scripts[b].ready&&a.scripts[b].exec_trigger){c=true;a.scripts[b].exec_trigger();a.scripts[b].exec_trigger=null}}return c}function t(a,c,b,d){a.onload=a.onreadystatechange=function(){if((a.readyState&&a.readyState!="complete"&&a.readyState!="loaded")||c[b])return;a.onload=a.onreadystatechange=null;d()}}function I(a){a.ready=a.finished=true;for(var c=0;c<a.finished_listeners.length;c++){a.finished_listeners[c]()}a.ready_listeners=[];a.finished_listeners=[]}function P(d,f,e,g,h){setTimeout(function(){var a,c=f.real_src,b;if("item"in i){if(!i[0]){setTimeout(arguments.callee,25);return}i=i[0]}a=document.createElement("script");if(f.type)a.type=f.type;if(f.charset)a.charset=f.charset;if(h){if(r){e.elem=a;if(E){a.preload=true;a.onpreload=g}else{a.onreadystatechange=function(){if(a.readyState=="loaded")g()}}a.src=c}else if(h&&c.indexOf(D)==0&&d[y]){b=new XMLHttpRequest();b.onreadystatechange=function(){if(b.readyState==4){b.onreadystatechange=function(){};e.text=b.responseText+"\n//@ sourceURL="+c;g()}};b.open("GET",c);b.send()}else{a.type="text/cache-script";t(a,e,"ready",function(){i.removeChild(a);g()});a.src=c;i.insertBefore(a,i.firstChild)}}else if(F){a.async=false;t(a,e,"finished",g);a.src=c;i.insertBefore(a,i.firstChild)}else{t(a,e,"finished",g);a.src=c;i.insertBefore(a,i.firstChild)}},0)}function J(){var l={},Q=r||M,n=[],p={},m;l[y]=true;l[z]=false;l[u]=false;l[A]=false;l[B]="";function R(a,c,b){var d;function f(){if(d!=null){d=null;I(b)}}if(p[c.src].finished)return;if(!a[u])p[c.src].finished=true;d=b.elem||document.createElement("script");if(c.type)d.type=c.type;if(c.charset)d.charset=c.charset;t(d,b,"finished",f);if(b.elem){b.elem=null}else if(b.text){d.onload=d.onreadystatechange=null;d.text=b.text}else{d.src=c.real_src}i.insertBefore(d,i.firstChild);if(b.text){f()}}function S(c,b,d,f){var e,g,h=function(){b.ready_cb(b,function(){R(c,b,e)})},j=function(){b.finished_cb(b,d)};b.src=N(b.src,c[B]);b.real_src=b.src+(c[A]?((/\?.*$/.test(b.src)?"&_":"?_")+~~(Math.random()*1E9)+"="):"");if(!p[b.src])p[b.src]={items:[],finished:false};g=p[b.src].items;if(c[u]||g.length==0){e=g[g.length]={ready:false,finished:false,ready_listeners:[h],finished_listeners:[j]};P(c,b,e,((f)?function(){e.ready=true;for(var a=0;a<e.ready_listeners.length;a++){e.ready_listeners[a]()}e.ready_listeners=[]}:function(){I(e)}),f)}else{e=g[0];if(e.finished){j()}else{e.finished_listeners.push(j)}}}function v(){var e,g=s(l,{}),h=[],j=0,w=false,k;function T(a,c){a.ready=true;a.exec_trigger=c;x()}function U(a,c){a.ready=a.finished=true;a.exec_trigger=null;for(var b=0;b<c.scripts.length;b++){if(!c.scripts[b].finished)return}c.finished=true;x()}function x(){while(j<h.length){if(G(h[j])){try{h[j++]()}catch(err){}continue}else if(!h[j].finished){if(O(h[j]))continue;break}j++}if(j==h.length){w=false;k=false}}function V(){if(!k||!k.scripts){h.push(k={scripts:[],finished:true})}}e={script:function(){for(var f=0;f<arguments.length;f++){(function(a,c){var b;if(!H(a)){c=[a]}for(var d=0;d<c.length;d++){V();a=c[d];if(G(a))a=a();if(!a)continue;if(H(a)){b=[].slice.call(a);b.unshift(d,1);[].splice.apply(c,b);d--;continue}
5if(typeof a=="string")a={src:a};a=s(a,{ready:false,ready_cb:T,finished:false,finished_cb:U});k.finished=false;k.scripts.push(a);S(g,a,k,(Q&&w));w=true;if(g[z])e.wait()}})(arguments[f],arguments[f])}return e},wait:function(){if(arguments.length>0){for(var a=0;a<arguments.length;a++){h.push(arguments[a])}k=h[h.length-1]}else k=false;x();return e}};return{script:e.script,wait:e.wait,setOptions:function(a){s(a,g);return e}}}m={setGlobalDefaults:function(a){s(a,l);return m},setOptions:function(){return v().setOptions.apply(null,arguments)},script:function(){return v().script.apply(null,arguments)},wait:function(){return v().wait.apply(null,arguments)},queueScript:function(){n[n.length]={type:"script",args:[].slice.call(arguments)};return m},queueWait:function(){n[n.length]={type:"wait",args:[].slice.call(arguments)};return m},runQueue:function(){var a=m,c=n.length,b=c,d;for(;--b>=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
6/* 
7 *
8  __  __      _            _       _              _                       
9 |  \/  | ___| |_ __ _  __| | __ _| |_ __ _ _ __ | | __ _ _   _  ___ _ __ 
10 | |\/| |/ _ \ __/ _` |/ _` |/ _` | __/ _` | '_ \| |/ _` | | | |/ _ \ '__|
11 | |  | |  __/ || (_| | (_| | (_| | || (_| | |_) | | (_| | |_| |  __/ |   
12 |_|  |_|\___|\__\__,_|\__,_|\__,_|\__\__,_| .__/|_|\__,_|\__, |\___|_|   
13                                           |_|            |___/         
14
15 *  Copyright 2010-2012 Institut de recherche et d'innovation 
16 *	contributor(s) : Karim Hamidou, Samuel Huron, Raphael Velt, Thibaut Cavalie, Yves-Marie Haussonne, Nicolas Durand, Olivier Aubert
17 *	 
18 *	[email protected]
19 *	http://www.iri.centrepompidou.fr 
20 *	 
21 *	This software is a computer program whose purpose is to show and add annotations on a video .
22 *	This software is governed by the CeCILL-C license under French law and
23 *	abiding by the rules of distribution of free software. You can  use, 
24 *	modify and/ or redistribute the software under the terms of the CeCILL-C
25 *	license as circulated by CEA, CNRS and INRIA at the following URL
26 *	"http://www.cecill.info". 
27 *	
28 *	The fact that you are presently reading this means that you have had
29 *	knowledge of the CeCILL-C license and that you accept its terms.
30*/
31// Metadataplayer - version 0.1/* Initialization of the namespace */
32
33if (typeof window.IriSP === "undefined") {
34    window.IriSP = {
35        VERSION: "0.3.2"
36    };
37}
38
39if (typeof IriSP.jQuery === "undefined" && typeof window.jQuery !== "undefined") {
40    var jvp = window.jQuery().jquery.split("."),
41        jv = 100 * parseInt(jvp[0]) + parseInt(jvp[1]);
42    if (jv > 170) {
43        IriSP.jQuery = window.jQuery;
44    }
45}
46
47if (typeof IriSP._ === "undefined" && typeof window._ !== "undefined" && parseFloat(window._.VERSION) >= 1.4) {
48    IriSP._ = window._;
49}
50/* utils.js - various utils that don't belong anywhere else */
51
52IriSP.jqEscape = function(_text) {
53    return _text.replace(/(:|\.)/g, '\\$1');
54};
55
56IriSP.getLib = function(lib) {
57    if (IriSP.libFiles.useCdn && typeof IriSP.libFiles.cdn[lib] == "string") {
58        return IriSP.libFiles.cdn[lib];
59    }
60    if (typeof IriSP.libFiles.locations[lib] == "string") {
61        return IriSP.libFiles.locations[lib];
62    }
63    if (typeof IriSP.libFiles.inDefaultDir[lib] == "string") {
64        return IriSP.libFiles.defaultDir + '/' + IriSP.libFiles.inDefaultDir[lib];
65    }
66};
67
68IriSP._cssCache = [];
69
70IriSP.loadCss = function(_cssFile) {
71    if (IriSP._(IriSP._cssCache).indexOf(_cssFile) === -1) {
72        IriSP.jQuery("<link>", {
73            rel : "stylesheet",
74            type : "text/css",
75            href : _cssFile
76        }).appendTo('head');
77        IriSP._cssCache.push(_cssFile);
78    }
79};
80
81IriSP.textFieldHtml = function(_text, _regexp, _extend) {
82    var list = [],
83        positions = [],
84        text = _text.trim();
85
86    function addToList(_rx, _startHtml, _endHtml) {
87        while (true) {
88            var result = _rx.exec(text);
89            if (!result) {
90                break;
91            }
92            var end = _rx.lastIndex,
93                start = result.index;
94            list.push({
95                start: start,
96                end: end,
97                startHtml: (typeof _startHtml === "function" ? _startHtml(result) : _startHtml),
98                endHtml: (typeof _endHtml === "function" ? _endHtml(result) : _endHtml)
99            });
100            positions.push(start);
101            positions.push(end);
102        }
103    }
104
105    if (_regexp) {
106        addToList(_regexp, '<span class="Ldt-Highlight">', '</span>');
107    }
108
109    addToList(/(https?:\/\/)?[\w\d\-]+\.\w[\w\d\-]+\S+/gm, function(matches) {
110        return '<a href="' + (matches[1] ? '' : 'http://') + matches[0] + '" target="_blank">';
111    }, '</a>');
112    addToList(/@([\d\w]{1,15})/gm, function(matches) {
113        return '<a href="http://twitter.com/' + matches[1] + '" target="_blank">';
114    }, '</a>');
115    addToList(/\*[^*]+\*/gm, '<b>', '</b>');
116    addToList(/[\n\r]+/gm, '', '<br />');
117
118    IriSP._(_extend).each(function(x) {
119        addToList.apply(null, x);
120    });
121
122    positions = IriSP._(positions)
123        .chain()
124        .uniq()
125        .sortBy(function(p) { return parseInt(p); })
126        .value();
127
128    var res = "", lastIndex = 0;
129
130    for (var i = 0; i < positions.length; i++) {
131        var pos = positions[i];
132        res += text.substring(lastIndex, pos);
133        for (var j = list.length - 1; j >= 0; j--) {
134            var item = list[j];
135            if (item.start < pos && item.end >= pos) {
136                res += item.endHtml;
137            }
138        }
139        for (var j = 0; j < list.length; j++) {
140            var item = list[j];
141            if (item.start <= pos && item.end > pos) {
142                res += item.startHtml;
143            }
144        }
145        lastIndex = pos;
146    }
147
148    res += text.substring(lastIndex);
149
150    return res;
151
152};
153
154IriSP.log = function() {
155    if (typeof console !== "undefined" && typeof IriSP.logging !== "undefined" && IriSP.logging) {
156        console.log.apply(console, arguments);
157    }
158};
159
160IriSP.attachDndData = function(jqSel, data) {
161    jqSel.attr("draggable", "true").on("dragstart", function(_event) {
162        var d = (typeof data === "function" ? data.call(this) : data);
163        try {
164            if (d.html === undefined && d.uri && d.text) {
165                d.html = '<a href="' + d.uri + '">' + d.text + '</a>';
166            }
167            IriSP._(d).each(function(v, k) {
168                if (v && k != 'text' && k != 'html') {
169                    _event.originalEvent.dataTransfer.setData("text/x-iri-" + k, v);
170                }
171            });
172            if (d.uri && d.text) {
173                _event.originalEvent.dataTransfer.setData("text/x-moz-url", d.uri + "\n" + d.text.replace("\n", " "));
174                _event.originalEvent.dataTransfer.setData("text/plain", d.text + " " + d.uri);
175            }
176            // Define generic text/html and text/plain last (least
177            // specific types, see
178            // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_operations#Drag_Data)
179            if (d.html !== undefined) {
180                _event.originalEvent.dataTransfer.setData("text/html", d.html);
181            }
182            if (d.text !== undefined && !d.uri) {
183                _event.originalEvent.dataTransfer.setData("text/plain", d.text);
184            }
185        } catch (err) {
186            _event.originalEvent.dataTransfer.setData("Text", JSON.stringify(d));
187        }
188    });
189};
190
191IriSP.FakeClass = function(properties) {
192    var _this = this,
193        noop = (function() {});
194    IriSP._(properties).each(function(p) {
195        _this[p] = noop;
196    });
197};
198
199IriSP.timestamp2ms = function(t) {
200    // Convert timestamp to numeric value
201    // It accepts the following forms:
202    // [h:mm:ss] [mm:ss] [ss]
203    var s = t.split(":").reverse();
204    while (s.length < 3) {
205        s.push("0");
206    }
207    return 1000 * (3600 * parseInt(s[2], 10) + 60 * parseInt(s[1], 10) + parseInt(s[0], 10));
208};
209
210IriSP.setFullScreen = function(elem, value) {
211    // Set fullscreen on or off
212    if (value) {
213        if (elem.requestFullscreen) {
214            elem.requestFullscreen();
215        } else if (elem.mozRequestFullScreen) {
216            elem.mozRequestFullScreen();
217        } else if (elem.webkitRequestFullscreen) {
218            elem.webkitRequestFullscreen();
219        } else if (elem.msRequestFullscreen) {
220            elem.msRequestFullscreen();
221        }
222    } else {
223        if (document.exitFullscreen) {
224            document.exitFullscreen();
225        } else if (document.msExitFullscreen) {
226            document.msExitFullscreen();
227        } else if (document.mozCancelFullScreen) {
228            document.mozCancelFullScreen();
229        } else if (document.webkitExitFullscreen) {
230            document.webkitExitFullscreen();
231        }
232    }
233};
234
235IriSP.isFullscreen = function() {
236    return (document.fullscreenElement ||  document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
237};
238
239IriSP.getFullscreenElement = function () {
240    return (document.fullscreenElement
241            || document.webkitFullscreenElement
242            || document.mozFullScreenElement
243            || document.msFullscreenElement
244            || undefined);
245};
246
247IriSP.getFullscreenEventname = function () {
248    return ((document.exitFullscreen && "fullscreenchange")
249            || (document.webkitExitFullscreen && "webkitfullscreenchange")
250            || (document.mozExitFullScreen && "mozfullscreenchange")
251            || (document.msExitFullscreen && "msfullscreenchange")
252            || "");
253};
254
255/**
256 * Generate a UUID
257 * Source: http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
258 */
259IriSP.generateUuid = function () {
260    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
261        var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
262        return v.toString(16);
263    });
264};
265
266/**
267 * Return a non-accentuated version of the given string
268 */
269IriSP.unaccent = function (s) {
270    return s.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
271};
272
273/* js is where data is stored in a standard form, whatever the serializer */
274
275//TODO: Separate Project-specific data from Source
276
277IriSP.Model = (function (ns) {
278
279    function pad(n, x, b) {
280        b = b || 10;
281        var s = (x).toString(b);
282        while (s.length < n) {
283            s = "0" + s;
284        }
285        return s;
286    }
287
288    function rand16(n) {
289        return pad(n, Math.floor(Math.random() * Math.pow(16, n)), 16);
290    }
291
292    var uidbase = rand16(8) + "-" + rand16(4) + "-", uidincrement = Math.floor(Math.random() * 0x10000);
293
294    var charsub = [
295        '[aáàâä]',
296        '[cç]',
297        '[eéèêë]',
298        '[iíìîï]',
299        '[oóòôö]',
300        '[uùûü]'
301    ];
302
303    var removeChars = [
304        String.fromCharCode(768), String.fromCharCode(769), String.fromCharCode(770), String.fromCharCode(771), String.fromCharCode(807),
305        "{", "}", "(", ")", "[", "]", "【", "】", "、", "・", "‥", "。", "「", "」", "『", "』", "〜", ":", "!", "?", " ",
306        ",", " ", ";", "(", ")", ".", "*", "+", "\\", "?", "|", "{", "}", "[", "]", "^", "#", "/"
307    ];
308
309    var Model = {},
310        _SOURCE_STATUS_EMPTY = Model._SOURCE_STATUS_EMPTY = 0,
311        _SOURCE_STATUS_WAITING = Model._SOURCE_STATUS_WAITING = 1,
312        _SOURCE_STATUS_READY = Model._SOURCE_STATUS_READY = 2,
313        extendPrototype = Model.extendPrototype = function(toClass, fromClass) {
314            var fromP = fromClass.prototype,
315                toP = toClass.prototype;
316            for (var k in fromP) {
317                if (fromP.hasOwnProperty(k)) {
318                    toP[k] = fromP[k];
319                }
320            }
321        },
322        getUID = Model.getUID = function() {
323            return uidbase + pad(4, (++uidincrement % 0x10000), 16) + "-" + rand16(4) + "-" + rand16(6) + rand16(6);
324        },
325        isLocalURL = Model.isLocalURL = function(url) {
326            var matches = url.match(/^(\w+:)\/\/([^/]+)/);
327            if (matches) {
328                return (matches[1] === document.location.protocol && matches[2] === document.location.host);
329            }
330            return true;
331        },
332        regexpFromTextOrArray = Model.regexpFromTextOrArray = function(_textOrArray, _testOnly, _iexact) {
333            var _testOnly = _testOnly || false,
334                _iexact = _iexact || false;
335            function escapeText(_text) {
336                return _text.replace(/([\\\*\+\?\|\{\[\}\]\(\)\^\$\.\#\/])/gm, '\\$1');
337            }
338            var _source =
339                    typeof _textOrArray === "string"
340                    ? escapeText(_textOrArray)
341                    : ns._(_textOrArray).map(escapeText).join("|"),
342                _flags = 'im';
343            if (!_testOnly) {
344                _source = '(' + _source + ')';
345                _flags += 'g';
346            }
347            if (_iexact) {
348                _source = '^' + _source + '$';
349            }
350            return new RegExp(_source, _flags);
351        },
352        fullTextRegexps = Model.fullTextRegexps = function(_text) {
353            var remsrc = "[\\" + removeChars.join("\\") + "]",
354                remrx = new RegExp(remsrc, "gm"),
355                txt = _text.toLowerCase().replace(remrx, ""),
356                res = [],
357                charsrx = ns._(charsub).map(function(c) {
358                    return new RegExp(c);
359                }),
360                src = "";
361            for (var j = 0; j < txt.length; j++) {
362                if (j) {
363                    src += remsrc + "*";
364                }
365                var l = txt[j];
366                ns._(charsub).each(function(v, k) {
367                    l = l.replace(charsrx[k], v);
368                });
369                src += l;
370            }
371            return "(" + src + ")";
372        },
373        isoToDate = Model.isoToDate = function(_str) {
374            // http://delete.me.uk/2005/03/iso8601.html
375            var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
376            var d = _str.match(new RegExp(regexp));
377
378            var offset = 0;
379            var date = new Date(d[1], 0, 1);
380
381            if (d[3]) { date.setMonth(d[3] - 1); }
382            if (d[5]) { date.setDate(d[5]); }
383            if (d[7]) { date.setHours(d[7]); }
384            if (d[8]) { date.setMinutes(d[8]); }
385            if (d[10]) { date.setSeconds(d[10]); }
386            if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
387            if (d[14]) {
388                offset = (Number(d[16]) * 60) + Number(d[17]);
389                offset *= ((d[15] == '-') ? 1 : -1);
390            }
391
392            offset -= date.getTimezoneOffset();
393            time = (Number(date) + (offset * 60 * 1000));
394            var _res = new Date();
395            _res.setTime(Number(time));
396            return _res;
397        },
398        dateToIso = Model.dateToIso = function(_d) {
399            var d = _d ? new Date(_d) : new Date();
400            return d.getUTCFullYear() + '-'
401                + pad(2, d.getUTCMonth() + 1) + '-'
402                + pad(2, d.getUTCDate()) + 'T'
403                + pad(2, d.getUTCHours()) + ':'
404                + pad(2, d.getUTCMinutes()) + ':'
405                + pad(2, d.getUTCSeconds()) + 'Z'  ;
406        };
407
408    /*
409     * List is a class for a list of elements (e.g. annotations, medias, etc. that each have a distinct ID)
410     */
411    var List = Model.List = function(_directory) {
412        Array.call(this);
413        this.directory = _directory;
414        this.idIndex = [];
415        this.__events = {};
416        if (typeof _directory == "undefined") {
417            console.trace();
418            throw "Error : new List(directory): directory is undefined";
419        }
420        var _this =  this;
421        this.on("clear-search", function() {
422            _this.searching = false;
423            _this.regexp = undefined;
424            _this.forEach(function(_element) {
425                _element.found = undefined;
426            });
427            _this.trigger("search-cleared");
428        });
429    };
430
431    List.prototype = new Array();
432
433    List.prototype.hasId = function(_id) {
434        return ns._(this.idIndex).include(_id);
435    };
436
437    /* On recent browsers, forEach and map are defined and do what we want.
438     * Otherwise, we'll use the Underscore.js functions
439     */
440    if (typeof Array.prototype.forEach === "undefined") {
441        List.prototype.forEach = function(_callback) {
442            var _this = this;
443            ns._(this).forEach(function(_value, _key) {
444                _callback(_value, _key, _this);
445            });
446        };
447    };
448
449    if (typeof Array.prototype.map === "undefined") {
450        List.prototype.map = function(_callback) {
451            var _this = this;
452            return ns._(this).map(function(_value, _key) {
453                return _callback(_value, _key, _this);
454            });
455        };
456    };
457
458    List.prototype.pluck = function(_key) {
459        return this.map(function(_value) {
460            return _value[_key];
461        });
462    };
463
464    /* We override Array's filter function because it doesn't return an List
465     */
466    List.prototype.filter = function(_callback) {
467        var _this = this,
468            _res = new List(this.directory);
469        _res.addElements(ns._(this).filter(function(_value, _key) {
470            return _callback(_value, _key, _this);
471        }));
472        return _res;
473    };
474
475    List.prototype.slice = function(_start, _end) {
476        var _res = new List(this.directory);
477        _res.addElements(Array.prototype.slice.call(this, _start, _end));
478        return _res;
479    };
480
481    List.prototype.splice = function(_start, _end) {
482        var _res = new List(this.directory);
483        _res.addElements(Array.prototype.splice.call(this, _start, _end));
484        this.idIndex.splice(_start, _end);
485        return _res;
486    };
487
488    /* Array has a sort function, but it's not as interesting as Underscore.js's sortBy
489     * and won't return a new List
490     */
491    List.prototype.sortBy = function(_callback) {
492        var _this = this,
493            _res = new List(this.directory);
494        _res.addElements(ns._(this).sortBy(function(_value, _key) {
495            return _callback(_value, _key, _this);
496        }));
497        return _res;
498    };
499
500    /* Title and Description are basic information for (almost) all element types,
501     * here we can search by these criteria
502     */
503    List.prototype.searchByTitle = function(_text, _iexact) {
504        var _iexact = _iexact || false,
505            _rgxp = regexpFromTextOrArray(_text, true, _iexact);
506        return this.filter(function(_element) {
507            return _rgxp.test(_element.title);
508        });
509    };
510
511    List.prototype.searchByDescription = function(_text, _iexact) {
512        var _iexact = _iexact || false,
513            _rgxp = regexpFromTextOrArray(_text, true, _iexact);
514        return this.filter(function(_element) {
515            return _rgxp.test(_element.description);
516        });
517    };
518
519    List.prototype.searchByTextFields = function(_text, _iexact) {
520        var _iexact = _iexact || false,
521            _rgxp =  regexpFromTextOrArray(_text, true, _iexact);
522        return this.filter(function(_element) {
523            var keywords = (_element.keywords || _element.getTagTexts() || []).join(", ");
524            return _rgxp.test(_element.description) || _rgxp.test(_element.title) || _rgxp.test(keywords);
525        });
526    };
527
528    List.prototype.search = function(_text) {
529        if (!_text) {
530            this.trigger("clear-search");
531            return this;
532        }
533        this.searching = true;
534        this.trigger("search", _text);
535        var rxsource = fullTextRegexps(_text),
536            rgxp = new RegExp(rxsource,"im");
537        this.regexp = new RegExp(rxsource,"gim");
538        var res = this.filter(function(_element, _k) {
539            var titlematch = rgxp.test(_element.title),
540                descmatch = rgxp.test(_element.description),
541                _isfound = !!(titlematch || descmatch);
542            _element.found = _isfound;
543            _element.trigger(_isfound ? "found" : "not-found");
544            return _isfound;
545        });
546        this.trigger(res.length ? "found" : "not-found", res);
547        return res;
548    };
549
550    List.prototype.searchByTags = function(_text) {
551        if (!_text) {
552            this.trigger("clear-search");
553            return this;
554        }
555        this.searching = true;
556        this.trigger("search", _text);
557        var rxsource = fullTextRegexps(_text),
558            rgxp = new RegExp(rxsource,"im");
559        this.regexp = new RegExp(rxsource,"gim");
560        var res = this.filter(function(_element, _k) {
561            var _isfound = rgxp.test(_element.getTagTexts());
562            _element.found = _isfound;
563            _element.trigger(_isfound ? "found" : "not-found");
564            return _isfound;
565        });
566        this.trigger(res.length ? "found" : "not-found", res);
567        return res;
568    };
569
570    List.prototype.getTitles = function() {
571        return this.map(function(_el) {
572            return _el.title;
573        });
574    };
575
576    List.prototype.addId = function(_id) {
577        var _el = this.directory.getElement(_id);
578        if (!this.hasId(_id) && typeof _el !== "undefined") {
579            this.idIndex.push(_id);
580            Array.prototype.push.call(this, _el);
581        }
582    };
583
584    List.prototype.push = function(_el) {
585        if (typeof _el === "undefined") {
586            return;
587        }
588        var _index = (ns._(this.idIndex).indexOf(_el.id));
589        if (_index === -1) {
590            this.idIndex.push(_el.id);
591            Array.prototype.push.call(this, _el);
592        } else {
593            this[_index] = _el;
594        }
595    };
596
597    List.prototype.addIds = function(_array) {
598        var _l = _array.length,
599            _this = this;
600        ns._(_array).forEach(function(_id) {
601            _this.addId(_id);
602        });
603    };
604
605    List.prototype.addElements = function(_array) {
606        var _this = this;
607        ns._(_array).forEach(function(_el) {
608            _this.push(_el);
609        });
610    };
611
612    List.prototype.removeId = function(_id, _deleteFromDirectory) {
613        var _deleteFromDirectory = _deleteFromDirectory || false,
614            _index = (ns._(this.idIndex).indexOf(_id));
615        if (_index !== -1) {
616            this.splice(_index, 1);
617        }
618        if (_deleteFromDirectory) {
619            delete this.directory.elements[_id];
620        }
621    };
622
623    List.prototype.removeElement = function(_el, _deleteFromDirectory) {
624        var _deleteFromDirectory = _deleteFromDirectory || false;
625        this.removeId(_el.id, _deleteFromDirectory);
626    };
627
628    List.prototype.removeIds = function(_list, _deleteFromDirectory) {
629        var _deleteFromDirectory = _deleteFromDirectory || false,
630            _this = this;
631        ns._(_list).forEach(function(_id) {
632            _this.removeId(_id, _deleteFromDirectory);
633        });
634    };
635
636    List.prototype.removeElements = function(_list, _deleteFromDirectory) {
637        var _deleteFromDirectory = _deleteFromDirectory || false,
638            _this = this;
639        ns._(_list).forEach(function(_el) {
640            _this.removeElement(_el, _deleteFromDirectory);
641        });
642    };
643
644    List.prototype.on = function(_event, _callback) {
645        if (typeof this.__events[_event] === "undefined") {
646            this.__events[_event] = [];
647        }
648        this.__events[_event].push(_callback);
649    };
650
651    List.prototype.off = function(_event, _callback) {
652        if (typeof this.__events[_event] !== "undefined") {
653            this.__events[_event] = ns._(this.__events[_event]).reject(function(_fn) {
654                return _fn === _callback;
655            });
656        }
657    };
658
659    List.prototype.trigger = function(_event, _data) {
660        var _list = this;
661        ns._(this.__events[_event]).each(function(_callback) {
662            _callback.call(_list, _data);
663        });
664    };
665
666    /* A simple time management object, that helps converting millisecs to seconds and strings,
667     * without the clumsiness of the original Date object.
668     */
669
670    var Time = Model.Time = function(_milliseconds) {
671        this.milliseconds = 0;
672        this.setMilliseconds(_milliseconds);
673    };
674
675    Time.prototype.setMilliseconds = function(_milliseconds) {
676        var _ante = this.milliseconds;
677        switch (typeof _milliseconds) {
678        case "string":
679            this.milliseconds = parseInt(_milliseconds);
680            break;
681        case "number":
682            this.milliseconds = Math.floor(_milliseconds);
683            break;
684        case "object":
685            this.milliseconds = parseInt(_milliseconds.valueOf());
686            break;
687        default:
688            this.milliseconds = 0;
689        }
690        if (this.milliseconds === NaN) {
691            this.milliseconds = _ante;
692        }
693    };
694
695    Time.prototype.setSeconds = function(_seconds) {
696        this.milliseconds = 1000 * _seconds;
697    };
698
699    Time.prototype.getSeconds = function() {
700        return this.milliseconds / 1000;
701    };
702
703    Time.prototype.getHMS = function() {
704        var _totalSeconds = Math.abs(Math.floor(this.getSeconds()));
705        return {
706            hours : Math.floor(_totalSeconds / 3600),
707            minutes : (Math.floor(_totalSeconds / 60) % 60),
708            seconds : _totalSeconds % 60,
709            milliseconds: this.milliseconds % 1000
710        };
711    };
712
713    Time.prototype.add = function(_milliseconds) {
714        this.milliseconds += new Time(_milliseconds).milliseconds;
715    };
716
717    Time.prototype.valueOf = function() {
718        return this.milliseconds;
719    };
720
721    Time.prototype.toString = function(showCs) {
722        var _hms = this.getHMS(),
723            _res = '';
724        if (_hms.hours) {
725            _res += _hms.hours + ':';
726        }
727        _res += pad(2, _hms.minutes) + ':' + pad(2, _hms.seconds);
728        if (showCs) {
729            _res += "." + Math.floor(_hms.milliseconds / 100);
730        }
731        return _res;
732    };
733
734    /* Reference handles references between elements
735     */
736
737    var Reference = Model.Reference = function(_source, _idRef) {
738        this.source = _source;
739        this.id = _idRef;
740        if (typeof _idRef === "object") {
741            this.isList = true;
742        } else {
743            this.isList = false;
744        }
745        this.refresh();
746    };
747
748    Reference.prototype.refresh = function() {
749        if (this.isList) {
750            this.contents = new List(this.source.directory);
751            this.contents.addIds(this.id);
752        } else {
753            this.contents = this.source.getElement(this.id);
754        }
755
756    };
757
758    Reference.prototype.getContents = function() {
759        if (typeof this.contents === "undefined" || (this.isList && this.contents.length != this.id.length)) {
760            this.refresh();
761        }
762        return this.contents;
763    };
764
765    Reference.prototype.isOrHasId = function(_idRef) {
766        if (this.isList) {
767            return (ns._(this.id).indexOf(_idRef) !== -1);
768        } else {
769            return (this.id == _idRef);
770        }
771    };
772
773    /* */
774
775    var BaseElement = Model.Element = function(_id, _source) {
776        this.elementType = 'element';
777        this.title = "";
778        this.description = "";
779        this.__events = {};
780        if (typeof _source === "undefined") {
781            return;
782        }
783        if (typeof _id === "undefined" || !_id) {
784            _id = getUID();
785        }
786        this.id = _id;
787        this.source = _source;
788        if (_source !== this) {
789            this.source.directory.addElement(this);
790        }
791    };
792
793    BaseElement.prototype.toString = function() {
794        return this.elementType + (this.elementType !== 'element' ? ', id=' + this.id + ', title="' + this.title + '"' : '');
795    };
796
797    BaseElement.prototype.setReference = function(_elementType, _idRef) {
798        this[_elementType] = new Reference(this.source, _idRef);
799    };
800
801    BaseElement.prototype.getReference = function(_elementType) {
802        if (typeof this[_elementType] !== "undefined") {
803            return this[_elementType].getContents();
804        }
805    };
806
807    BaseElement.prototype.getRelated = function(_elementType, _global) {
808        _global = (typeof _global !== "undefined" && _global);
809        var _this = this;
810        return this.source.getList(_elementType, _global).filter(function(_el) {
811            var _ref = _el[_this.elementType];
812            return _ref && _ref.isOrHasId(_this.id);
813        });
814    };
815
816    /**
817     * Return a short surrogate for the element:
818     * either its title or the first line of its description
819     */
820    BaseElement.prototype.getTitleOrDescription = function () {
821        var t = this.title || this.description || "";
822        t = t.split(/\n/)[0];
823        return t;
824    };
825
826    BaseElement.prototype.on = function(_event, _callback) {
827        if (typeof this.__events[_event] === "undefined") {
828            this.__events[_event] = [];
829        }
830        this.__events[_event].push(_callback);
831    };
832
833    BaseElement.prototype.off = function(_event, _callback) {
834        if (typeof this.__events[_event] !== "undefined") {
835            this.__events[_event] = ns._(this.__events[_event]).reject(function(_fn) {
836                return _fn === _callback;
837            });
838        }
839    };
840
841    BaseElement.prototype.trigger = function(_event, _data) {
842        var _element = this;
843        ns._(this.__events[_event]).each(function(_callback) {
844            _callback.call(_element, _data);
845        });
846    };
847
848    /* */
849
850    var Playable = Model.Playable = function(_id, _source) {
851        BaseElement.call(this, _id, _source);
852        if (typeof _source === "undefined") {
853            return;
854        }
855        this.elementType = 'playable';
856        this.currentTime = new Time();
857        this.volume = .5;
858        this.paused = true;
859        this.muted = false;
860        this.timeRange = false;
861        this.loadedMetadata = false;
862        var _this = this;
863        this.on("play", function() {
864            _this.paused = false;
865        });
866        this.on("pause", function() {
867            _this.paused = true;
868        });
869        this.on("timeupdate", function(_time) {
870            _this.currentTime = _time;
871            _this.getAnnotations().filter(function(_a) {
872                return (_a.end <= _time || _a.begin > _time) && _a.playing;
873            }
873).forEach(function(_a) {
874                _a.playing = false;
875                _a.trigger("leave");
876                _this.trigger("leave-annotation", _a);
877            });
878            _this.getAnnotations().filter(function(_a) {
879                return _a.begin <= _time && _a.end > _time && !_a.playing;
880            }).forEach(function(_a) {
881                _a.playing = true;
882                _a.trigger("enter");
883                _this.trigger("enter-annotation", _a);
884            });
885
886            if (_this.getTimeRange()) {
887                if (_this.getTimeRange()[0] > _time) {
888                    _this.pause();
889                    _this.setCurrentTime(_this.getTimeRange()[0]);
890                }
891                if (_this.getTimeRange()[1] < _time) {
892                    _this.pause();
893                    _this.setCurrentTime(_this.getTimeRange()[1]);
894                }
895            }
896
897        });
898        this.on("loadedmetadata", function() {
899            _this.loadedMetadata = true;
900        });
901    };
902
903    extendPrototype(Playable, BaseElement);
904
905    Playable.prototype.getCurrentTime = function() {
906        return this.currentTime;
907    };
908
909    Playable.prototype.getVolume = function() {
910        return this.volume;
911    };
912
913    Playable.prototype.getPaused = function() {
914        return this.paused;
915    };
916
917    Playable.prototype.getMuted = function() {
918        return this.muted;
919    };
920
921    Playable.prototype.getTimeRange = function() {
922        return this.timeRange;
923    };
924
925    Playable.prototype.setCurrentTime = function(_time) {
926        this.trigger("setcurrenttime", _time);
927    };
928
929    Playable.prototype.setVolume = function(_vol) {
930        this.trigger("setvolume", _vol);
931    };
932
933    Playable.prototype.setMuted = function(_muted) {
934        this.trigger("setmuted", _muted);
935    };
936
937    Playable.prototype.setTimeRange = function(_timeBegin, _timeEnd) {
938        if ((_timeBegin < _timeEnd) && (_timeBegin >= 0) && (_timeEnd > 0)) {
939            return this.trigger("settimerange", [_timeBegin, _timeEnd]);
940        }
941    };
942
943    Playable.prototype.resetTimeRange = function() {
944        return this.trigger("resettimerange");
945    };
946
947    Playable.prototype.play = function() {
948        this.trigger("setplay");
949    };
950
951    Playable.prototype.pause = function() {
952        this.trigger("setpause");
953    };
954
955    Playable.prototype.togglePlayPause = function () {
956        if (this.getPaused()) {
957            this.play();
958        } else {
959            this.pause();
960        };
961    };
962
963    Playable.prototype.show = function() {};
964
965    Playable.prototype.hide = function() {};
966
967    /* */
968
969    var Media = Model.Media = function(_id, _source) {
970        Playable.call(this, _id, _source);
971        this.elementType = 'media';
972        this.duration = new Time();
973        this.video = '';
974        var _this = this;
975    };
976
977    extendPrototype(Media, Playable);
978    /* */
979
980    var Media = Model.Media = function(_id, _source) {
981        Playable.call(this, _id, _source);
982        this.elementType = 'media';
983        this.duration = new Time();
984        this.video = '';
985        var _this = this;
986    };
987
988    extendPrototype(Media, Playable);
989
990    /* Default functions to be overriden by players */
991
992    Media.prototype.setDuration = function(_durationMs) {
993        this.duration.setMilliseconds(_durationMs);
994    };
995
996    Media.prototype.getAnnotations = function() {
997        return this.getRelated("annotation");
998    };
999
1000    Media.prototype.getAnnotationsByTypeTitle = function(_title) {
1001        var _annTypes = this.source.getAnnotationTypes().searchByTitle(_title).pluck("id");
1002        if (_annTypes.length) {
1003            return this.getAnnotations().filter(function(_annotation) {
1004                return ns._(_annTypes).indexOf(_annotation.getAnnotationType().id) !== -1;
1005            });
1006        } else {
1007            return new List(this.source.directory);
1008        }
1009    };
1010
1011    /* */
1012
1013    var Tag = Model.Tag = function(_id, _source) {
1014        BaseElement.call(this, _id, _source);
1015        this.elementType = 'tag';
1016    };
1017
1018    extendPrototype(Tag, BaseElement);
1019
1020    Tag.prototype.getAnnotations = function() {
1021        return this.getRelated("annotation");
1022    };
1023
1024    /* */
1025    var AnnotationType = Model.AnnotationType = function(_id, _source) {
1026        BaseElement.call(this, _id, _source);
1027        this.elementType = 'annotationType';
1028    };
1029
1030    extendPrototype(AnnotationType, BaseElement);
1031
1032    AnnotationType.prototype.getAnnotations = function() {
1033        return this.getRelated("annotation");
1034    };
1035
1036    /* Annotation
1037     * */
1038
1039    var Annotation = Model.Annotation = function(_id, _source) {
1040        BaseElement.call(this, _id, _source);
1041        this.elementType = 'annotation';
1042        this.begin = new Time();
1043        this.end = new Time();
1044        this.tag = new Reference(_source, []);
1045        this.playing = false;
1046        var _this = this;
1047        this.on("click", function() {
1048            _this.getMedia().setCurrentTime(_this.begin);
1049        });
1050    };
1051
1052    extendPrototype(Annotation, BaseElement);
1053
1054    /* Set begin and end in one go, to avoid undesired side-effects in
1055     * setBegin/setEnd interaction */
1056    Annotation.prototype.setBeginEnd = function(_beginMs, _endMs) {
1057        _beginMs = Math.max(0, _beginMs);
1058        _endMs = Math.max(0, _endMs);
1059        if (_endMs < _beginMs) {
1060            _endMs = _beginMs;
1061        }
1062        this.begin.setMilliseconds(_beginMs);
1063        this.end.setMilliseconds(_endMs);
1064        this.trigger("change-begin");
1065        this.trigger("change-end");
1066    };
1067
1068    Annotation.prototype.setBegin = function(_beginMs) {
1069        this.begin.setMilliseconds(Math.max(0, _beginMs));
1070        this.trigger("change-begin");
1071        if (this.end < this.begin) {
1072            this.setEnd(this.begin);
1073        }
1074    };
1075
1076    Annotation.prototype.setEnd = function(_endMs) {
1077        this.end.setMilliseconds(Math.min(_endMs, this.getMedia().duration.milliseconds));
1078        this.trigger("change-end");
1079        if (this.end < this.begin) {
1080            this.setBegin(this.end);
1081        }
1082    };
1083
1084    Annotation.prototype.setDuration = function(_durMs) {
1085        this.setEnd(_durMs + this.begin.milliseconds);
1086    };
1087
1088    Annotation.prototype.setMedia = function(_idRef) {
1089        this.setReference("media", _idRef);
1090    };
1091
1092    Annotation.prototype.getMedia = function() {
1093        return this.getReference("media");
1094    };
1095
1096    Annotation.prototype.setAnnotationType = function(_idRef) {
1097        this.setReference("annotationType", _idRef);
1098    };
1099
1100    Annotation.prototype.getAnnotationType = function() {
1101        return this.getReference("annotationType");
1102    };
1103
1104    Annotation.prototype.setTags = function(_idRefs) {
1105        this.setReference("tag", _idRefs);
1106    };
1107
1108    Annotation.prototype.getTags = function() {
1109        return this.getReference("tag");
1110    };
1111
1112    Annotation.prototype.getTagTexts = function() {
1113        return this.getTags().getTitles();
1114    };
1115
1116    Annotation.prototype.getDuration = function() {
1117        return new Time(this.end.milliseconds - this.begin.milliseconds);
1118    };
1119
1120    /* */
1121
1122    var MashedAnnotation = Model.MashedAnnotation = function(_mashup, _annotation) {
1123        BaseElement.call(this, _mashup.id + "_" + _annotation.id, _annotation.source);
1124        this.elementType = 'mashedAnnotation';
1125        this.annotation = _annotation;
1126        this.begin = new Time();
1127        this.end = new Time();
1128        this.duration = new Time();
1129        this.title = this.annotation.title;
1130        this.description = this.annotation.description;
1131        this.color = this.annotation.color;
1132        var _this = this;
1133        this.on("click", function() {
1134            _mashup.setCurrentTime(_this.begin);
1135        });
1136        this.on("enter", function() {
1137            _this.annotation.trigger("enter");
1138        });
1139        this.on("leave", function() {
1140            _this.annotation.trigger("leave");
1141        });
1142    };
1143
1144    extendPrototype(MashedAnnotation, BaseElement);
1145
1146    MashedAnnotation.prototype.getMedia = function() {
1147        return this.annotation.getReference("media");
1148    };
1149
1150    MashedAnnotation.prototype.getAnnotationType = function() {
1151        return this.annotation.getReference("annotationType");
1152    };
1153
1154    MashedAnnotation.prototype.getTags = function() {
1155        return this.annotation.getReference("tag");
1156    };
1157
1158    MashedAnnotation.prototype.getTagTexts = function() {
1159        return this.annotation.getTags().getTitles();
1160    };
1161
1162    MashedAnnotation.prototype.getDuration = function() {
1163        return this.annotation.getDuration();
1164    };
1165
1166    MashedAnnotation.prototype.setBegin = function(_begin) {
1167        this.begin.setMilliseconds(_begin);
1168        this.duration.setMilliseconds(this.annotation.getDuration());
1169        this.end.setMilliseconds(_begin + this.duration);
1170    };
1171
1172    /* */
1173
1174    var Mashup = Model.Mashup = function(_id, _source) {
1175        Playable.call(this, _id, _source);
1176        this.elementType = 'mashup';
1177        this.duration = new Time();
1178        this.segments = new List(_source.directory);
1179        this.loaded = false;
1180        var _this = this;
1181        this._updateTimes = function() {
1182            _this.updateTimes();
1183            _this.trigger("change");
1184        };
1185        this.on("add", this._updateTimes);
1186        this.on("remove", this._updateTimes);
1187    };
1188
1189    extendPrototype(Mashup, Playable);
1190
1191    Mashup.prototype.updateTimes = function() {
1192        var _time = 0;
1193        this.segments.forEach(function(_segment) {
1194            _segment.setBegin(_time);
1195            _time = _segment.end;
1196        });
1197        this.duration.setMilliseconds(_time);
1198    };
1199
1200    Mashup.prototype.addAnnotation = function(_annotation, _defer) {
1201        var _mashedAnnotation = new MashedAnnotation(this, _annotation),
1202            _defer = _defer || false;
1203        this.segments.push(_mashedAnnotation);
1204        _annotation.on("change-begin", this._updateTimes);
1205        _annotation.on("change-end", this._updateTimes);
1206        if (!_defer) {
1207            this.trigger("add");
1208        }
1209    };
1210
1211    Mashup.prototype.addAnnotationById = function(_elId, _defer) {
1212        var _annotation = this.source.getElement(_elId),
1213            _defer = _defer || false;
1214        if (typeof _annotation !== "undefined") {
1215            this.addAnnotation(_annotation, _defer);
1216        }
1217    };
1218
1219    Mashup.prototype.addAnnotations = function(_segments) {
1220        var _this = this;
1221        ns._(_segments).forEach(function(_segment) {
1222            _this.addAnnotation(_segment, true);
1223        });
1224        this.trigger("add");
1225    };
1226
1227    Mashup.prototype.addAnnotationsById = function(_segments) {
1228        var _this = this;
1229        ns._(_segments).forEach(function(_segment) {
1230            _this.addAnnotationById(_segment, true);
1231        });
1232        this.trigger("add");
1233    };
1234
1235    Mashup.prototype.removeAnnotation = function(_annotation, _defer) {
1236        var _defer = _defer || false;
1237        _annotation.off("change-begin", this._updateTimes);
1238        _annotation.off("change-end", this._updateTimes);
1239        this.segments.removeId(this.id + "_" + _annotation.id);
1240        if (!_defer) {
1241            this.trigger("remove");
1242        }
1243    };
1244
1245    Mashup.prototype.removeAnnotationById = function(_annId, _defer) {
1246        var _defer = _defer || false;
1247        var _annotation = this.source.getElement(_annId);
1248
1249        if (_annotation) {
1250            this.removeAnnotation(_annotation, _defer);
1251        }
1252        if (!_defer) {
1253            this.trigger("remove");
1254        }
1255    };
1256
1257    Mashup.prototype.setAnnotations = function(_segments) {
1258        while (this.segments.length) {
1259            this.removeAnnotation(this.segments[0].annotation, true);
1260        }
1261        this.addAnnotations(_segments);
1262    };
1263
1264    Mashup.prototype.setAnnotationsById = function(_segments) {
1265        while (this.segments.length) {
1266            this.removeAnnotation(this.segments[0].annotation, true);
1267        }
1268        this.addAnnotationsById(_segments);
1269    };
1270
1271    Mashup.prototype.hasAnnotation = function(_annotation) {
1272        return !!ns._(this.segments).find(function(_s) {
1273            return _s.annotation === _annotation;
1274        });
1275    };
1276
1277    Mashup.prototype.getAnnotation = function(_annotation) {
1278        return ns._(this.segments).find(function(_s) {
1279            return _s.annotation === _annotation;
1280        });
1281    };
1282
1283    Mashup.prototype.getAnnotationById = function(_id) {
1284        return ns._(this.segments).find(function(_s) {
1285            return _s.annotation.id === _id;
1286        });
1287    };
1288
1289    Mashup.prototype.getAnnotations = function() {
1290        return this.segments;
1291    };
1292
1293    Mashup.prototype.getOriginalAnnotations = function() {
1294        var annotations = new List(this.source.directory);
1295        this.segments.forEach(function(_s) {
1296            annotations.push(_s.annotation);
1297        });
1298        return annotations;
1299    };
1300
1301    Mashup.prototype.getMedias = function() {
1302        var medias = new List(this.source.directory);
1303        this.segments.forEach(function(_annotation) {
1304            medias.push(_annotation.getMedia());
1305        });
1306        return medias;
1307    };
1308
1309    Mashup.prototype.getAnnotationsByTypeTitle = function(_title) {
1310        var _annTypes = this.source.getAnnotationTypes().searchByTitle(_title).pluck("id");
1311        if (_annTypes.length) {
1312            return this.getAnnotations().filter(function(_annotation) {
1313                return ns._(_annTypes).indexOf(_annotation.getAnnotationType().id) !== -1;
1314            });
1315        } else {
1316            return new List(this.source.directory);
1317        }
1318    };
1319
1320    Mashup.prototype.getAnnotationAtTime = function(_time) {
1321        var _list = this.segments.filter(function(_annotation) {
1322            return _annotation.begin <= _time && _annotation.end > _time;
1323        });
1324        if (_list.length) {
1325            return _list[0];
1326        } else {
1327            return undefined;
1328        }
1329    };
1330
1331    Mashup.prototype.getMediaAtTime = function(_time) {
1332        var _annotation = this.getAnnotationAtTime(_time);
1333        if (typeof _annotation !== "undefined") {
1334            return _annotation.getMedia();
1335        } else {
1336            return undefined;
1337        }
1338    };
1339
1340    /* */
1341
1342    var Source = Model.Source = function(_config) {
1343        BaseElement.call(this, false, this);
1344        this.status = _SOURCE_STATUS_EMPTY;
1345        this.elementType = "source";
1346        if (typeof _config !== "undefined") {
1347            var _this = this;
1348            ns._(_config).forEach(function(_v, _k) {
1349                _this[_k] = _v;
1350            });
1351            this.callbackQueue = [];
1352            this.contents = {};
1353            this.get();
1354        }
1355    };
1356
1357    extendPrototype(Source, BaseElement);
1358
1359    Source.prototype.addList = function(_listId, _contents) {
1360        if (typeof this.contents[_listId] === "undefined") {
1361            this.contents[_listId] = new List(this.directory);
1362        }
1363        this.contents[_listId].addElements(_contents);
1364    };
1365
1366    Source.prototype.getList = function(_listId, _global) {
1367        _global = (typeof _global !== "undefined" && _global);
1368        if (_global) {
1369            return this.directory.getGlobalList().filter(function(_e) {
1370                return (_e.elementType === _listId);
1371            });
1372        } else {
1373            if (typeof this.contents[_listId] === "undefined") {
1374                this.contents[_listId] = new List(this.directory);
1375            }
1376            return this.contents[_listId];
1377        }
1378    };
1379
1380    Source.prototype.forEach = function(_callback) {
1381        var _this = this;
1382        ns._(this.contents).forEach(function(_value, _key) {
1383            _callback.call(_this, _value, _key);
1384        });
1385    };
1386
1387    Source.prototype.getElement = function(_elId) {
1388        return this.directory.getElement(_elId);
1389    };
1390
1391    Source.prototype.get = function() {
1392        this.status = _SOURCE_STATUS_WAITING;
1393        this.handleCallbacks();
1394    };
1395
1396    /* We defer the callbacks calls so they execute after the queue is cleared */
1397    Source.prototype.deferCallback = function(_callback) {
1398        var _this = this;
1399        ns._.defer(function() {
1400            _callback.call(_this);
1401        });
1402    };
1403
1404    Source.prototype.handleCallbacks = function() {
1405        this.status = _SOURCE_STATUS_READY;
1406        while (this.callbackQueue.length) {
1407            this.deferCallback(this.callbackQueue.splice(0, 1)[0]);
1408        }
1409    };
1410    Source.prototype.onLoad = function(_callback) {
1411        if (this.status === _SOURCE_STATUS_READY) {
1412            this.deferCallback(_callback);
1413        } else {
1414            this.callbackQueue.push(_callback);
1415        }
1416    };
1417
1418    Source.prototype.serialize = function() {
1419        return this.serializer.serialize(this);
1420    };
1421
1422    Source.prototype.deSerialize = function(_data) {
1423        this.serializer.deSerialize(_data, this);
1424    };
1425
1426    Source.prototype.getAnnotations = function(_global) {
1427        _global = (typeof _global !== "undefined" && _global);
1428        return this.getList("annotation", _global);
1429    };
1430
1431    Source.prototype.getMedias = function(_global) {
1432        _global = (typeof _global !== "undefined" && _global);
1433        return this.getList("media", _global);
1434    };
1435
1436    Source.prototype.getTags = function(_global) {
1437        _global = (typeof _global !== "undefined" && _global);
1438        return this.getList("tag", _global);
1439    };
1440
1441    Source.prototype.getMashups = function(_global) {
1442        _global = (typeof _global !== "undefined" && _global);
1443        return this.getList("mashup", _global);
1444    };
1445
1446    Source.prototype.getAnnotationTypes = function(_global) {
1447        _global = (typeof _global !== "undefined" && _global);
1448        return this.getList("annotationType", _global);
1449    };
1450
1451    Source.prototype.getAnnotationsByTypeTitle = function(_title, _global) {
1452        _global = (typeof _global !== "undefined" && _global);
1453        var _res = new List(this.directory),
1454            _annTypes = this.getAnnotationTypes(_global).searchByTitle(_title);
1455        _annTypes.forEach(function(_annType) {
1456            _res.addElements(_annType.getAnnotations(_global));
1457        });
1458        return _res;
1459    };
1460
1461    Source.prototype.getDuration = function() {
1462        var _m = this.currentMedia;
1463        if (typeof _m !== "undefined") {
1464            return this.currentMedia.duration;
1465        }
1466    };
1467
1468    Source.prototype.getCurrentMedia = function(_opts) {
1469        if (typeof this.currentMedia === "undefined") {
1470            if (_opts.is_mashup) {
1471                var _mashups = this.getMashups();
1472                if (_mashups.length) {
1473                    this.currentMedia = _mashups[0];
1474                }
1475            } else {
1476                var _medias = this.getMedias();
1477                if (_medias.length) {
1478                    this.currentMedia = _medias[0];
1479                }
1480            }
1481        }
1482        return this.currentMedia;
1483    };
1484
1485    Source.prototype.merge = function(_source) {
1486        var _this = this;
1487        _source.forEach(function(_value, _key) {
1488            _this.getList(_key).addElements(_value);
1489        });
1490    };
1491
1492    /* */
1493
1494    var RemoteSource = Model.RemoteSource = function(_config) {
1495        Source.call(this, _config);
1496    };
1497
1498    extendPrototype(RemoteSource, Source);
1499
1500    RemoteSource.prototype.get = function() {
1501        this.status = _SOURCE_STATUS_WAITING;
1502        var _this = this,
1503            urlparams = this.url_params || {},
1504            dataType = (isLocalURL(this.url) ? "json" : "jsonp");
1505        urlparams.format = dataType;
1506        ns.jQuery.ajax({
1507            url: this.url,
1508            dataType: dataType,
1509            data: urlparams,
1510            traditional: true,
1511            success: function(_result) {
1512                _this.deSerialize(_result);
1513                _this.handleCallbacks();
1514            }
1515        });
1516    };
1517
1518    /* */
1519
1520    var Directory = Model.Directory = function() {
1521        this.remoteSources = {};
1522        this.elements = {};
1523    };
1524
1525    Directory.prototype.remoteSource = function(_properties) {
1526        if (typeof _properties !== "object" || typeof _properties.url === "undefined") {
1527            throw "Error : Directory.remoteSource(configuration): configuration.url is undefined";
1528        }
1529        var _config = ns._({ directory: this }).extend(_properties);
1530        _config.url_params = _config.url_params || {};
1531        var _hash = _config.url + "?" + ns.jQuery.param(_config.url_params);
1532        if (typeof this.remoteSources[_hash] === "undefined") {
1533            this.remoteSources[_hash] = new RemoteSource(_config);
1534        }
1535        return this.remoteSources[_hash];
1536    };
1537
1538    Directory.prototype.newLocalSource = function(_properties) {
1539        var _config = ns._({ directory: this }).extend(_properties),
1540            _res = new Source(_config);
1541        return _res;
1542    };
1543
1544    Directory.prototype.getElement = function(_id) {
1545        return this.elements[_id];
1546    };
1547
1548    Directory.prototype.addElement = function(_element) {
1549        this.elements[_element.id] = _element;
1550    };
1551
1552    Directory.prototype.getGlobalList = function() {
1553        var _res = new List(this);
1554        _res.addIds(ns._(this.elements).keys());
1555        return _res;
1556    };
1557    return Model;
1558
1559})(IriSP);
1560
1561/* END js */
1562/* HTML player, to be reused in a widget, or elsewhere */
1563
1564IriSP.htmlPlayer = function (media, jqselector, options) {
1565
1566    var opts = options || {},
1567        videoURL = opts.video || media.video;
1568
1569    if (typeof opts.url_transform === "function") {
1570        videoURL = opts.url_transform(videoURL);
1571    }
1572
1573    var videoEl = IriSP.jQuery('<video>');
1574
1575    videoEl.attr({
1576        width : opts.width || undefined,
1577        height : opts.height || undefined,
1578        controls : opts.controls || undefined,
1579        autoplay : opts.autostart || opts.autoplay || undefined,
1580        preload: "metadata"
1581    });
1582
1583    if (typeof videoURL === "string") {
1584        videoEl.attr("src", videoURL);
1585    } else {
1586        for (var i = 0; i < videoURL.length; i++) {
1587            var _srcNode = IriSP.jQuery('<source>');
1588            _srcNode.attr({
1589                src: videoURL[i].src,
1590                type: videoURL[i].type
1591            });
1592            videoEl.append(_srcNode);
1593        }
1594    }
1595    if (opts.subtitle) {
1596        var _trackNode = IriSP.jQuery('<track>');
1597        _trackNode.attr({
1598            label: "Subtitles",
1599            kind: "subtitles",
1600            srclang: "fr",
1601            src: opts.subtitle,
1602            default: ""
1603        });
1604        videoEl.append(_trackNode);
1605    }
1606    jqselector.prepend(videoEl);
1607
1608    var mediaEl = videoEl[0];
1609
1610    // Binding HTML video functions to media events
1611    media.on("setcurrenttime", function (_milliseconds) {
1612        try {
1613            mediaEl.currentTime = (_milliseconds / 1000);
1614        } catch (err) {
1615
1616        }
1617    });
1618
1619    media.on("setvolume", function (_vol) {
1620        media.volume = _vol;
1621        try {
1622            mediaEl.volume = _vol;
1623        } catch (err) {
1624
1625        }
1626    });
1627
1628    media.on("setmuted", function (_muted) {
1629        media.muted = _muted;
1630        try {
1631            mediaEl.muted = _muted;
1632        } catch (err) {
1633
1634        }
1635    });
1636
1637    media.on("settimerange", function (_timeRange) {
1638        media.timeRange = _timeRange;
1639        try {
1640            if (media.getCurrentTime() > _timeRange[0] || media.getCurrentTime() < _timeRange) {
1641                mediaEl.currentTime = (_timeRange[0] / 1000);
1642            }
1643        } catch (err) {
1644        }
1645    });
1646
1647    media.on("resettimerange", function () {
1648        media.timeRange = false;
1649    });
1650
1651    media.on("setplay", function () {
1652        try {
1653            mediaEl.play();
1654        } catch (err) {
1655
1656        }
1657    });
1658
1659    media.on("setpause", function () {
1660        try {
1661            mediaEl.pause();
1662        } catch (err) {
1663
1664        }
1665    });
1666
1667    // Binding DOM events to media
1668    function getVolume() {
1669        media.muted = mediaEl.muted;
1670        media.volume = mediaEl.volume;
1671    }
1672
1673    videoEl.on("loadedmetadata", function () {
1674        getVolume();
1675        media.trigger("loadedmetadata");
1676        media.trigger("volumechange");
1677    });
1678
1679    videoEl.on("timeupdate", function () {
1680        media.trigger("timeupdate", new IriSP.Model.Time(1000 * mediaEl.currentTime));
1681    });
1682
1683    videoEl.on("volumechange", function () {
1684        getVolume();
1685        media.trigger("volumechange");
1686    });
1687
1688    videoEl.on("play", function () {
1689        media.trigger("play");
1690    });
1691
1692    videoEl.on("pause", function () {
1693        media.trigger("pause");
1694    });
1695
1696    videoEl.on("seeking", function () {
1697        media.trigger("seeking");
1698    });
1699
1700    videoEl.on("seeked", function () {
1701        media.trigger("seeked");
1702    });
1703
1704    videoEl.on("click", function () {
1705        if (mediaEl.paused) {
1706            media.play();
1707        } else {
1708            media.pause();
1709        };
1710    });
1711};
1712/* START contentapi-serializer.js */
1713
1714if (typeof IriSP.serializers === "undefined") {
1715    IriSP.serializers = {};
1716}
1717
1718IriSP.serializers.content = {
1719    deSerialize : function(_data, _source) {
1720        var _medialist = new IriSP.Model.List(_source.directory);
1721        
1722        function deserializeObject(_m, i) {
1723            var _media = new IriSP.Model.Media(_m.iri_id, _source);
1724            _media.video = _m.media_url;
1725            _media.title = _m.title;
1726            _media.description = _m.description;
1727            _media.setDuration(_m.duration);
1728            _media.thumbnail = _m.image;
1729            _media.color = IriSP.vizcolors[i % IriSP.vizcolors.length];
1730            _media.keywords = _m.tags;
1731            _medialist.push(_media);
1732        }
1733        
1734        if (typeof _data.objects !== "undefined") {
1735            IriSP._(_data.objects).each(deserializeObject);
1736        } else {
1737            deserializeObject(_data, 0);
1738        }
1739        
1740        _source.addList("media", _medialist);
1741    }
1742};
1743
1744/* END contentapi-serializer.js */
1745/* Start ldt-serializer.js */
1746
1747if (typeof IriSP.serializers === "undefined") {
1748    IriSP.serializers = {};
1749}
1750
1751IriSP.serializers.ldt = {
1752    types :  {
1753        media : {
1754            serialized_name : "medias",
1755            deserializer : function(_data, _source) {
1756                var _res = new IriSP.Model.Media(_data.id, _source);
1757                _res.video = (
1758                    typeof _data.url !== "undefined"
1759                    ? _data.url
1760                    : (
1761                        typeof _data.href !== "undefined"
1762                        ? _data.href
1763                        : null
1764                    )
1765                );
1766                if (typeof _data.meta.item !== "undefined" && _data.meta.item.name === "streamer") {
1767                    _res.streamer = _data.meta.item.value;
1768                }
1769                _res.title = _data.meta["dc:title"];
1770                _res.description = _data.meta["dc:description"];
1771                _res.setDuration(_data.meta["dc:duration"]);
1772                _res.url = _data.meta.url;
1773                _res.meta = _data.meta;
1774                if (typeof _data.meta.img !== "undefined" && _data.meta.img.src !== "undefined") {
1775                    _res.thumbnail = _data.meta.img.src;
1776                }
1777                return _res;
1778            },
1779            serializer : function(_data, _source, _dest) {
1780                var _res = {
1781                    id : _data.id,
1782                    url : _data.video,
1783                    meta : {
1784                        "dc:title": _data.title || "",
1785                        "dc:description": _data.description || "",
1786                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
1787                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
1788                        "dc:creator" : _data.creator || _source.creator,
1789                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
1790                        "dc:duration" : _data.duration.milliseconds
1791                    }
1792                };
1793                IriSP._.defaults(_res.meta, _data.meta);
1794                _dest.medias.push(_res);
1795                var _list = {
1796                    id: IriSP.Model.getUID(),
1797                    meta : {
1798                        "dc:title": _data.title || "",
1799                        "dc:description": _data.description || "",
1800                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
1801                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
1802                        "dc:creator" : _data.creator || _source.creator,
1803                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
1804                        "id-ref": _data.id
1805                    },
1806                    items: _source.getAnnotationTypes().filter(function(_at) {
1807                        switch (typeof _at.media) {
1808                            case "object":
1809                                return (_at.media === _data);
1810                            case "string":
1811                                return (_at.media === _data.id);
1812                            default:
1813                                var _ann = _at.getAnnotations();
1814                                if (_ann) {
1815                                    for (var i = 0; i < _ann.length; i++) {
1816                                        if (_ann[i].getMedia() === _data) {
1817                                            return true;
1818                                        }
1819                                    }
1820                                }
1821                        }
1822                        return false;
1823                    }).map(function(_at) {
1824                        return {
1825                            "id-ref": _at.id
1826                        };
1827                    })
1828                };
1829                _dest.lists.push(_list);
1830                _dest.views[0].contents.push(_data.id);
1831            }
1832        },
1833        tag : {
1834            serialized_name : "tags",
1835            deserializer : function(_data, _source) {
1836                var _res = new IriSP.Model.Tag(_data.id, _source);
1837                _res.title = _data.meta["dc:title"];
1838                return _res;        
1839            },
1840            serializer : function(_data, _source, _dest) {
1841                if (_source.regenerateTags && !_data.regenerated) {
1842                    return;
1843                }
1844                var _res = {
1845                    id : _data.id,
1846                    meta : {
1847                        "dc:title": _data.title || "",
1848                        "dc:description": _data.description || "",
1849                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
1850                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
1851                        "dc:creator" : _data.creator || _source.creator,
1852                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator
1853                    }
1854                };
1855                _dest.tags.push(_res);
1856            }
1857        },
1858        annotationType : {
1859            serialized_name : "annotation-types",
1860            deserializer : function(_data, _source) {
1861                var _res = new IriSP.Model.AnnotationType(_data.id, _source);
1862                _res.title = _data["dc:title"];
1863                _res.description = _data["dc:description"];
1864                return _res;
1865            },
1866            serializer : function(_data, _source, _dest) {
1867                var _res = {
1868                    id : _data.id,
1869                    "dc:title": _data.title || "",
1870                    "dc:description": _data.description || "",
1871                    "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
1872                    "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
1873                    "dc:creator" : _data.creator || _source.creator,
1874                    "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator
1875                };
1876                _dest["annotation-types"].push(_res);
1877                _dest.views[0].annotation_types.push(_data.id);
1878            }
1879        },
1880        annotation : {
1881            serialized_name : "annotations",
1882            deserializer : function(_data, _source) {
1883                var _res = new IriSP.Model.Annotation(_data.id, _source);
1884                _res.title = _data.content.title || "";
1885                _res.description = _data.content.description || "";
1886                if (typeof _data.content.img !== "undefined" && _data.content.img.src !== "undefined") {
1887                    _res.thumbnail = _data.content.img.src;
1888                }
1889                _res.created = IriSP.Model.isoToDate(_data.created ? _data.created : _data.meta? _data.meta["dc:created"] : "");
1890                if (typeof _data.color !== "undefined") {
1891                    var _c = parseInt(_data.color).toString(16);
1892                    while (_c.length < 6) {
1893                        _c = '0' + _c;
1894                    }
1895                    _res.color = '#' + _c;
1896                }
1897                _res.content = _data.content;
1898                _res.setMedia(_data.media);
1899                _res.setAnnotationType(_data.meta["id-ref"]);
1900                _res.setTags(IriSP._(_data.tags).pluck("id-ref"));
1901                _res.keywords = _res.getTagTexts();
1902                _res.setBegin(_data.begin);
1903                _res.setEnd(_data.end);
1904                _res.creator = _data.meta["dc:creator"] || "";
1905                _res.project = _data.meta.project || "";
1906                if (typeof _data.meta["dc:source"] !== "undefined" && typeof _data.meta["dc:source"].content !== "undefined") {
1907                    _res.source = JSON.parse(_data.meta["dc:source"].content);
1908                }
1909                if (typeof _data.content.audio !== "undefined" && _data.content.audio.href) {
1910                    _res.audio = _data.content.audio;
1911                }
1912                _res.meta = _data.meta;
1913                return _res;
1914            },
1915            serializer : function(_data, _source, _dest) {
1916                var _color = parseInt(_data.color.replace(/^#/,''),16).toString();
1917                var _res = {
1918                    id : _data.id,
1919                    begin : _data.begin.milliseconds,
1920                    end : _data.end.milliseconds,
1921                    content : IriSP._.defaults(
1922                        {},
1923                        {
1924                            title : _data.title,
1925                            description : _data.description,
1926                        audio : _data.audio,
1927                        img: {
1928                            src: _data.thumbnail
1929                        }
1930                    },
1931                        _data.content,
1932                        {
1933                            title: "",
1934                            description: ""
1935                        }
1936                    ),
1937                    color: _color,
1938                    media : _data.media.id,
1939                    meta : {
1940                        "id-ref" : _data.getAnnotationType().id,
1941                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
1942                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
1943                        "dc:creator" : _data.creator || _source.creator,
1944                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator
1945//                        project : _source.projectId
1946                    }
1947                };
1948                if (_source.regenerateTags) {
1949                    _res.tags = IriSP._(_data.keywords).map(function(_kw) {
1950                        return {
1951                            "id-ref": _source.__keywords[_kw.toLowerCase()].id
1952                        };
1953                    });
1954                } else {
1955                    _res.tags = IriSP._(_data.tag.id).map(function(_id) {
1956                        return {
1957                           "id-ref" : _id
1958                       };
1959                    });
1960                }
1961                _res.content.title = _data.title || _res.content.title || "";
1962                IriSP._.defaults(_res.meta, _data.meta);
1963                _dest.annotations.push(_res);
1964            }
1965        },
1966        mashup : {
1967            serialized_name : "lists",
1968            deserializer : function(_data, _source) {
1969                if (typeof _data.meta !== "object" || typeof _data.meta.listtype !== "string" || _data.meta.listtype !== "mashup") {
1970                    return undefined;
1971                }
1972                var _res = new IriSP.Model.Mashup(_data.id, _source);
1973                _res.title = _data.meta["dc:title"];
1974                _res.description = _data.meta["dc:description"];
1975                _res.creator = _data.meta["dc:creator"];
1976                _res.setAnnotationsById(_data.items);
1977                return _res;        
1978            },
1979            serializer : function(_data, _source, _dest) {
1980                var _res = {
1981                    meta : {
1982                        "dc:title": _data.title || "",
1983                        "dc:description": _data.description || "",
1984                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
1985                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
1986                        "dc:creator" : _data.creator || _source.creator,
1987                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
1988                        listtype: "mashup"
1989                    },
1990                    items: _data.segments.map(function(_annotation) {
1991                        return _annotation.annotation.id;
1992                    }),
1993                    id: _data.id
1994                };
1995                _dest.lists.push(_res);
1996            }
1997        }
1998    },
1999    serialize : function(_source) {
2000        var _res = {
2001                meta: {
2002                    "dc:creator": _source.creator,
2003                    "dc:contributor" : _source.contributor || _source.creator,
2004                    "dc:created": IriSP.Model.dateToIso(_source.created),
2005                    "dc:modified" : IriSP.Model.dateToIso(_source.modified),
2006                    "dc:title": _source.title || "",
2007                    "dc:description": _source.description || "",
2008                    id: _source.projectId || _source.id
2009                },
2010                views: [
2011                    {
2012                        id: IriSP.Model.getUID(),
2013                        contents: [],
2014                        annotation_types: []
2015                    }
2016                ],
2017                lists: [],
2018                "annotation-types": [],
2019                medias: [],
2020                tags: [],
2021                annotations: []
2022            },
2023            _this = this;
2024        if (_source.regenerateTags) {
2025            _source.__keywords = {};
2026            _source.getAnnotations().forEach(function(a) {
2027                IriSP._(a.keywords).each(function(kw) {
2028                    var lkw = kw.toLowerCase();
2029                    if (typeof _source.__keywords[lkw] === "undefined") {
2030                        _source.__keywords[lkw] = {
2031                            id: IriSP.Model.getUID(),
2032                            title: kw,
2033                            regenerated: true
2034                        };
2035                    }
2036                });
2037            });
2038            IriSP._(_source.__keywords).each(function(kw) {
2039                _this.types.tag.serializer(kw, _source, _res);
2040            });
2041        }
2042        _source.forEach(function(_list, _typename) {
2043            if (typeof _this.types[_typename] !== "undefined") {
2044                _list.forEach(function(_el) {
2045                    _this.types[_typename].serializer(_el, _source, _res);
2046                });
2047            }
2048        });
2049        return JSON.stringify(_res);
2050    },
2051    deSerialize : function(_data, _source) {
2052        if (typeof _data !== "object" || _data === null) {
2053            return;
2054        }
2055        IriSP._(this.types).forEach(function(_type, _typename) {
2056            var _listdata = _data[_type.serialized_name],
2057                _list = new IriSP.Model.List(_source.directory);
2058            if (typeof _listdata !== "undefined" && _listdata !== null) {
2059                if (_listdata.hasOwnProperty("length")) {
2060                    var _l = _listdata.length;
2061                    for (var _i = 0; _i < _l; _i++) {
2062                        var _element = _type.deserializer(_listdata[_i], _source);
2063                        if (typeof _element !== "undefined" && _element) {
2064                            _list.push(_element);
2065                        }
2066                    }
2067                } else {
2068                    var _element = _type.deserializer(_listdata, _source);
2069                    if (typeof _element !== "undefined" && _element) {
2070                        _list.push(_element);
2071                    }
2072                }
2073            }
2074            _source.addList(_typename, _list);
2075        });
2076        
2077        if (typeof _data.meta !== "undefined") {
2078            _source.projectId = _data.meta.id;
2079            _source.title = _data.meta["dc:title"] || _data.meta.title || "";
2080            _source.description = _data.meta["dc:description"] || _data.meta.description || "";
2081            _source.creator = _data.meta["dc:creator"] || _data.meta.creator || "";
2082            _source.contributor = _data.meta["dc:contributor"] || _data.meta.contributor || _source.creator;
2083            _source.created = IriSP.Model.isoToDate(_data.meta["dc:created"] || _data.meta.created);
2084        }
2085        
2086        if (typeof _data.meta !== "undefined" && typeof _data.meta.main_media !== "undefined" && typeof _data.meta.main_media["id-ref"] !== "undefined") {
2087            _source.currentMedia = _source.getElement(_data.meta.main_media["id-ref"]);
2088        }
2089    }
2090};
2091
2092/* End of LDT Platform Serializer */
2093/* ldt_annotate serializer: Used when Putting annotations on the platform */
2094
2095if (typeof IriSP.serializers === "undefined") {
2096    IriSP.serializers = {};
2097}
2098
2099IriSP.serializers.ldt_annotate = {
2100    serializeAnnotation : function(_data, _source) {
2101        var _annType = _data.getAnnotationType();
2102        return {
2103            id: _data.id,
2104            begin: _data.begin.milliseconds,
2105            end: _data.end.milliseconds,
2106            content: {
2107                data: (_data.content ? _data.content.data || {} : {}),
2108                description: _data.description,
2109                title: _data.title,
2110                audio: _data.audio
2111            },
2112            id: _data.id ? _data.id : "", // If annotation is new, id will be undefined
2113            tags: _data.getTagTexts(),
2114            media: _data.getMedia().id,
2115            project: _data.project_id,
2116            type_title: _annType.title,
2117            sharing: _data.sharing || "private",
2118            type: (typeof _annType.dont_send_id !== "undefined" && _annType.dont_send_id ? "" : _annType.id),
2119            meta: {
2120                created: _data.created,
2121                creator: _data.creator,
2122                modified: _data.modified,
2123                contributor: _data.contributor
2124            }
2125        };
2126    },
2127    deserializeAnnotation : function(_anndata, _source) {
2128        var _ann = new IriSP.Model.Annotation(_anndata.id, _source);
2129        _ann.description = _anndata.content.description || "";
2130        _ann.title = _anndata.content.title || "";
2131        _ann.creator = _anndata.meta.creator || "";
2132        _ann.created = new Date(_anndata.meta.created);
2133        _ann.setMedia(_anndata.media, _source);
2134        var _anntype = _source.getElement(_anndata.type);
2135        if (!_anntype) {
2136            _anntype = new IriSP.Model.AnnotationType(_anndata.type, _source);
2137            _anntype.title = _anndata.type_title;
2138            _source.getAnnotationTypes().push(_anntype);
2139        }
2140        _ann.meta = _anndata.meta;
2141        _ann.setAnnotationType(_anntype.id);
2142        var _tagIds = IriSP._(_anndata.tags).map(function(_title) {
2143            var _tags = _source.getTags(true).searchByTitle(_title, true);
2144            if (_tags.length) {
2145                var _tag = _tags[0];
2146            } else {
2147                _tag = new IriSP.Model.Tag(_title.replace(/\W/g, '_'), _source);
2148                _tag.title = _title;
2149                _source.getTags().push(_tag);
2150            }
2151            return _tag.id;
2152        });
2153        _ann.setTags(_tagIds);
2154        _ann.setBeginEnd(_anndata.begin, _anndata.end);
2155        if (typeof _anndata.content.audio !== "undefined" && _anndata.content.audio.href) {
2156            _ann.audio = _anndata.content.audio;
2157        };
2158        if (_anndata.content.data) {
2159            _ann.content = { data: _anndata.content.data };
2160        };
2161        _source.getAnnotations().push(_ann);
2162    },
2163    serialize : function(_source) {
2164        return JSON.stringify(this.serializeAnnotation(_source.getAnnotations()[0], _source));
2165    },
2166    deSerialize : function(_data, _source) {
2167        if (typeof _data == "string") {
2168            _data = JSON.parse(_data);
2169        }
2170
2171        _source.addList('tag', new IriSP.Model.List(_source.directory));
2172        _source.addList('annotationType', new IriSP.Model.List(_source.directory));
2173        _source.addList('annotation', new IriSP.Model.List(_source.directory));
2174        this.deserializeAnnotation(_data, _source);
2175    }
2176};
2177
2178/* End ldt_annotate serializer */
2179/* ldt_localstorage serializer: Used to store personal annotations in local storage */
2180
2181if (typeof IriSP.serializers === "undefined") {
2182    IriSP.serializers = {};
2183}
2184
2185IriSP.serializers.ldt_localstorage = {
2186    serializeAnnotation : function(_data, _source) {
2187        var _annType = _data.getAnnotationType();
2188        return {
2189            id: _data.id,
2190            begin: _data.begin.milliseconds,
2191            end: _data.end.milliseconds,
2192            content: {
2193                data: (_data.content ? _data.content.data || {} : {}),
2194                description: _data.description,
2195                title: _data.title,
2196                audio: _data.audio
2197            },
2198            tags: _data.getTagTexts(),
2199            media: _data.getMedia().id,
2200            type_title: _annType.title,
2201            type: ( typeof _annType.dont_send_id !== "undefined" && _annType.dont_send_id ? "" : _annType.id ),
2202            meta: {
2203                created: _data.created,
2204                creator: _data.creator,
2205                modified: _data.modified,
2206                contributor: _data.contributor
2207            }
2208        };
2209    },
2210    deserializeAnnotation : function(_anndata, _source) {
2211        var _ann = new IriSP.Model.Annotation(_anndata.id, _source);
2212        _ann.description = _anndata.content.description || "";
2213        _ann.title = _anndata.content.title || "";
2214        _ann.creator = _anndata.meta.creator || "";
2215        _ann.created = new Date(_anndata.meta.created);
2216        _ann.contributor = _anndata.meta.contributor || "";
2217        _ann.modified = new Date(_anndata.meta.modified);
2218        _ann.setMedia(_anndata.media, _source);
2219        var _anntype = _source.getElement(_anndata.type);
2220        if (!_anntype) {
2221            _anntype = new IriSP.Model.AnnotationType(_anndata.type, _source);
2222            _anntype.title = _anndata.type_title;
2223            _source.getAnnotationTypes().push(_anntype);
2224        }
2225        _ann.setAnnotationType(_anntype.id);
2226        var _tagIds = IriSP._(_anndata.tags).map(function(_title) {
2227            var _tags = _source.getTags(true).searchByTitle(_title, true);
2228            if (_tags.length) {
2229                var _tag = _tags[0];
2230            }
2231            else {
2232                _tag = new IriSP.Model.Tag(_title.replace(/\W/g,'_'),_source);
2233                _tag.title = _title;
2234                _source.getTags().push(_tag);
2235            }
2236            return _tag.id;
2237        });
2238        _ann.setTags(_tagIds);
2239        _ann.setBeginEnd(_anndata.begin, _anndata.end);
2240        if (typeof _anndata.content.audio !== "undefined" && _anndata.content.audio.href) {
2241            _ann.audio = _anndata.content.audio;
2242        };
2243        if (_anndata.content.data) {
2244            _ann.content = { data: _anndata.content.data };
2245        };
2246        _source.getAnnotations().push(_ann);
2247    },
2248    serialize : function(_source) {
2249        var _this = this;
2250        return JSON.stringify(_source.getAnnotations().map(function (a) { return _this.serializeAnnotation(a, _source); }));
2251    },
2252    deSerialize : function(_data, _source) {
2253        var _this = this;
2254        if (typeof _data == "string") {
2255            _data = JSON.parse(_data);
2256        }
2257
2258        _source.addList('tag', new IriSP.Model.List(_source.directory));
2259        _source.addList('annotationType', new IriSP.Model.List(_source.directory));
2260        _source.addList('annotation', new IriSP.Model.List(_source.directory));
2261        _data.map( function (a) { _this.deserializeAnnotation(a, _source); });
2262    }
2263};
2264
2265/* End ldt_localstorage serializer */
2266/* START segmentapi-serializer.js */
2267
2268if (typeof IriSP.serializers === "undefined") {
2269    IriSP.serializers = {};
2270}
2271
2272IriSP.serializers.segmentapi = {
2273    deSerialize : function(_data, _source) {
2274        var _annotationlist = new IriSP.Model.List(_source.directory),
2275            _medialist = new IriSP.Model.List(_source.directory);
2276        _source.addList("media", _medialist);
2277        
2278        function deserializeObject(_s) {
2279            var _ann = new IriSP.Model.Annotation(_s.element_id, _source),
2280                _media = _source.getElement(_s.iri_id);
2281            if (!_media) {
2282                _media = new IriSP.Model.Media(_s.iri_id, _source);
2283                _source.getMedias().push(_media);
2284            }
2285            _ann.setMedia(_s.iri_id);
2286            _ann.title = _s.title;
2287            _ann.description = _s['abstract'];
2288            _ann.begin = new IriSP.Model.Time(_s.start_ts);
2289            _ann.end = new IriSP.Model.Time(_s.start_ts + _s.duration);
2290            _ann.keywords = (_s.tags ? _s.tags.split(",") : []);
2291            _ann.project_id = _s.project_id;
2292            _annotationlist.push(_ann);
2293        }
2294        
2295        if (typeof _data.objects !== "undefined") {
2296            IriSP._(_data.objects).each(deserializeObject);
2297        } else {
2298            deserializeObject(_data);
2299        }
2300        _source.addList("annotation", _annotationlist);
2301    }
2302};
2303
2304/* END segmentapi-serializer.js */
2305/* Start of defaults.js */
2306
2307IriSP.language = 'en';
2308
2309IriSP.libFiles = {
2310    defaultDir : "js/libs/",
2311    inDefaultDir : {
2312        underscore : "underscore-min.js",
2313        Mustache : "mustache.js",
2314        jQuery : "jquery.min.js",
2315        jQueryUI : "jquery-ui.min.js",
2316        swfObject : "swfobject.js",
2317        cssjQueryUI : "jquery-ui.css",
2318        popcorn : "popcorn-complete.min.js",
2319        jwplayer : "jwplayer.js",
2320        raphael : "raphael-min.js",
2321        tracemanager : "tracemanager.js",
2322        jwPlayerSWF : "player.swf",
2323        json : "json2.js",
2324        zeroClipboardJs: "ZeroClipboard.js",
2325        zeroClipboardSwf: "ZeroClipboard.swf",
2326        backbone: "backbone.js",
2327        backboneRelational: "backbone-relational.js",
2328        paper: "paper.js",
2329        jqueryMousewheel: "jquery.mousewheel.min.js",
2330        splitter: "jquery.touchsplitter.js",
2331        cssSplitter: "jquery.touchsplitter.css",
2332        renkanPublish: "renkan.js",
2333        processing: "processing-1.3.6.min.js",
2334        recordMicSwf: "record_mic.swf",
2335        mousetrap: "mousetrap.min.js",
2336        mousetrapGlobal: "mousetrap-global-bind.js",
2337        markjs: "jquery.mark.min.js"
2338    },
2339    locations : {
2340        // use to define locations outside default_dir
2341    },
2342    cdn : {
2343        jQuery : "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.min.js",
2344        jQueryUI : "http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.22/jquery-ui.min.js",
2345        swfObject : "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js",
2346        cssjQueryUI : "http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.22/themes/ui-lightness/jquery-ui.css",
2347        underscore : "http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js",
2348        Mustache : "http://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.5.0-dev/mustache.min.js",
2349        raphael : "http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js",
2350        json : "http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js",
2351        popcorn: "http://cdn.popcornjs.org/code/dist/popcorn-complete.min.js"
2352    },
2353    useCdn : false
2354};
2355
2356IriSP.widgetsDir = 'widgets';
2357
2358IriSP.widgetsRequirements = {
2359    PopcornPlayer: {
2360        noCss: true,
2361        requires: [ "popcorn" ]
2362    },
2363    HtmlPlayer: {
2364        noCss: true
2365    },
2366    JwpPlayer: {
2367        noCss: true,
2368        requires: [ "jwplayer" ]
2369    },
2370    DailymotionPlayer: {
2371        noCss: true,
2372        requires: [ "swfObject" ]
2373    },
2374    AdaptivePlayer: {
2375        noCss: true
2376    },
2377    AutoPlayer: {
2378        noCss: true
2379    },
2380    AnnotationsList: {
2381        requires: [ "jwplayer" ]
2382    },
2383    Sparkline: {
2384        noCss: true,
2385        requires: [ "raphael" ]
2386    },
2387    Arrow: {
2388        noCss: true,
2389        requires: [ "raphael" ]
2390    },
2391    Mediafragment: {
2392        noCss: true
2393    },
2394    Trace : {
2395        noCss: true,
2396        requires: [ "tracemanager" ]
2397    },
2398    Slideshare: {
2399        requires: [ "swfObject" ]
2400    },
2401    Social: {
2402        requires: [ "zeroClipboardJs" ]
2403    },
2404    Renkan: {
2405        requires: [ "backbone", "backboneRelational", "paper", "jqueryMousewheel", "renkanPublish" ]
2406    },
2407    KnowledgeConcierge: {
2408        requires: [ "processing" ]
2409    },
2410    MultiSegments: {
2411        noCss: true
2412    },
2413    SlideVideoPlayer: {
2414        requires: [ "jQuery", "jQueryUI", "splitter" ]
2415    },
2416    Shortcuts: {
2417        requires: [ "mousetrap", "mousetrapGlobal" ]
2418    },
2419    EnrichedPlan: {
2420        requires: [ "markjs" ]
2421    }
2422};
2423
2424IriSP.guiDefaults = {
2425    width : "100%",
2426    container : 'LdtPlayer',
2427    spacer_div_height : 0,
2428    widgets: []
2429};
2430
2431/* End of defaults.js */
2432/* widgets-container/metadataplayer.js - initialization and configuration of the widgets
2433*/
2434
2435/* The Metadataplayer Object, single point of entry, replaces IriSP.init_player */
2436
2437(function(ns) {
2438
2439var formerJQuery, formerUnderscore, former$;
2440
2441var Metadataplayer = ns.Metadataplayer = function(config) {
2442    ns.log("IriSP.Metadataplayer constructor");
2443    for (var key in ns.guiDefaults) {
2444        if (ns.guiDefaults.hasOwnProperty(key) && !config.hasOwnProperty(key)) {
2445            config[key] = ns.guiDefaults[key]
2446        }
2447    }
2448    var _container = document.getElementById(config.container);
2449    _container.innerHTML = '<h3 class="Ldt-Loader">Loading... Chargement...</h3>';
2450    this.sourceManager = new ns.Model.Directory();
2451    this.config = config;
2452    this.__events = {};
2453    this.loadLibs();
2454};
2455
2456Metadataplayer.prototype.toString = function() {
2457    return 'Metadataplayer in #' + this.config.container;
2458};
2459
2460Metadataplayer.prototype.on = function(_event, _callback) {
2461    if (typeof this.__events[_event] === "undefined") {
2462        this.__events[_event] = [];
2463    }
2464    this.__events[_event].push(_callback);
2465};
2466
2467Metadataplayer.prototype.trigger = function(_event, _data) {
2468    var _element = this;
2469    ns._(this.__events[_event]).each(function(_callback) {
2470        _callback.call(_element, _data);
2471    });
2472};
2473
2474Metadataplayer.prototype.loadLibs = function() {
2475    ns.log("IriSP.Metadataplayer.prototype.loadLibs");
2476    var $L = $LAB
2477        .queueScript(ns.getLib("Mustache"));
2478    formerJQuery = !!window.jQuery;
2479    former$ = !!window.$;
2480    formerUnderscore = !!window._;
2481
2482    if (typeof ns.jQuery === "undefined") {
2483        $L.queueScript(ns.getLib("jQuery"));
2484    }
2485
2486    if (typeof ns._ === "undefined") {
2487        $L.queueScript(ns.getLib("underscore"));
2488    }
2489
2490    if (typeof window.JSON == "undefined") {
2491        $L.queueScript(ns.getLib("json"));
2492    }
2493    $L.queueWait().queueScript(ns.getLib("jQueryUI")).queueWait();
2494
2495    /* widget specific requirements */
2496    for(var _i = 0; _i < this.config.widgets.length; _i++) {
2497        var _t = this.config.widgets[_i].type;
2498        if (typeof ns.widgetsRequirements[_t] !== "undefined" && typeof ns.widgetsRequirements[_t].requires !== "undefined" ) {
2499            for (var _j = 0; _j < ns.widgetsRequirements[_t].requires.length; _j++) {
2500                $L.queueScript(ns.getLib(ns.widgetsRequirements[_t].requires[_j]));
2501            }
2502        }
2503    }
2504
2505    var _this = this;
2506    $L.queueWait(function() {
2507        _this.onLibsLoaded();
2508    });
2509    
2510    $L.runQueue();
2511};
2512
2513Metadataplayer.prototype.onLibsLoaded = function() {
2514    ns.log("IriSP.Metadataplayer.prototype.onLibsLoaded");
2515
2516    if (typeof ns.jQuery === "undefined" && typeof window.jQuery !== "undefined") {
2517        ns.jQuery = window.jQuery;
2518        if (former$ || formerJQuery) {
2519            window.jQuery.noConflict(formerJQuery);
2520        }
2521    }
2522    if (typeof ns._ === "undefined" && typeof window._ !== "undefined") {
2523        ns._ = window._;
2524        if (formerUnderscore) {
2525            _.noConflict();
2526        }
2527    }
2528    
2529    ns.loadCss(ns.getLib("cssjQueryUI"));
2530    ns.loadCss(this.config.css);
2531
2532    this.$ = ns.jQuery('#' + this.config.container);
2533    this.$.css({
2534        "width": this.config.width,
2535        "clear": "both"
2536    });
2537    if (typeof this.config.height !== "undefined") {
2538        this.$.css("height", this.config.height);
2539    }
2540
2541    this.widgets = [];
2542    var _this = this;
2543    ns._(this.config.widgets).each(function(widgetconf, key) {
2544        _this.widgets.push(null);
2545        _this.loadWidget(widgetconf, function(widget) {
2546            _this.widgets[key] = widget;
2547            if (widget.isLoaded()) {
2548                _this.trigger("widget-loaded");
2549            }
2550        });
2551    });
2552    this.$.find('.Ldt-Loader').detach();
2553
2554    this.widgetsLoaded = false;
2555
2556    this.on("widget-loaded", function() {
2557        if (_this.widgetsLoaded) {
2558            return;
2559        }
2560        var isloaded = !ns._(_this.widgets).any(function(w) {
2561            return !(w && w.isLoaded());
2562        });
2563        if (isloaded) {
2564            _this.widgetsLoaded = true;
2565            _this.trigger("widgets-loaded");
2566        }
2567    });
2568};
2569
2570Metadataplayer.prototype.loadLocalAnnotations = function(localsourceidentifier) {
2571    if (this.localSource === undefined)
2572        this.localSource = this.sourceManager.newLocalSource({serializer: IriSP.serializers['ldt_localstorage']});
2573    // Load current local annotations
2574    if (localsourceidentifier) {
2575        // Allow to override localsourceidentifier when necessary (usually at init time)
2576        this.localSource.identifier = localsourceidentifier;
2577    }
2578    this.localSource.deSerialize(window.localStorage[this.localSource.identifier] || "[]");
2579    return this.localSource;
2580};
2581
2582Metadataplayer.prototype.saveLocalAnnotations = function() {
2583    // Save annotations back to localstorage
2584    window.localStorage[this.localSource.identifier] = this.localSource.serialize();
2585    // Merge modifications into widget source
2586    // this.source.merge(this.localSource);
2587};
2588
2589Metadataplayer.prototype.addLocalAnnotation = function(a) {
2590    this.loadLocalAnnotations();
2591    this.localSource.getAnnotations().push(a);
2592    this.saveLocalAnnotations();
2593};
2594
2595Metadataplayer.prototype.deleteLocalAnnotation = function(ident) {
2596    this.localSource.getAnnotations().removeId(ident, true);
2597    this.saveLocalAnnotations();
2598};
2599
2600Metadataplayer.prototype.getLocalAnnotation = function (ident) {
2601    this.loadLocalAnnotations();
2602    // We cannot use .getElement since it fetches
2603    // elements from the global Directory
2604    return IriSP._.first(IriSP._.filter(this.localSource.getAnnotations(), function (a) { return a.id == ident; }));
2605};
2606
2607Metadataplayer.prototype.loadMetadata = function(_metadataInfo) {
2608    if (_metadataInfo.elementType === "source") {
2609        return _metadataInfo;
2610    }
2611    if (typeof _metadataInfo.serializer === "undefined" && typeof _metadataInfo.format !== "undefined") {
2612        _metadataInfo.serializer = ns.serializers[_metadataInfo.format];
2613    }
2614    if (typeof _metadataInfo.url !== "undefined" && typeof _metadataInfo.serializer !== "undefined") {
2615        return this.sourceManager.remoteSource(_metadataInfo);
2616    } else {
2617        return this.sourceManager.newLocalSource(_metadataInfo);
2618    }
2619};
2620
2621Metadataplayer.prototype.loadWidget = function(_widgetConfig, _callback) {
2622    /* Creating containers if needed */
2623    if (typeof _widgetConfig.container === "undefined") {
2624        var _divs = this.layoutDivs(_widgetConfig.type);
2625        _widgetConfig.container = _divs[0];
2626    }
2627
2628    var _this = this;
2629
2630    if (typeof ns.Widgets[_widgetConfig.type] !== "undefined") {
2631        ns._.defer(function() {
2632            _callback(new ns.Widgets[_widgetConfig.type](_this, _widgetConfig));
2633        });
2634    } else {
2635        /* Loading Widget CSS */
2636        if (typeof ns.widgetsRequirements[_widgetConfig.type] === "undefined" || typeof ns.widgetsRequirements[_widgetConfig.type].noCss === "undefined" || !ns.widgetsRequirements[_widgetConfig.type].noCss) {
2637            ns.loadCss(ns.widgetsDir + '/' + _widgetConfig.type + '.css');
2638        }
2639        /* Loading Widget JS    */
2640        $LAB.script(ns.widgetsDir + '/' + _widgetConfig.type + '.js').wait(function() {
2641            _callback(new ns.Widgets[_widgetConfig.type](_this, _widgetConfig));
2642        });
2643    }
2644};
2645
2646/** create a subdiv with an unique id, and a spacer div as well.
2647    @param widgetName the name of the widget.
2648    @return an array of the form [createdivId, spacerdivId].
2649*/
2650Metadataplayer.prototype.layoutDivs = function(_name, _height) {
2651    if (typeof(_name) === "undefined") {
2652       _name = "";
2653    }
2654    var newDiv = ns._.uniqueId(this.config.container + "_widget_" + _name + "_"),
2655        spacerDiv = ns._.uniqueId("LdtPlayer_spacer_"),
2656        divHtml = ns.jQuery('<div>')
2657            .attr("id",newDiv)
2658            .css({
2659                width: this.config.width + "px",
2660                position: "relative",
2661                clear: "both"
2662            }),
2663        spacerHtml = ns.jQuery('<div>')
2664            .attr("id",spacerDiv)
2665            .css({
2666                width: this.config.width + "px",
2667                height: this.config.spacer_div_height + "px",
2668                position: "relative",
2669                clear: "both"
2670            });
2671    if (typeof _height !== "undefined") {
2672        divHtml.css("height", _height);
2673    }
2674
2675    this.$.append(divHtml);
2676    this.$.append(spacerHtml);
2677
2678    return [newDiv, spacerDiv];
2679};
2680
2681})(IriSP);
2682
2683/* End of widgets-container/metadataplayer.js */
2684/* widgetsDefinition of an ancestor for the Widget classes */
2685
2686if (typeof IriSP.Widgets === "undefined") {
2687    IriSP.Widgets = {};
2688}
2689
2690/**
2691 * @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
2692 *
2693 *  Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
2694 *  defined in init.js
2695 *
2696 * @constructor
2697 * @param player - a reference to the player widget
2698 * @param config - configuration options for the widget
2699 */
2700
2701
2702IriSP.Widgets.Widget = function (player, config) {
2703
2704    if (typeof player === "undefined") {
2705        /* Probably an abstract call of the class when
2706         * individual widgets set their prototype */
2707        return;
2708    }
2709
2710    this.__subwidgets = [];
2711
2712    /* Setting all the configuration options */
2713    var _type = config.type || "(unknown)",
2714        _config = IriSP._.defaults({}, config, (player && player.config ? player.config.default_options : {}), this.defaults),
2715        _this = this;
2716
2717    IriSP._(_config).forEach(function (_value, _key) {
2718        _this[_key] = _value;
2719    });
2720
2721    this.$ = IriSP.jQuery('#' + this.container);
2722
2723    if (typeof this.width === "undefined") {
2724        this.width = this.$.width();
2725    } else {
2726        this.$.css("width", this.width);
2727    }
2728
2729    if (typeof this.height !== "undefined") {
2730        this.$.css("height", this.height);
2731    }
2732
2733    /* Setting this.player at the end in case it's been overriden
2734     * by a configuration option of the same name :-(
2735     */
2736    this.player = player || new IriSP.FakeClass(["on","trigger","off","loadWidget","loadMetadata"]);
2737
2738    /* Adding classes and html attributes */
2739    this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type);
2740
2741    this.l10n = (
2742        typeof this.messages[IriSP.language] !== "undefined"
2743        ? this.messages[IriSP.language]
2744        : (
2745            IriSP.language.length > 2 && typeof this.messages[IriSP.language.substr(0, 2)] !== "undefined"
2746            ? this.messages[IriSP.language.substr(0, 2)]
2747            : this.messages["en"]
2748        )
2749    );
2750
2751    /* Loading Metadata if required */
2752
2753    function onsourceloaded() {
2754        if (_this.localannotations) {
2755            _this.localsource = player.loadLocalAnnotations(_this.localannotations);
2756            _this.source.merge(_this.localsource);
2757        }
2758        if (_this.media_id) {
2759            _this.media = this.getElement(_this.media_id);
2760        } else {
2761            var _mediaopts = {
2762                is_mashup: _this.is_mashup || false
2763            };
2764            _this.media = _this.source.getCurrentMedia(_mediaopts);
2765        }
2766        if (_this.pre_draw_callback) {
2767            IriSP.jQuery.when(_this.pre_draw_callback()).done(_this.draw());
2768        } else {
2769            _this.draw();
2770        }
2771        _this.player.trigger("widget-loaded");
2772    }
2773
2774    if (this.metadata) {
2775        /* Getting metadata */
2776        this.source = player.loadMetadata(this.metadata);
2777        /* Call draw when loaded */
2778        this.source.onLoad(onsourceloaded);
2779    } else {
2780        if (this.source) {
2781            onsourceloaded();
2782        }
2783    }
2784
2785
2786};
2787
2788IriSP.Widgets.Widget.prototype.defaults = {};
2789
2790IriSP.Widgets.Widget.prototype.template = '';
2791
2792IriSP.Widgets.Widget.prototype.messages = {"en":{}};
2793
2794IriSP.Widgets.Widget.prototype.toString = function () {
2795    return "Widget " + this.type;
2796};
2797
2798IriSP.Widgets.Widget.prototype.templateToHtml = function (_template) {
2799    return Mustache.to_html(_template, this);
2800};
2801
2802IriSP.Widgets.Widget.prototype.renderTemplate = function () {
2803    this.$.append(this.templateToHtml(this.template));
2804};
2805
2806IriSP.Widgets.Widget.prototype.functionWrapper = function (_name) {
2807    var _this = this,
2808        _function = this[_name];
2809    if (typeof _function !== "undefined") {
2810        return function () {
2811            return _function.apply(_this, Array.prototype.slice.call(arguments, 0));
2812        };
2813    } else {
2814        console.log("Error, Unknown function IriSP.Widgets." + this.type + "." + _name);
2815    }
2816};
2817
2818IriSP.Widgets.Widget.prototype.getFunctionOrName = function (_functionOrName) {
2819    switch (typeof _functionOrName) {
2820        case "function":
2821            return _functionOrName;
2822        case "string":
2823            return this.functionWrapper(_functionOrName);
2824        default:
2825            return undefined;
2826    }
2827};
2828
2829IriSP.Widgets.Widget.prototype.onMdpEvent = function (_eventName, _functionOrName) {
2830    this.player.on(_eventName, this.getFunctionOrName(_functionOrName));
2831};
2832
2833IriSP.Widgets.Widget.prototype.onMediaEvent = function (_eventName, _functionOrName) {
2834    this.media.on(_eventName, this.getFunctionOrName(_functionOrName));
2835};
2836
2837IriSP.Widgets.Widget.prototype.getWidgetAnnotations = function () {
2838    var result = null;
2839    if (typeof this.annotation_type === "undefined") {
2840        result = this.media.getAnnotations();
2841    } else if (this.annotation_type.elementType === "annotationType") {
2842        result = this.annotation_type.getAnnotations();
2843    } else {
2844        result = this.media.getAnnotationsByTypeTitle(this.annotation_type);
2845    }
2846    if (typeof this.annotation_filter !== "undefined") {
2847        return this.annotation_filter(result);
2848    } else {
2849        return result;
2850    }
2851};
2852
2853IriSP.Widgets.Widget.prototype.getWidgetAnnotationsAtTime = function () {
2854    var _time = this.media.getCurrentTime();
2855    return this.getWidgetAnnotations().filter(function (_annotation) {
2856        return _annotation.begin <= _time && _annotation.end > _time;
2857    });
2858};
2859
2860IriSP.Widgets.Widget.prototype.isLoaded = function () {
2861    var isloaded = !IriSP._(this.__subwidgets).any(function (w) {
2862        return !(w && w.isLoaded());
2863    });
2864    return isloaded;
2865};
2866
2867IriSP.Widgets.Widget.prototype.insertSubwidget = function (_selector, _widgetoptions, _propname) {
2868    var _id = _selector.attr("id"),
2869        _this = this,
2870        _type = _widgetoptions.type,
2871        $L = $LAB,
2872        key = this.__subwidgets.length;
2873    this.__subwidgets.push(null);
2874    if (typeof _id == "undefined") {
2875        _id = IriSP._.uniqueId(this.container + '_sub_widget_' + _widgetoptions.type);
2876        _selector.attr("id", _id);
2877    }
2878    _widgetoptions.container = _id;
2879    if (typeof IriSP.widgetsRequirements[_type] !== "undefined" && typeof IriSP.widgetsRequirements[_type].requires !== "undefined") {
2880        for (var _j = 0; _j < IriSP.widgetsRequirements[_type].requires.length; _j++) {
2881            $L.script(IriSP.getLib(IriSP.widgetsRequirements[_type].requires[_j]));
2882        }
2883    }
2884    $L.wait(function () {
2885        _this.player.loadWidget(_widgetoptions, function (_widget) {
2886            if (_propname) {
2887                _this[_propname] = _widget;
2888            }
2889            _this.__subwidgets[key] = _widget;
2890        });
2891    });
2892};
2893
2894/*
2895 * Position the player to the next/previous annotations based on current player position
2896 *
2897 * Parameter: offset: -1 for previous annotation, +1 for next annotation
2898 */
2899IriSP.Widgets.Widget.prototype.navigate = function (offset) {
2900    // offset is normally either -1 (previous slide) or +1 (next slide)
2901    var _this = this;
2902    var currentTime = _this.media.getCurrentTime();
2903    var annotations = _this.getWidgetAnnotations().sortBy(function (_annotation) {
2904        return _annotation.begin;
2905    });
2906    for (var i = 0; i < annotations.length; i++) {
2907        if (annotations[i].begin <= currentTime && currentTime < annotations[i].end) {
2908            // Found a current annotation - clamp i+offset value to [0, length - 1]
2909            i = Math.min(annotations.length - 1, Math.max(0, i + offset));
2910            _this.media.setCurrentTime(annotations[i].begin);
2911            break;
2912        }
2913    };
2914};
2915
2916/*
2917 * Propose an export of the widget's annotations
2918 *
2919 * Parameter: a list of annotations. If not specified, the widget's annotations will be exported.
2920 */
2921IriSP.Widgets.Widget.prototype.exportAnnotations = function (annotations) {
2922    var widget = this;
2923    if (annotations === undefined) {
2924        annotations = this.getWidgetAnnotations();
2925    }
2926    var $ = IriSP.jQuery;
2927
2928    // FIXME: this should belong to a proper serialize/deserialize component?
2929    var content = Mustache.to_html("[video:{{url}}]\n", {url: widget.media.url}) + annotations.map(function (a) { return Mustache.to_html("[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]", { a: a }); }).join("\n");
2930
2931    var el = $("<pre>")
2932            .addClass("exportContainer")
2933            .text(content)
2934            .dialog({
2935                title: "Annotation export",
2936                open: function (event, ui) {
2937                    // Select text
2938                    var range;
2939                    if (document.selection) {
2940                        range = document.body.createTextRange();
2941                        range.moveToElementText(this[0]);
2942                        range.select();
2943                    } else if (window.getSelection) {
2944                        range = document.createRange();
2945                        range.selectNode(this[0]);
2946                        window.getSelection().addRange(range);
2947                    }
2948                },
2949                autoOpen: true,
2950                width: '80%',
2951                minHeight: '400',
2952                height: 400,
2953                buttons: [ { text: "Close", click: function () { $(this).dialog("close"); } },
2954                           { text: "Download", click: function () {
2955                               a = document.createElement('a');
2956                               a.setAttribute('href', 'data:text/plain;base64,' + btoa(content));
2957                               a.setAttribute('download', 'Annotations - ' + widget.media.title.replace(/[^ \w]/g, '') + '.txt');
2958                               a.click();
2959                           } } ]
2960            });
2961};
2962
2963/**
2964 * This method responsible of drawing a widget on screen.
2965 */
2966IriSP.Widgets.Widget.prototype.draw = function () {
2967    /* implemented by "sub-classes" */
2968};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.